Forum: Mikrocontroller und Digitale Elektronik Probleme und Anregungen mit USART auf ATMega32


von Hack K. (hackerfleisch)


Lesenswert?

Hi zusammen!

Zu alles erst, wenn Ihr eine bessere und sichere Lösung habt, bitte 
posten. Ich muss in der Main zyklisch prüfen ob etwas empfangen wurde, 
wenn ja was für Zeichen. Es sind Kommandos, welche immer mit einem 
Buchstabe anfangen (wird Dezimal im ASCII vom PC gesendet), danach 
folgen byteweise zahlen mit einer 16er Basis (HEX Zeichen), diese muss 
ich dann erst in Dezimal umwandeln um diese weiter zu verarbeiten).

Zum Code:
Ich benutze einen ATMega32, u.a. auch das USART Interface um Daten vom 
und zum PC zu schicken.
Dazu benutze ich den "USART_RXC_vect" Interrupt.
Wenn ein Zeichen rein kommt, wird dieses erst in einen puffer-array 
geschrieben, dann wird der Index (schreibindex) des arrays erhöht (bedes 
geschiet in der ISR).

Parallel prüfe ich in der Main ob mein leseindex != schreibindex ist. 
Wenn ja, dann wurde ein Zeichen empfangen, erhöhe mein leseindex um 1 
und lese die stelle vom array aus (das ist ja das neue Zeichen).
Funktioniert auch, jedoch bemerke ich, das der gesamt puffer (also die 
definierte array größe) nie immer komplett durchlaufen wird.
Ich habe normalerweise eine arraygröße von 200 zeilen (0 - 199) á 8 Bit 
breite (uint8_t).
Ich prüfe in der ISR und in der Funktion, welche von der Main zyklisch 
aufgerufen wird, ob der lese- bzw. schreibindex < 200 ist, wenn nicht, 
dann setze ihn wieder auf 0.

Wenn ich mir jetzt den leseindex und den schreibindex über zwei PORTS 
über LEDs ausgebe, sehe ich, das manchemal nur bis 22, 135 ... gezählt 
wird und nicht bis 199 (aber beide indexe setzen sich wieder 
gleichzeitig auf 0).
Ein paar mal hatte ich sogar, dass (NUR) der leseindex nach dem überlauf 
(>= 200) nicht auf 0 zurückgesetzt wird, sondern weiter erhöht wird 
(LEDs haben wild geflackert -> schnell hochgezählt).

Anbei mal ein paar code schnipsel.
Habe Ihr eine Idee woran da liegen kann? Oder einen Vorschlag, welcher 
zu 100% funktioniert.

Ich muss einfach prüfen ob ein Zeichen rein kam, wenn ja muss ich prüfen 
welches ZEichen es ist, dann ob wieder ein Zeichen empfangen wurde, wenn 
ja passt es zu meinem erwarteten Muster usw.
Z.b. habe ich ein Kommando: "Q1234;"
"Q" wird in ASCII geschickt, d.h. ich prüfe in der Main ob der ASCII 
code von "Q" rein kam. Wenn ja, dann müssen 4 Zahlen mit der Basis 16 
folgen -> ich springe in eine for-schleife und hol mir jedesmal das neue 
Zeichen. Am schluss muss ein ";" in ASCII folgen. Wenn alles passt mache 
etwas, wenn wo ein Fehler bemerkt wurde, schicke ein Error code an den 
PC.


Code schnipsel:
1
#define MAXPUFFER 200
2
.
3
.
4
5
6
volatile uint8_t puffer[(MAXPUFFER)]; //USART receive buffer.
7
uint8_t read_idx = 0;
8
volatile uint8_t write_idx = 0;
9
.
10
.

Funktion, welche von der Main aus zyklisch aufgerufen wird:
Wenn nichts empfangen wurde, dann return 255, ansonsten das empfangene 
Byte.
Die erste Zeile in dieser Funktion ruft eine andere Funktion auf, diese 
wartet keine kurze Zeit da ich mit 19200 baud sende und die Main 
eventuell zu schnell sein kann mit dem abrufen eines Zeichens da das 
neue noch nicht ankam. diese func wartet je nach Bausrate eine gewisse 
Zeit, hier ~520µs.
1
int USART_get_Byte(void)
2
{
3
  DelayWaitTimeSerial(); //Description is in this Function
4
  if (read_idx != write_idx) //If something received in array "puffer"
5
  {
6
    uint8_t Byte = 0;
7
    if (read_idx < 200)
8
    {
9
      Byte = puffer[read_idx]; //Read out current Byte
10
      read_idx++; //increment the read-index to next array position.
11
    }
12
    else
13
    {
14
      read_idx = 0;
15
    }
16
    return Byte;
17
  }
18
  return 255;
19
}


Hier der Interrupt welche beim Empfangen eines Zeichen ausgelößt wird:
1
ISR(USART_RXC_vect)
2
{  
3
  if (write_idx < 200)
4
  {
5
    puffer[write_idx] = UDR;
6
    write_idx++;
7
  }
8
  else
9
  {
10
    write_idx = 0;
11
  }
12
}


Schon mal vielen Dank für eure Hilfe!!

Grüße
Thomas D.

von m.n. (Gast)


Lesenswert?

Von hinten noch vorn:
write_idx++;
if(write_idx > 200) write_idx = 0;

Anstatt Schreib- und Lesezeiger zu vergleichen, kann man auch die Anzahl 
der Bytes im Puffer mitzählen: anzahl++ bei neuem Zeichen, anzahl-- wenn 
es aus dem Puffer gelesen wird.

Hat Dein Datenstring ein festes Endzeichen? ";" beispielsweise?
Dann setze in der ISR ein Flag, wenn das Endzeichen empfangen wurde und 
starte dann erst mit der Auswertung. Oder verwende einen Zwischenpuffer, 
der die Zeichenkette bis zum Endzeichen einliest und dann konvertiert.

Die Routinen für Zwischenpufferung und Auswertung werden von main() zwar 
aufgerufen, aber nicht in main() selbst eingefügt.

Ich hoffe, meine Ausführungen sind nicht zu knapp :-)

von Hack K. (hackerfleisch)


Lesenswert?

Hi m.n.!

Ja, die Zeichenketten haben immer einen "delimiter" (";" also in ASCII 
== 59).
D.h. es kommt ein Zeichen (Kommando) rein, je nach kommando gibt es 
unterschiedliche anzahl von folgenden zahlen mit der basis 16 und dann 
immer ein ";".

Jupp, dass ich die anzahl der empfangenen Zeichen hoch (beim empfangen) 
und runter (beim lesen) zähle wäre auch eine Möglichkeit! D.h ich lese 
dann immer so lang Zeichen für Zeichen bis eine zählvariable z.b. wieder 
== 0 ist.
Jedoch fange ich erst an zu zählen wenn ein Flag gesetzt wurde, dass 
z.B. ein ";" empfangen wurde.
Ich hoff nur, dass das Problem mit der Arraygröße dann nicht mehr ist. 
Denn es gibt auch Kommandos wo ~60 Zeichen empfangen werden. Wenn dann 
plötzlich nach 35 Einträge der Puffer wieder bei 0 beginnt gehen mir 
Daten verloren, so wie es jetzt passieren könnte. Das ist das ja was ich 
nciht verstehe. Eigentlich kann ich jetzt bis zu 200 Zeichen empfangen 
bis der puffer wieder überläuft.

Am einfachsten und am schnellsten wäre ein Funktion, die mir beim 
zyklischen aufrufen in der Main sagt ob ein Zeichen empfangen wurde oder 
nicht und wenn eins empfangen wurde, welches empfangen wurde. Dann 
könnte ich schauen welchen ASCII wert dieses Zeichen hat, z.B. "Q" und 
springe mit "if (empfangenesZeichen == 81)" (Q in ASCII = 81) in eine 
unterfunktion. Da ich dann weis welches Kommando an kam (hier das "Q") 
weis ich auch wieviel Zeichen folgen und könnte dann mit einer 
for-schleife die restlichen Zeichen abholen, welche im puffer liegen. 
Nach dem auslesen der ZEichen prüfe ich ob das letzte Zeichen ein ";" 
ist, wenn ja, gut, wenn icht, schicke ein Fehler.


Gruß
Thomas D.

von m.n. (Gast)


Angehängte Dateien:

Lesenswert?

Deine int USART_get_Byte(void) ist auch etwas merkwürdig.
Ich gebe Dir mal ein Schnipsel für UART-Pufferung. Es muß noch angepaßt 
werden!

von Karl H. (kbuchegg)


Lesenswert?

> Ich hoff nur, dass das Problem mit der Arraygröße dann nicht mehr
> ist. Denn es gibt auch Kommandos wo ~60 Zeichen empfangen werden.

Dann muss dein Buffer, in dem die ISR die empfangenen Zeichen 
zwischenlagert eben so groß sein. 60 Bytes sind jetzt nichts, was einen 
groß umbringt.


> Am einfachsten und am schnellsten wäre ein Funktion, die mir
> beim zyklischen aufrufen in der Main sagt ob ein Zeichen
> empfangen wurde oder nicht und wenn eins empfangen wurde,
> welches empfangen wurde.

Das klingt zwar logisch.
Aber am einfachsten sind die Dinge tatsächlich, wenn die ISR sich schon 
darum kümmert, aus den eingehenden Zeichen komplette 'Datensätze' zu 
machen und die entsprechend zu gruppieren.


> Da ich dann weis welches Kommando an kam (hier das "Q")
> weis ich auch wieviel Zeichen folgen und könnte dann mit
> einer for-schleife die restlichen Zeichen abholen, welche
> im puffer liegen. Nach dem auslesen der ZEichen prüfe ich
> ob das letzte Zeichen ein ";" ist, wenn ja, gut, wenn icht,
> schicke ein Fehler.

Denn siehst du, genau da brichst du dir jetzt das Genick.
In der Hauptschleife müsstest du jeweils 1 Zeichen holen. Das prüfst du, 
ob es ein ';' ist und wenn ja, beginnst du mit der Auswertung des 
Kommandos. Wenn nicht (kein ';') dann wird es in einen Buffer angehängt, 
in dem Zeichen für Zeichen nacheinander das Kommando entsteht, solange 
bis dann irgendwann der ';' kommt. Mit deiner 'for-Schleife' und 'wir 
wissen das' Methode holst du dir nur Fehler ins System.

> ob das letzte Zeichen ein ";" ist, wenn ja, gut, wenn icht,
> schicke ein Fehler.

Dein Mega32 ist schon mit der neuen 'Ich kann in die Zukunft sehen - 
Glaskugel' ausgerüstet? Oder wie weiß der, dass der Sender schon alles 
weggeschickt hat und das ';' schon angekommen sein müsste?





Und das Sammeln von Zeichen bis zum ';', das kann die ISR genausogut 
machen. Deine main() muss nur überprüfen, ob ein vollständiges Kommando 
vorhanden ist oder nicht und wenn eines da ist, dann wird es ausgewertet 
und die Antwort generiert.

von Hack K. (hackerfleisch)


Lesenswert?

Hi!
@m.n.
Klasse, danke für die Datei! Ich werde versuchen mir die spätestens am 
WE an zu schauen!

@Buchegger
Zu deinem ersten quote:
Mein Puffer ist momentan 200 Bytes groß. Das ist ja auch nicht das 
Problem.
Es geht mir um das von mir ganz am Anfang beschriebenes Problem mit der 
Test-Ausgabe über LEDs (incrementiert dauerhaft den readidx, warum auch 
immer, d.h. er wird nicht auf 0 gesetzt wenn er größer 200 ist).

Quote 2:
Jupp, wird wohl das beste sein, dachte halt, das ich die ISR so kurz wie 
möglich gestalte, ohne viel mit Bits und Bytes zu arbeiten.

Quote 3:
Dies hatte zwar funktioniert, hat mir aber nicht gefallen. Ich habe dann 
immer so viel "if" anweisungen wie ich kommandos habe, wenn der richtige 
Kommandobuchstabe kommt, springt er in die richtige If anweisung rein 
und holt sich mit USART_get_Byte das nächste Zeichen usw.

Quote 4:
Ich weis durch die Protokollbeschreibung wieviel Zeichen für welches 
Kommando kommen müssen. Wenn z.B. "Q" kommt, weis ich das z.B. 8 Zeichen 
folgen und dann der delimiter (";"). Wie einen Absatz weiter oben 
geschrieben, wird geprüft welches Zeichen an kommt (mit IF anweisung), 
dann geht er in die Anweisung und in eine for schleife, diese macht so 
viel Runden wie ich Zeichen empfangen muss (die sind bei jedem Befehl 
bekannt), danach prüfe ich ob ein ";" kommt, wenn ja dann hat alles 
gepasst.
IC hoff ich konnte es richtig schreiben wie ich das meine.

Jedoch frage ich mich warum der µC ab und an nicht bis 199 zählt (Puffer 
von 0- 199), sondern schon früher den readidx &writeidx auf 0 setzt oder 
nicht mehr aufhört zu incrementieren, ist doch eigentlich wasserdicht:

If Anweisung in USART_get_Byte:
1
 if (read_idx < 200)
2
    {
3
      Byte = puffer[read_idx]; //Read out current Byte
4
      read_idx++; //increment the read-index to next array position.
5
    }
6
    else
7
    {
8
      read_idx = 0;
9
    }
und If Anweisung in der ISR:
1
if (write_idx < 200)
2
  {
3
    puffer[write_idx] = UDR;
4
    write_idx++;
5
  }
6
  else
7
  {
8
    write_idx = 0;
9
  }

Gruß
Thomas D.

von Magnus M. (magnetus) Benutzerseite


Lesenswert?

Thomas D. schrieb:
> Hier der Interrupt welche beim Empfangen eines Zeichen ausgelößt wird:
>
1
> ISR(USART_RXC_vect)
2
> {
3
>   if (write_idx < 200)
4
>   {
5
>     puffer[write_idx] = UDR;
6
>     write_idx++;
7
>   }
8
>   else
9
>   {
10
>     write_idx = 0;
11
>   }
12
> }
13
>

Wenn du "puffer[write_idx] = UDR;" in die IF-Abfrage rein nimmst, wird 
UDR beim Überlauf des Schreibindexes nicht in das Array übertragen!

Schreib es besser so:
1
ISR(USART_RXC_vect)
2
{
3
  if (write_idx < 200)
4
  {
5
    write_idx++;
6
  }
7
  else
8
  {
9
    write_idx = 0;
10
  }
11
12
  puffer[write_idx] = UDR;
13
}

von Magnus M. (magnetus) Benutzerseite


Lesenswert?

Nachtrag:

Selbiges gilt übrigens auch für die Lesefunktion.

von xfr (Gast)


Lesenswert?

1
int USART_get_Byte(void)
2
{
3
  DelayWaitTimeSerial(); //Description is in this Function
4
  if (read_idx != write_idx) //If something received in array "puffer"
5
  {
6
    uint8_t Byte = 0;
7
    if (read_idx < 200)
8
    {
9
      Byte = puffer[read_idx]; //Read out current Byte
10
      read_idx++; //increment the read-index to next array position.
11
    }
12
    else
13
    {
14
      read_idx = 0;
15
    }
16
    return Byte;
17
  }
18
  return 255;
19
}

Was mir als erstes auffällt ist, dass Du nicht unterscheiden kannst, ob 
Du den Wert 255 per UART empfangen hast oder kein Zeichen empfangen 
wurde. Nimm als Rückgabewert bei keinem Zeichen also lieber -1 (oder EOF 
= End of File aus stdio.h, ist auch als -1 definiert).

Als zweites: Wenn read_idx == 199, wird er auf 200 erhöht. Im nächsten 
Durchlauf wird er auf 0 gesetzt, aber kein Wert gelesen, sondern 0 
zurückgegeben. Erst im dritten Durchgang wird dann der richtige Wert 
gelesen.

Und als drittes: Die Funktion DelayWaitTimeSerial() ist überflüssig. 
Wenn noch kein neues Zeichen empfangen wurde, machst Du eben nichts:
1
int USART_get_Byte(void)
2
{
3
  if (read_idx != write_idx) //If something received in array "puffer"
4
  {
5
    uint8_t Byte = puffer[read_idx]; //Read out current Byte
6
    if (read_idx < 200)
7
    {
8
      read_idx++; //increment the read-index to next array position.
9
    }
10
    else
11
    {
12
      read_idx = 0;
13
    }
14
    return Byte;
15
  }
16
  return -1;
17
}

[c]int main(void) {
[...]
while (1) {
  int zeichen = USART_get_Byte(void);
  if (zeichen != -1) {
    // lese Zeichen
  }
  [...]
}

Ansonsten musst Du aufpassen, dass Deine Indizes nur uint8_t sein 
dürfen. Wenn write_idx 16 Bit breit ist, musst Du den Zugriff darauf 
atomar machen.

von Hack K. (hackerfleisch)


Lesenswert?

Hi!

@Magnus M.
Hey Stimmt, werde ich sofort ändern! Danke!

@xfr
Ich bekomme nie einen Wert der 200 ist (0xFF). Ich bekomme HEX Werte pro 
Byte geschickt. D.h. in dezimal ist jedes empfangene Byte max "15". Also 
ich empfange maximal von "0" bis "15" in dezimal welche in das 8Bit 
breite array geschrieben werden. D.h. es ist noch massig platz um einen 
rückgabewert zu definieren der mir sagt, dass nix empfangen wurde. 
Anfangs hatte ich als Rückgabewert "0" wenn nix gekommen ist, da war die 
Übertragung vom PC aber auch komplett in ASCII. Das geht jetzt nicht 
mehr da ich keine ASCII Werte mehr, sondern "HEX" Werte vom PC bekomme, 
welche jeweils in einem Byte übertragen werden. Später könnte man zwei 
Werte pro Byte übertragen, dann passt das mit der 255 natürlich nicht 
mehr. Aber so ist das nioht angedacht, obwohl ich dadurch nur halb so 
viel Datenstrom über die RS232 Schnittstelle hätte da ich ja dann gleich 
zwei statt nur einen Wert pro Byte übertrag (vom PC zum µC).

Nein, die indizes sind nur uint8_t, also keine 16 Bit, daher brauche ich 
nicht an eine Atomare variante denken, aber trozdem danke fürs mit 
denken!

Zu deinem Einwand, dass der writeidx bei 199 auf 200 erhöht wird stimmt 
natürlich auch! Vielen Dank auch an Dich!

So jetzt werde ich erst mal probieren. Melde mich spätestens nächste 
Woche ob alles funzt oder nicht.


@m.n.
natürlich schau ich mir noch Deine file mit an!


Grüße
Thomas D.

von Hack K. (hackerfleisch)


Lesenswert?

Nachtrag:

@xfr
Da ich momentan in der Main, polling betrieb mit den einzelnen Zeichen 
habe (ob ein empfangen wurde, wenn ja, welches) könnte es passieren , 
dass ich ein Kommando empfange, dann in die If Anweisung springe und 
dann die nächsten Zeichen abholen muss, da ich aber nich weis wie 
schnell die While schleife in der Main ist (also wie offt diese in einer 
Sekunde durchläuft [ist ja auch je nach Befehl schneller oder kurzer, je 
nach dem was der µC zu tun hat]), kann es passieren, dass er das erste 
Steuerzeichen wie z.B. "Q" empfangen und erkannt hat, dann die nächsten 
Zeichen vom Puffer holen will, da aber ncih nix drin ist, weil die 
Übertragung im gegensatz zum µC (16MHz) mit 19200baud recht langsam ist 
(19200/10Bit Wortlänge = 1920 Zeichen pro Sekunde).
Daher warte ich je nach eingestellter Bautrate min. die Zeit von einem 
Zeichen + 20% bis ich zurück gebe ob ein Zeichen empfangen wurde oder 
nicht.
Nachteil: Ich kann max~ 1000 Zeichen am stück empfangen da mir sonst 
mein puffer überläuft weil ich ja bei jedem empfangenen Zeichen etwas 
länger warte wie das neue Zeichen braucht um übertragen und abgearbeitet 
zu werden -> alle 5 empfange Zeichen liegt ein Zeichen mehr im Puffer (5 
* 20% = 100% -> ein Zeichen).

Somit ist aber sichergestellt, dass, wenn der ein Steuerzeichen erkannt 
hat (If abfrage) und dann in eine for-schleife geht um die nächsten 
folgezeichen zu holen, immer eins da ist und nicht in einen puffer 
unterlauf rennt weil er die daten schneller abarbeitet wie diese rein 
kommen.

Gruß
Thomas D.

von xfr (Gast)


Lesenswert?

Thomas D. schrieb:
> Somit ist aber sichergestellt, dass, wenn der ein Steuerzeichen erkannt
> hat (If abfrage) und dann in eine for-schleife geht um die nächsten
> folgezeichen zu holen, immer eins da ist und nicht in einen puffer
> unterlauf rennt weil er die daten schneller abarbeitet wie diese rein
> kommen.

Nö, das ist nicht sichergestellt. Wer sagt denn, dass die Gegenstelle 
(PC) die Zeichen so schnell hintereinander sendet? Wenn Du die von Hand 
ins Terminal eintippst, wird das sehr viel länger dauern ...

Der Ansatz, dass Du USART_get_Byte() so oft hintereinander aufrufen 
kannst, wie Du Zeichen erwartest und immer eins da ist, ist grundfalsch. 
Du musst in der Hauptschleife immer erst nachfragen, ob ein neues 
Zeichen angekommen ist. Nur wenn das der Fall ist, wertest Du das 
Zeichen aus. Zwischendurch darf USART_get_Byte() beliebig oft 
zurückgeben, dass noch kein neues Zeichen da ist. Anders geht das 
garantiert in die Hose.

Wenn Du die Zeichen alle hintereinander verarbeiten willst, musst Du sie 
vorher puffern und erst mit der Verarbeitung anfangen, wenn alle da 
sind. Das haben Dir im Prinzip ja auch die anderen vorgeschlagen.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.