Hi,
ich meld mich nochmal. Also beim Praxistest bin ich auf verschiedene
Probleme gestoßen. Wenn ich mit beiden Rechnern zugleich Sende/Empfange,
hängt sich eine Seite auf. Auch der Timeout greift nicht mehr.
Theoretisch müsste es doch funktionieren...das Würde ja bedeuten, dass
sich ein UART komplett aufhängt. Ich kann mir das nicht so ganz
vorstellen!
Im Anhang nochmal der Code
Viele Grüße Florentin
Florentin S. wrote:
> Hi,> ich meld mich nochmal. Also beim Praxistest bin ich auf verschiedene> Probleme gestoßen.
Aha, dann weiß ja gleich jeder, wer Du bist und wovon Du redest.
Im Forum gibts ja nur 10 neue Beiträge pro Jahr. Da muß natürlich jeder
Leser jeden kennen, der mal was geschrieben hat.
Peter
Wie wär's, wenn du das alles kurz zusammenfassen würdest (Was hast du,
was geht nicht)? Einfach so aus Nächstenliebe wird sich hier wohl
niemand drei Threads durchlesen ;)
Also das Programm ermöglicht die Kommunikation zwischen 2 Rechnern aus
dem Programm heraus, was sonst nicht funktioniert. Mann kann mit dem
Rechner Variablen an den µC schicken oder diese Abfragen. Das
Funktioniert wunderbar. Es greift auch der Timeout bei falscher
Kommunikation. Die alles funktioniert allerdings nur, wenn man nicht mit
2 Rechnern zugleich auf den µC zugreift. Theoretisch müsste das auch
klappen, da ja jedes Byte nacheinander Verarbeitet wird. Und der µC auch
einen Puffer hat. Wenn ich mit 2 Rechnern zugleich sende greift jedoch
auch der Timeout nicht. Warum auch immer.
Naja also die 2 Uarts müssten ja theoretisch komplett unabhängig
voneinander arbeiten. Und jeder UART hat ja 2 Byte Puffer. Zeichen
können nur zwischen Cli und Sei verloren gehen. Und wenn in der Zeit was
kommt müssten si ja gepuffert werden. Und 9600k ist meiner Meinung nach
nicht so schnell im Gegensatz zu ca. 7 MHz. Komisch ist ja auch, dass nu
ein UART ausfällt und der Time out auch nicht anspringt.
Zwei Sachen:
Programmstrategie
Du hast (exemplarisch) diese Stelle im Code
1
elseif(State0==SEND_HEADER)
2
{
3
// ...
4
5
//Header Senden
6
for(i=0;i<(50-1);i++)
7
{
8
Transmit0(SendHeader[i]);
9
Summe0+=SendHeader[i];
10
}
11
12
// ...
13
}
Die zerhaut dir dein Timing. In der äussersten Schleife arbeitest du
Zeichen für Zeichen ab und jetzt kommst du irgendwann in deinem
Protokoll an diese Stelle (und vergleichbare) und schickst einzeln,
hintereinander mit "Riesenwartezeiten" Zeichen an nur eine UART raus.
Kommen hier auf der anderen UART Zeichen rein, gehen die hoffnungslos
verloren.
Du könntest dem auf mehrere Arten begegnen.
Einmal indem du einen Sendepuffer einrichtest (bzw. bei dem eh knappen
RAM einen vorhandenen Puffer dafür verwendest - hier SendHeader[]) und
wenn du dann in dem "Sendpuffer-Leer-Interrupt" die Daten rausschickst.
Dadurch erhält die andere UART Gelegenheit Daten zu empfangen ohne
Zeichen zu verlieren.
Du könntest auch wie beim Empfangen nur ein Zeichen pro Durchlauf in dem
if(State0 == SEND_HEADER) Fall rausschicken und den State0 erst
wechseln, wenn alle Zeichen raus sind.
Von beiden Lösungen halte ich die erste für geschickter. Die Delays beim
Senden auf UART-"A" könnte (sollte) man mit dem sowieso vorhandenen und
laufenden Timer behandeln. Das gibt dir noch bessere Performance beim
Empfangen auf UART-"B"
Timer
Könntest du in einfachen Worten beschreiben, wie deine Timer arbeiten
sollen?
Du hast je im Moment für die beiden UART-Kanäle zwei sehr
unterschiedliche Timer aktiv - Timer0 ist ein 8-Bit-Timer und Timer1 ist
ein 16-Bit Timer.
Meiner Ansicht nach würde es genügen, einen durchlaufenden Timer zu
haben und im Programm anhand von gemerkten Startzeitpunkten (einer pro
UART) und vorgegebenen Alarmwerten (dito.) zu prüfen, ob seit dem
letzten Empfang oder Senden ein Timeout auf dem betreffenden UART-Kanal
aufgetreten ist.
Ok vielen Danke erstmal für die Ausführliche Antwort. Also ich hab dich
bestimmt mit der Variable Timer1 durcheinander gebracht. Ich verwende
Timer1 und Timer2.
Die von dir angesprochene Stelle scheint wirklich Problematisch zu
seien. Ich werde versuchen das Problem schnellstmöglich zu lösen.
Allerdings ist das ja eine Stelle, die nur ausgeführt wird, wenn eine
Variable angefordert wird. Das Problem tritt ja aber auch schon beim
gleichzeitigen Senden auf. Also kann es nicht an der Stelle liegen. Das
ist ja das merkwürdige.
Zum Timer:
Also die Timer erzeugen nach 2 Sekunden einen Interrupt. Bei jedem
Empfangenen Zeichen wird er zurück gesetzt. Läuft er über wird der State
auf IDLE gesetzt.
Aus welcher Sicht "gleichzeitigen Senden"?
AVR als Sender
Wenn aus der Sicht des µC kommt die Stelle schon in Frage. Z.B. das
Senden auf UART-A blockiert Empfangen auf UART-A, Senden auf UART-B und
Empfangen auf UART-B. Auch alle Timeout-Checks werden in der Zeit nicht
gemacht. Möglicherweise läuft auch in der relativ langen, exklusiven
Sendezeit für den 50-Byte Block (250-300 ms!) ein Timer über und der
anschliessende Timeout-Check versagt.
Casios als Sender
Betrachtet man Senden aus Sicht der Casios sind die Delays von 4,35ms
bei jedem zu Protokollbestätigungszeichen vom µC aus kritisch zu
betrachten. Wie in einem anderen Thread berechnet, könntenbei den
verwendeten 9600 Baud mehr als vier Zeichen ankommen. angenommen UART-A
macht Handshake und UART-B wartet auf 50 Zeichen für den Header. Es ist
IMHO sehr wahrscheinlich, dass UART-B die 50 Zeichen nicht fehlerfrei
bekommt. Das in Zusammengang mit der ungeklärten Timeout-Geschichte...
Also geht darum, wenn ich mit 2 Casios zugleich sende. Also müsste ich
die Wartezeiten durch einen Timer realisieren. Also kann ich ja für
alles mit Zeit(Timeout, warten) etc. einen Timer verwenden, indem ich
immer messe, wie viel zeit schon vergangen ist. Also Überlauf bei ner
Sekunde und dann wie ne Uhr die zeit berechnen. Aber ob man das Auf ms
genau hin bekommt?Das Warten kann ich ja so realisieren, dass es im loop
der Main Schleife ggf. prüft ob die Warte Zeit schon verstrichen ist. So
können auch weiterhin Zeichen verarbeitet werden.
Bleibt halt nur die Frage, warum das Timeout nicht funktioniert.
> Bleibt halt nur die Frage, warum das Timeout nicht funktioniert.
Ich denke das ist ein Copy&Paste-Fehler. Bei den beiden Timern des
Atmega162 hast du diese Stelle drin, Kommentare von mir.
1
// Je ein 8-Bit Timer pro UART
2
TCCR0|=(1<<CS00)|(1<<CS02);// Vorteiler 1024
3
TCCR2|=(1<<CS00)|(1<<CS02);// Vorteiler 128 !!!
Das bedeutet, auf UART0 hast du bei 7,3728 MHz den gewünschten ca. 2s
Timeout. Aber auf UART1 beträgt der Timeout mehr als 16s.
Um bei Timer2 ebenfalls einen Vorteiler von 1024 einzustellen, müsste
die Zeile so lauten:
Damit hast du eine dritte Baustelle aufgemacht.
1/ Aufhänger bei zwei Casios an einem AVR
2/ Timeout
3/ Aufhänger bei "illegalem" Input vom Casio zum AVRZu 3
Kannst du ausmachen, worin der Unterschied zwischen Variable und Matris
besteht?
Und zu welchen Abweichungen bei der state machine führt das, d.h. in
welchem state fährt sich AVR mit der Matrix fest?
Zu 3 und 2
Tritt das bei beiden UARTs auf oder kommt der AVR bei einer UART durch
Timeout wieder raus?
Hallo,
ich hab mich nochmal rangesetzt und konnte den "illegalen Input"
verhindern. Der Timeout geht nicht. Ich möchte jetzt aber gleich die
Pausen etc. alles mit Timer lösen. Wie mache ich das am besten. Ich habe
mir das so gedacht, dass es sich die Startzeit merkt und dann bei Zeit
Differenz xx weiter macht etc. Nur wie realisiere ich das. Ich müsste es
ja wie eine Uhr aufbauen. Denn sonnst habe ich ja das Problem, das wenn
ich eine Aktion kurz vor Timer Überlauf starte die Differenz nicht
korrekt errechnen kann. Mit einer Uhr würde ich es aber nicht so genau
hin bekommen. Also wie kann ich so einen globalen Timer realisieren?
Viele Grüße Florentin
Florentin S. wrote:
> Denn sonst habe ich ja das Problem, das wenn> ich eine Aktion kurz vor Timer Überlauf starte die Differenz nicht> korrekt errechnen kann.
Wieso?
Zeitpunkt1 = Timerstand1 + Timerumfang * Überlaufstand1;
// 208 256 0 => 208 Ticks
Zeitpunkt2 = Timerstand2 + Timerumfang * Überlaufstand2;
// 23 256 10 => 2583 Ticks
Vergangenezeit = Zeitpunkt2 - Zeitpunkt1;
// => 2375 Ticks
Vergangenezeit = Timerstand2 + Timerumfang * Überlaufstand2 -
Timerstand1 - Timerumfang * Überlaufstand1:
Vergangenezeit = (Timerstand2 - Timerstand1) + Timerumfang *
(Überlaufstand2 - Überlaufstand1);
// (23 - 208) + 256 * (10 - 0) => 2375 Ticks
und so weiter...
Anpassungen:
Will man immer positive Werte für (Timerstand2 - Timerstand1) und
timerstand2 ist kleiner timerstand1, dann kann man künstlich einen
Überlauf wegrechnen.
Vergangenezeit = ((Timerstand2+256) - Timerstand1) + Timerumfang *
((Überlaufstand2-1) - Überlaufstand1);
(Timerstand2+256) - Timerstand1) entspricht aber gerade einem UND mit
0xFF, wenn man unsigned char Werte ohne Addition von 256 subtrahiert
// 23=0x17 - 208=0xD0 => 0xFFFFF47 => & 0xFF => 0x47=71
// 71 * 256 ((10-1))-0) => 2375 Ticks
Wenn man nicht Multiplizieren möchte, kann man
+ Timerumfang * (Überlaufstand2 - Überlaufstand1) als mehrfache Addition
implementieren.
Wenn man keine grossen Variablen möchte, kann man
für die Zeiten immer zwei getrennte Variablen führen (timerstand,
überlaufstand) quasi wie man es mit der Uhrzeit macht: Stunden, Minuten,
Sekunden statt alles in Sekunden seit der Landung in der Normandie
umzurechnen.
Bsp: vergangenezeit:
unsigned char Timerstand1 = 208;
unsigned char Timerstand2 = 23;
unsigned char Überlaufstand1 = 0;
unsigned char Überlaufstand2 = 10;
if (timerstand1 > timerstand2) // 208 > 23
Überlaufstand1++; // 0+1=1
timerstand = Timerstand2 - Timerstand1; // 23_8Bit - 208_8Bit = 71_8Bit
überlaufstand = Überlaufstand2 - Überlaufstand1; // 10-1 = 9
// 2375 Ticks entspricht timerstand=71 überlaufstand=9
Löse dich vielleicht für ein paar Tage von dem Projekt Taschenrechner
Kommunikation und implementiere einfach eine Uhr, die über RS232 alle
10s die Laufzeit seit Programmstart ausgibt.
Also ich verstehe das jetzt so, ich lasse den Timer immer laufen und
Zähle die Überlaufe, die der Timer schon gemacht hat. Oder die
Überläufe, die seit dem ersten Zeitpunkt vergangen sind. So richtig
verstehe ich das noch nicht. Es wäre ja dann aber trotzdem Möglich. das
überlaufstand zu groß für die Variable ist.
Dann mach die Variable doch grösser, also z.B. 16-Bit statt 256 Bit.
Oder richte einen Überlauf für den Überlauf ein... Wie ist es denn bei
der Uhr? Sekunden und der erste "Überlauf" sind die Minuten. Und wenn
die überlaufen? Tja dann kommen als "Überlauf für den Überlauf" die
Stunden und so weiter.
Ok also programmiere ich einfach eine Uhr mit ms-s-m-h dann merke ich
mir die Zeit und errechne daraus die Differenz. Oder hasst du das anders
gemeint?
Nu wenn ich damit rechnen will komme ich ja auch auf ne recht große
Zahl, wenn ich da die Differenz in ms ausrechnen will.
Zeit 1:
30 Min, 10s, 50 ms
Zeit 2:
30 Min, 10s, 55 ms
Zeit=
((Min * 60)*100)+(Sek*100)+ms
Zeit2-Zeit1=181055-181050=5ms
Oder löst das C selbstständig und ich kann mit so Großen Werten rechnen?
noch was ich denke Ich werde das mit CTC und nem 16 Bit Timer lösen. Hab
ich zwar noch nie gemacht, aber es gibt immer ein erstes Mal.
Ich habe 7372800 Hz
73728/7372800=0.01s
also eine ms.
Da die Auswertung des Überlaufs wie ich verstanden habe erst im nächsten
Takt folgt muss ich den CTC auf 73727 setzten oder? Nur leider passt das
nicht in den Timer. Kann ich ihn da bis 36862 zählen lasen und alle 2
Resets eine ms erhöhen? Oder lieber Prescaler 8 also9 9216-1 ins CTC?
Also ne möglichkeit auf µs genau zu machen habe ich noch nicht gefunden.
Ich komme nie auf einen ganzzahligen Wert.
sind natürlich 1000 sorry für den tipp Fehler.
Ich habe jetzt auch eine Funktion geschrieben, welche die aktuelle Zeit
speichert. Sie arbeitet mit Zeigern, was ich noch nie zuvor gemacht
habe. Der Compiler spuckt auch eine Warnung aus:
"assignment makes integer from pointer without a cast"
hier der Code:
1
voidTime(int*ms,uint8_t*s,uint8_t*min){
2
*ms=ms;
3
*s=s;
4
*min=min;
5
}
und hier der Funktionsaufruf:
1
Time(&Timeout11_ms,&Timeout11_s,&Timeout11_min);
kann das sein, dass der Fehler daher kommt, weil die Speicheradresse zu
groß für uint8_t ist?
Nee du hast zweimal (mal drei) den gleichen Variablennamen.
Beispiel:
ms ist deine globale Variable für die Millisekunden
und (!!!)
ms ist ein Pointer als dein lokales Funktionsargument in Time.
Innerhalb der Funktion Time fällt wegen Namensgleichheit die globale
Variable weg, der Code sieht nur die zuletzt definierte lokale Variable
mit gleichem Namen. Die Zuweisung ist dann Unfug und die Warnung kommt.
Schreib Time() so
void Time (int *pms, uint8_t *ps, uint8_t *pmin)
{
*pms = ms;
*ps = s;
*pmin = min;
}
Vielen dank ich habs vorhin auch durch probieren raus gefunden. Ich
dachte ne Pointer Variable ist was anderes als ne normale. Egal. Auf
jeden Fall funktioniert irgend etwas nicht so, wie es soll...auch der
Timeout springt nicht an. Es ist vermutlich irgendwas an der
Initialisierung falsch. Ich habe nochmal den kompletten Code angehangen:
Ich kann (und will) diesen Code nicht testen, sorry. Um an den
eigentlich zu debuggenden Code für den Timer zu kommen, müsste ich mich
durch die beiden UARTs durchbeissen. Das macht mir keinen Spass.
Du hättest es einfacher, wenn du den fraglichen Timercode in ein kleines
Extraprojekt von übersichtlichen 30-50 Zeilen packst, dieses lauffähig
machst und dann mit dem gewonnenen Wissen dein Hauptprojekt verbesserst.
der Teil wird ja nur ausgeführt, wenn ein Byte empfangen wurde. Ich muss
also eine Art warte Code machen, der das Byte dann sendet aber so lange
noch andere bytes bearbeiten kann. Ich habe mir das so gedacht, dass ich
noch eine While schleife um die Byteverarbeitung mache, welche immer
solange ausgeführt wird, bis die warte Zeit um ist. Aber wie bekomme ich
dass dann hin, das an der richtigen Stelle weiter gemacht wird?
Aufdrieseln ist gut. Exemplarisch hast du diese Stelle im Code:
1
//Request
2
elseif(State1==REQUEST){
3
4
//Nur die 0x06 aufnhemen!
5
6
State1=SEND_HEADER;
7
//WARTESCHLEIFE!!!!!!!
8
_delay_ms(4.35);
9
10
}
Jetzt kann die Statemachine so erweitert werden, dass es einen
zusätzlichen Wartezustand gibt.
1
#if 0
2
// werden von Timer-Interrupt befüllt
3
// sollten volatile sein!
4
volatile int ms; // Millisekunden
5
volatile uint8_t s; // Sekunden
6
volatile uint8_t min; // Minuten
7
//
8
// Die ganze Rechnung mit Minuten/Sekunden... sparen wir uns für
9
// die kleinen Delays
10
//
11
// Dafür brauchen wir einen einmal gestarteten Zähler für die
12
// Millisekunden seit RESET (Timerstart).
13
// uint32_t reicht für 49,7 Tage bis ein Überlauf kommt.
14
// (Wenn nicht strategisch im Programm zurückgesetzt wird z.B.
15
// wenn beide Statemachines im IDLE Modus sind)
16
//
17
volatile uint32_t total_ms; // wird wie ms einfach im IRQ hochgezählt
18
uint32_t startzeit1;
19
#endif
20
21
//Request
22
elseif(State1==REQUEST){
23
State1=REQUEST_435_WARTEN;
24
startzeit1=totalms;
25
}
26
//Request
27
elseif(State1==REQUEST_435_WARTEN){
28
// 4.35 leider unterhalb der Timer-Auflösung
29
if((totalms-startzeit1)>=4)
30
{
31
// ggf. Verfeinern oder weglassen und 5 Millisekunden warten
32
// was robuster beim Transfer µC <> Casio ist
33
if((totalms-startzeit1)==4)
34
_delay_ms(0.35);
35
State1=SEND_HEADER;
36
}
37
}
In den Transmit1/Transmit0 Routinen, die ja eigene _delay_ms() haben,
lässt sich das Warten so nicht behandeln. Dort muss eine andere Lösung
her und zwar muss das Senden parallel zum Hauptprogramm laufen.
D.h. im Interrupt und es muss einen Puffer gehen (bzw. können die
vorhandenen Puffer benutzt werden) und es muss eine Abfrage geben, ob
der Puffer komplett gesendet wurde.
Grundsätzlich hat man die Wahl, welchen Interrupt man für das Senden
benutzt. Man könnte auch den TX-Puffer-leer-Interrupt nehmen oder den
TX-Zeichen-erfolgreich-verschickt-Interrupt.
Ich schlage aber vor, den vorhandenen Timer dafür zu benutzen. Das
Senden erfolgt von dir (bzw. dem Casio Protokoll) vorgeheben
grundsätzlich nur alle 4,35 ms. Das ist langsam und kann in jedem 4./5.
Timer-Interrupt (der mit 1 ms Auflösung läuft) gut gemanagt werden.
Nähme man einen der UART Interrupts, würden die sehr oft ohne eine
Aktion zu machen aufgerufen, weil ja zuerst die Zeit abgewartet werden
muss.
Im Hauptprogramm ändert sich der Teil in etwa (exemplarisch) so:
1
//wenn status SEND_HEADER
2
elseif(State1==SEND_HEADER)
3
{
4
SendHeader[12-1]=VarName1;
5
// Umkopieren (bzw. mit Pointer arbeiten s.u.)
6
for(i=0;i<(50-1);i++)
7
interruptsendepuffer1[i]=SendHeader[i]);
8
9
// 50 Zeichen müssen raus
10
// die folgende Zeile muss ein atomarer Zugriff sein,
11
// d.h. uint8_t interruptsendeanzahl1
12
// oder Interrupts müssen gesperrt werden!
13
interruptsendeanzahl1=50;
14
15
// warten, aber der anderen Statemachine Gelegenheit
16
// zum Reagieren geben!
17
State1==SEND_HEADER_WARTEN;
18
}
19
elseif(State1==SEND_HEADER_WARTEN)
20
{
21
// Das ist das neue Warten
22
// nur weiterschalten, wenn Sendepuffer leer
23
if(interruptsendeanzahl1==0)
24
State1==SEND_HEADER_CHECKSUMME;
25
}
26
elseif(State1==SEND_HEADER_CHECKSUMME)
27
{
28
// Warten fertig, jetzt Checksumme anhängen
29
uint8_tSumme1=0;
30
uint8_tChecksumme1;
31
32
for(i=0;i<(50-1);i++)
33
Summe1+=SendHeader[i];
34
35
Checksumme1=0x3a;
36
Checksumme1-=Summe1;
37
38
// wie oben
39
interruptsendepuffer1[0]=Checksumme1;
40
interruptsendeanzahl1=1;
41
State1==SEND_HEADER_CHECKSUMME_WARTEN;
42
}
43
elseif(State1==SEND_HEADER_CHECKSUMME_WARTEN)
44
{
45
// Nur weiterschalten, wenn Sendepuffer leer
46
if(interruptsendeanzahl1==0)
47
State1==ACK1;
48
}
Das Senden im Timer-Interrupt sähe dann in etwa so aus:
1
//Timer1 Compare Match Interrupt
2
ISR(TIMER1_COMPA_vect)
3
{
4
//Millisekunden um 1 erhoehen
5
totalms++;
6
ms++;
7
8
//sind 1000 ms um?
9
if(ms==1000){
10
//ms zurueck setzen und Sekunden erhoehen
11
ms=0;
12
s++;
13
//sind 60 Sekunden um?
14
if(s==60){
15
//Sekunden zurueck setzen und Minuten erhoehen
16
s=0;
17
min++;
18
}
19
}
20
21
// Transmit0 Ersatz
22
23
// Kann grundsätzlich gesendet werden?
24
// Wenn nicht sind wir schon fertig,
25
// und hoffen auf die nächste Millisekunde
26
if((UCSR0A&(1<<UDRE0))
27
{
28
// Ja wir dürfen. Jetzt prüfen, ob seit letztem Senden min.
29
// 4.35 ms vergangen sind
30
// globale Variable uint32_t startms0 muss angelegt sein, Startwert 0!
31
if(totalms-startms0>4)
32
{
33
// wir dürfen. Zeitpunkt merken für das nächste Zeichen
34
startms0=totalms;
35
36
// Zeichen da?
37
if(interruptsendeanzahl0)
38
{
39
// globale Variable int interruptsendezähler0 = 0 anlegen!
Wenn man Platz sparen muss, kann man wenn man vorsichtig ist mit
Pointern arbeiten. Statt dem interruptsendepuffer1[] wird z.B. ein
Pointer auf SendHeader[] gerichtet.
Ich hoffe du bekommst die Idee hinter dem Ganzen mit, auch wenn ich den
Code nicht komplett ausformuliert habe.
Florentin S. wrote:
> !Ok so klappt es...Nur wird der Code immer un übersichtlicher.
Deswegen wurden Editoren erfunden, die den Code einklappen können. Wenn
jemand mit etwas Gefühl für Ästhetik den Quellcode massiert, sieht das
gleich besser aus. Das Aussehen sollte dich im Moment nicht stören. Das
ist im Moment Work in Progress, kein Schönheitswettbewerb ;-)
also so wie du das im ersten Code hasst, so habe ich das ja hetzt auch
implementiert. Ich musste es aber wie gesagt außerhalb der
empfangsroutine anbringen, da diese ja nur ausgeführt wird, wenn ein
Zeichen ankommt. Also wenn ich für die ms uint32_t verwende kann ich
quasi Minuten und Sekunden gleich weglassen, wenn das so lange reicht.
Das würde dann das Zeit holen auch noch mal vereinfachen. Das senden
habe ich auch grob verstanden. Es schreibt die zu Sendenden Bytes immer
in einen Puffer und beim Interrupt wird immer ein gesendet.
Also das Senden seitens des µCs funktioniert leider nicht.
interruptsendeanzahl1 = 50; das müsste ja eigentlich 49 seinen. Das
klappt aber auch nicht so.
Ich habe jetzt deine Source von 23.01.2008 23:28 genommen und einiges
geändert. Wenn du das nachvollziehen willst, aufpassen, dass du ein
Backup deiner Ur-Version machst!
Die Änderungen sind
- Das Senden geschieht jetzt in einem Sendeinterrupt der UARTs
- Jedes Senden geschieht im Interrupt. Die Transmit0/Transmit1 füttern
nur
die Interruptroutine.
- Paar Variablen wurden eingespart und der Code wurde gestrafft
("Beautifiziert")
- viele Kommentare warum und wieso was gemacht wird
ADD:
Bei ungültigen Zeichen die einzelnen Variablennamen und Puffernamen in
der Source mal checken. Bei dem vielen Copy&Paste zwischen UART0 und
UART1 Teilen, flutscht gerne mal ein Schreifehler bei den 9/1 Anhängseln
durch. An einer Stelle ist mir ein solcher Fehler aufgefallen (<==
markiert), einen habe ich selbst fabriziert und dies ist die zweite
Version des Uploads, eine musste ich löschen nachdem ich einen Fehler
gesehen habe.
> Das Programm hängt sich auch nicht auf sondern sendet nur irgendwas> falsch.
Wenn du keinen Fehler im Protokoll an sich meinst (Hänger beim
Handshake, Hänger in der stete machine, Timeouts und
Prozokollresets...), sondern falsche Datensätze, dann habe ich dazu
inzwischen auch eine Theorie.
Ein Casio kann ja auf dem AVR Daten ablegen und Daten abholen, die der
andere Casio abgelegt hat. Die Protokolle dafür sind eingerichtet (und
hoffentlich fehlerfrei).
Aber was passiert, wenn ein Casio Daten abholen will, aber der andere
Casio noch keine Daten abgelegt hat? Im Moment bekommt dann der Anfrager
illegale Daten. Es fehlt nämlich eine Kennzeichnung der Daten
gültig/ungültig.
Grundsätzlich haben beide Varianten das Problem. Es tritt im Moment wohl
eher auf als vorher. Das kann den Grund haben, dass vorher der AVR lange
mit einem Casio beschäftigt war und der andere Casio nur selten eine
Chance hatte, um seine Anfrage nach Daten abzugeben.
Jetzt mit dem fast gleichzeitigen Reagieren auf beide Casios und einem
hoffentlich funktionierenden Timeout (!) kann es eher vorkommen, dass
einer was abfragt und der andere noch nichts gültiges geliefert hat.
Um die Kennzeichnung gültig/ungültig im Programm zu vermerken, könnte
man sechs einzelne Bits in einer 8-Bit Variablen pro Casio verwenden.
Das Setzen würde im SAVE Teil erfolgen und das Prüfen im SEND_DATA Teil.
Ist es richtig, dass es pro Casio sechs Datensätze (Variablennamen
A,B,C,D,E,F) gibt, die jeweils 16 Bytes lang sind? Auf 6*16 komme ich
wegen der 100 Byte Länge des Datenpuffers in dem die Datensätze
hintereinander abgelegt werden.
Hallo,
also an deiner Version ist leider etwas falsch. Selbst beim senden an
den µC kommt ein Error.
Zu deiner Theorie ja das Problem hatte ich auch einmal. Ich lade aber am
Anfang gleich Standardwerte in den µc:
1
//Werte laden
2
for(Temp=0;Temp<96;Temp+=16){
3
4
5
for(i=0;i<16;i++){
6
7
Daten0[Temp+i]=Default[i];
8
Daten1[Temp+i]=Default[i];
9
10
}
11
12
}
also daran liegt es nicht. Ich habe nochmal den Code angehangen, der
Funktioniert. Synchrones Senden an den µC klappt wunderbar das
gleichzeitige Empfangen nur, wenn man das Empfangen am Rechner zur
gleichen Zeit startet. Versetzt klappt es leider noch nicht. Ja und du
hasst recht ich verwende im Moment nur die Variablen A-F, weil zum
Testen reicht das erstmal und ich hab noch genug Ram zum arbeiten.
Ich kann es halt nur schreiben und kompilieren. Schon Simulieren wäre
eine Pein, weil ich dazu zwei UARTS mit einem rel. umfangreichen
Protokoll füttern müsste.
Und über Debuganzeigen (wenigstens ein paar LED oder so), um wenigstens
den Status im Protokoll festzustellen, hatten wir uns schon mal glaub
ich erfolglos unterhalten.
Na statt LED kann ich wie gesagt auch einen Pin auf High schalten und
testen. Ich muss mir mal LED's mit Vorwiderstand besorgen, die würden
noch drauf passen. Ich könnte nochmal schritt für Schritt probieren,
deine Änderungen zu implementieren. Ich probier jetzt erstmal die
Interrupt Senden Funktion einzubauen.
Mit deinem Testcode für IRQ&Transmit0 und einem Transmit0('#') ale
einziger Code in der while(1)-Schleife bekomme ich in der Simulation
ein Programm, das brav ein Zeichen nach dem anderen über UART0 sendet.
Aber du hast Recht, in dem Code von gestern ist ein Bug in dem
Interrupthandler drin.
Hier die entwanzte Version für IRQ&Transmit0, entsprechendes gilt für
IRQ&Transmit1.
Die Namen habe ich mit ..._num... und ..._cnt... etwas sinnvoller
gewählt als vorher (Search&Replace im Editor ...cnt durch ...num, dann
...i durch ...cnt).
1
ISR(USART0_UDRE_vect)
2
{
3
if(irq_send_num0)
4
{
5
UDR0=irq_send_buf0[irq_send_cnt0++];
6
irq_send_num0--;// Simulation: hier Breakpoint #1
7
}
8
9
//
10
// nicht else!
11
// irq_send_cnt0 sofort zurücksetzen, nicht erst warten,
12
// bis der nächste Interrupt kommt. Das geht nämlich
13
// schief, wenn eine nicht IRQ-Routine irq_send_num0 un-
14
// gleich 0 setzt
15
//
16
if(!irq_send_num0)
17
irq_send_cnt0=0;
18
}
19
20
voidTransmit0(intSenden)
21
{
22
// ggf. warten bis irq_send_buf[] leer ist
23
// optimierbar mit Anhängen an Puffer usw.
24
while(irq_send_num0);
25
26
irq_send_buf0[0]=Senden;
27
irq_send_num0=1;// Simulation: hier Breakpoint #2
28
}
Die Wanze hatte sich in dem else Fall im Interrupthandler versteckt.
Sehr merkwürdig. Aber irgendwas ist komisch ich habe jetzt nochmal
deinen Code genommen. Und da kommt der Com-Error unmittelbar nach dem
absenden...bei der minimal Variante dauert es eine Weile, bis der Error
erscheint. Es ist trotzdem merkwürdig, das es nicht funktioniert. Es
wurde ja nur das Transmit durch einen Interrupt ersetzt.
Interessant ist, dass sich der komplette µC aufhängt. Auch der Timeout
greift nicht. Auch wenn ich die Timeout Überprüfung in den
Timerinterrupt nehme greift er nicht. Es scheint also in nem Interrupt
festzuhängen.
Das ist ein sehr guter Hinweis.
Die Däumchendreher sind tatsächlich die neuen
Sendepuffer-leer-Interrupts. Die sollen so wie ich im Datenblatt sehe
(Transmitter Flags and Interrupts) nur angeschaltet sein, wenn
tatsächlich Daten geschrieben werden sollen/müssen.
ISR(USART0_UDRE_vect)
{
if (irq_send_num0)
{
UDR0 = irq_send_buf0[irq_send_cnt0++];
irq_send_num0--; // Simulation: hier Breakpoint #1
}
//
// nicht else!
// irq_send_cnt0 sofort zurücksetzen, nicht erst warten,
// bis der nächste Interrupt kommt. Das geht nämlich
// schief, wenn eine nicht IRQ-Routine irq_send_num0 un-
// gleich 0 setzt
//
if (!irq_send_num0)
{
irq_send_cnt0 = 0;
// fertig diesen IRQ abschalten
USART0_UDRE_ABSCHALTEN();
}
}
void Transmit0(int Senden)
{
// ggf. warten bis irq_send_buf[] leer ist
// optimierbar mit Anhängen an Puffer usw.
while(irq_send_num0);
irq_send_buf0[0] = Senden;
irq_send_num0 = 1; // Simulation: hier Breakpoint #2
USART0_UDRE_ANSCHALTEN();
}
Entsprechend diese Sequenz an den verschiedenen Stellen in main()
irq_send_num0 = ...;
USART0_UDRE_ANSCHALTEN();
auch benutzen, wenn 50/16/... Bytes gesendet werden.
Die neuen Funktionen als Makros:
#define USART0_UDRE_ANSCHALTEN() ( UCSR0B |= (1<<UDRIE0) )
#define USART0_UDRE_ABSCHALTEN() ( UCSR0B &= ~(1<<UDRIE0) )
In der Initialisierung in main() ist das Einschalten des Interrupts
herauszunehmen.
UCSR0B |= (1<<RXCIE0)|(1<<RXEN0)|(1<<TXEN0) /* |(1<<UDRIE0) */;
Das ganze für USART1 entsprechend auch.
Ok das scheint es gewesen zu sein. Ich hab jetzt mal die Interrupts rein
genommen und die Transmits geändert. Nur verstehe ich nicht, wieso ich
das in die main schreiben soll. Das geschieht doch schon automatisch
beim Senden das aktivieren/deaktivieren. Auf jeden Fall geht es jetzt.
Jetzt werde ich mich mal an das Senden mittels Buffer machen.
Dort (und an den anderen ähnlichen Stellen)
irq_send_num0=49;
State0 = SEND_HEADER_WARTEN;
muss auch der Sendepuffer-leer-Interrupt wieder eingeschaltet werden,
also
ändern in:
irq_send_num0 = 49;
USART0_UDRE_ANSCHALTEN();
State0 = SEND_HEADER_WARTEN;
Das muss so gemacht werden, weil jetzt nicht mehr alle Sendeaufgaben
über die einzelzeichenorientierte Transmitx() gehen, sondern im
Hauptprogramm der IRQ-Sendepuffer direkt befüllt wird.
Grundsätzlich sollte es an deiner Position auch funktionieren, die state
machine kommt aber erst eine Runde später dort vorbei und man
"vertrödelt" etwas Zeit.
BTW. die 49 oben macht mich stutzig. In der Version vom 20080124 hatte
ich bereits eine Änderung drin bei der die Checksumme in den Header
geschrieben wurde und direkt 50 Bytes statt getrennt 49 dann 1 Bytes
gesendet wurden. Das spart u.a. ein paar Variablen und ein paar States
(SEND_HEADER_CHECKSUMME, SEND_HEADER_CHECKSUMME_WARTEN).
Das Weiterschalten auf SEND_HEADER_CHECKSUMME_WARTEN in
SEND_HEADER_CHECKSUMME fehlt bei dir übrigens und das halte ich für die
Problemursache. Die state maschine fährt sich in SEND_HEADER_CHECKSUMME
fest.
Ich habe im Moment zu wenig Zeit, um alle Änderungen hier einzubauen und
die komplette Source anzuhängen. Vielleicht geht es heute abend oder
morgen.
Danke...war ein blöder Fehler jetzt klappt es. Dann habe ich noch was
entdeckt, was die gleichzeitige Kommunikation stören könnte/wird. Der
Variablen Name wird direkt in den Send_Header geschrieben. Wenn beide zu
gleich auf Empfang sind kommt da Brühe raus, sofern sie eine andere
Variable anfordern. Deswegen finde ich es auch besser, wenn die
Checksumme angegangen wird. Man könnte natürlich jedem UART einen
eigenen Send_Header zuordnen, was ja auch wieder Platz kostet.
Selber schreiben brauchst du es nicht. Ich finde man lernt beim selber
schreiben mehr, was ja mein Ziel ist. Über Tipps bin ich natürlich immer
dankbar.
So ich habs jetzt mal komplett geändert...das ganze funktioniert
wunderbar auch syncron. Es hat nur einen Hacken. Receive(A)
funktioniert. Receive(B) Klappt nicht. Irgendwas hab ich geändert und
nun gehts nicht mehr...
Sendheader nicht zu verändern ist eine gute Idee. Das hilft, wenn man
für später daran denkt, die konstanten (read-only) Arrays ins ROM zu
schaffen. Dort sind knapp 80% noch frei, während im RAM schon fast 70%
belegt sind.
Neuschreiben meinte ich auch nicht. Ich dachte daran, wieder eine
konsolidierte Version zu schaffen, in der aller Änderungen seit der
Umstellung auf das interruptbasierte Senden sicher mit IRQ
An-/Abschalten implementiert sind.
Das habe ich jetzt gemacht. Deine Sendheader-Geschichte habe ich auch
eingebaut. Und einen Fehlercheck bzgl. dem Variablenname (nur A-F
erlaubt, bei anderen Namen wird A eingesetzt).
Also dein Version funktioniert dür UART1 beim 0 kommt sowohl beim Senden
als auch beim Empfangen nen Error. Was hasst du gegenüber meiner Version
jetzt noch verändert? Und weist du wo der Fehler herkam, der das mit
Reveive(B) etc. verursacht hat?
Zu dem Unterschied deiner letzten Version bzgl. Variablenname kann ich
nichts sagen.
Ich hatte meine letzte Version genommen, deine Sendpuffer-Idee
nachprogrammiert und die IRQ-Geschichte fast richtig gemacht.
Fast richtg...
Durch Copy & Paste & Umfummeln von 0 nach 1 schleichen sich Fehler ein.
Man muss alle Stellen peinlich nachkontrollieren, damit man nach ^C und
^V nicht die 0/1 Änderung verpasst.
U.a. habe ich jetzt einen 0/1 Vertauscherfehler gefunden in
ISR(USART1_UDRE_vect) => Richtige USART abschalten!!!
Der Vertauscher führt dazu, dass ein fertiges Senden bei UART1 das
Senden bei UART0 abschaltet. Die Sendeblockade (Error) auf UART0 ist
also erklärbar und wahrscheinlich einfach behebbar.
der Error ist hier:
if(FlagACKTests0 == AKTIV)
wo genau weis ich nicht. Ich habe einfach nach und nach alles vom UART 1
kopiert wieder auf 0 geändert und mittels vertify nach Differenzen
gesucht!
Änderungen
1/ Debugcode
Ob es tatsächlich in an der von dir vermuteten Stelle hakt, kann man mit
einer Debugausgabe auf einen der beiden freien Pins (PA0 PA1) an PortA
herausfinden. Ich habe den Code dafür in dem Anhang: PA0 entspricht dem
Zustand von FlagACKtests0 und PA1 dem von FlagACKtests1. Hast du
vielleicht noch mehr freie Pins?
2/ RAM Einsparung
Jetzt werden 116 Bytes weniger benutzt. EndHeader0 und EndHeader1 wurden
eingespart und Default[]. Daten0[] und Daten1[] werden bereits zur
Compilezeit initialisiert statt erst zur Laufzeit)
3/ Timeout-Abfrage
Die Timeout-Abfrage wurde aus der Hauptschleife in den Timer-Interrupt
verlagert. Das holt ein hängendes Programm aus dem Sumpf auch wenn die
Hauptschleife nicht mehr durchlaufen wird. Ist das Programmverhalten
eigentlich vom Timeout-Wert (derzeit 2000) abhängig? Ich habe den Wert
mal als Define eingesetzt so dass man es rel. einfach mit einem
wesentlich höheren Wert probieren kann.
4/ IDLE Zustand
Der IDLE Zustand wird jetzt in der Hauptschleife behandelt und
initialisiert alle lebenswichtigen Variablen.
5/ Änderung im Status REC_FOOTER_ACK
Wenn die Statemachine Status REC_FOOTER_ACK ist und keine Variable
gespeichert werden soll (Header?[5] != 'V'), welchen Status soll die
Statemachine dann einnehmen? Im Moment verharrt sie dann im Status
REC_FOOTER_ACK. Dieser Hänger wäre dann auch in der Nähe von der
FlagACKTests Abfrage, so wie du es beobachtet hast. Die
Sicherheitsabfrage (Header?[5] == 'V' im Status REC_FOOTER_ACK) ist eine
rel. kurzfristige Änderung von dir, jedenfalls wurde die noch nicht mit
lauffähigem Emfangen/Senden Code getestet. Man könnte meiner Meinung
nach im else-Fall in den Zustand IDLE gehen oder die Sicherheitsabfrage
entfernen und immer in den Zustand SAVE (so wie jetzt im Code) gehen,
denn das Programm kommt aus REC_DATA und hatte eine Sicherheitsabfrage
bereits erfolgreich überstanden (in REC_HEADER_ACK).
Fragen
1/ Sehe ich die verschiedenen Zustände und den Protokollablauf im
Sourcecode so richtig?
Empfangen
IDLE (bis 0x15 empfangen)
HEADER_INIT_ACK (5ms warten, 0x13 senden)
REC_HEADER (50 Zeichen empfangen)
REC_HEADER_ACK (5ms warten, 0x06 senden)
=> REQUEST (Header?[1] == 'R' && Header?[5] == 'V')
oder
=> REC_DATA (Header?[1] == 'V' && Header?[5] == 'V')
oder
=> IDLE (Header?[1] != 'V' && Header?[1] != 'R' || Header?[5]
!= 'V')
Empfangen Untervariante 1
REC_DATA (16 Zeichen empfangen)
REC_DATA_ACK (5ms warten, 0x06 senden)
REC_FOOTER (50 Zeichen empfangen)
REC_FOOTER_ACK (5ms warten, 0x06 senden)
=> SAVE (Header?[5] == 'V')
oder
=> ??? (Header?[5] != 'V' Fall siehe oben)
Empfangen Untervariante 2
SAVE (empfangene Daten speichern)
IDLE
Senden
REQUEST (beliebiges Zeichen empfangen)
REQUEST_WAIT (5ms warten)
SEND_HEADER (50 Bytes senden)
SEND_HEADER_WARTEN (5ms warten)
ACK1 (beliebiges Zeichen empfangen)
ACK1_WAIT (5ms warten)
SEND_DATA (16 Bytes senden)
SEND_DATA_WARTEN (5ms warten)
ACK2 (beliebiges Zeichen empfangen)
ACK2_WAIT (5ms warten)
SEND_ENDHEADER (50 Bytes senden)
SEND_IDLE_WARTEN (5ms warten)
IDLE
2/ Ändern sich die Hänger wenn die Timeoutzeit geändert wird?
3/ Was zeigen die Debugpins an, wenn es hakt?
ich hatte mich vielleicht blöd ausgedrückt, aber der Code hat ja
funktioniert, wo ich die besagte Stelle von UART1 kopiert habe und sie
dann wieder in UART0 geändert habe. Da war sicher nur ein Tippfehler.
Ich werde trotzdem mal deine Änderungen einbauen.
Zu den Fragen:
also so weit ich das Überblicken konnte ist der Ablauf korrekt. Das
Timeout habe ich jetzt mal auf 2 Sekunden gewählt, da ja ab dem Senden
der Status nicht zurückgesetzt wird. Deswegen die 2 Sekunden. Zu der
Überprüfung in REC_HADER_ACK: Also Header[1] gibt an, ob vom Rechner
gesendet wird(V) oder ob der Rechner eine Variable anfordert(R).
Header[5] steht für den Typ. V ist Variable. Aber so weit ich das sehe
sind doch alle falschen Inputs abgesichert. Die Abfrage hatte ih
eigentlich getestet und sie Funktioniert. Ich hatte den Timeout
abgeschalten und mal eine Matrix gesendet/Empfangen und kann danach
problemlos weiter kommunizieren. Ohne dies Änderung blieb es hängen.