Forum: Mikrocontroller und Digitale Elektronik Peter Flury´s UART Routinen


von Jan H. (janiiix3)


Lesenswert?

Guten Morgen,

ich benutze die Routinen von der "UART Lib." vom Peter Flury 
(http://homepage.hispeed.ch/peterfleury/avr-software.html)

Diese werden ja ziemlich oft genutzt.
Nun hat er dort einen RingBuffer implementiert.
1
static volatile unsigned char UART_TxBuf[UART_TX_BUFFER_SIZE];
2
static volatile unsigned char UART_RxBuf[UART_RX_BUFFER_SIZE];
3
static volatile unsigned char UART_TxHead;
4
static volatile unsigned char UART_TxTail;
5
static volatile unsigned char UART_RxHead;
6
static volatile unsigned char UART_RxTail;
7
static volatile unsigned char UART_LastRxError;

Das sind die ganzen Variablen die genutzt werden um diesen zu 
Realisieren.

Jedes empfangenes Zeichen kann ich mit der Funktion "uart_getc();" 
auslesen.
Der Index muss ja jedes mal neu berechnet werden.
Aktuell kopiere ich die ganzen Empfangengen Zeichen so..
1
     for(uint8_t x = 0 ; x < UART_RX_BUFFER_SIZE ; x++)
2
     {
3
       tmpBuff[x] = uart_getc();
4
     }
Das dass sch**** ist, weiß ich selber.
Gibt es die Möglichkeit auf "UART_RxBuf[UART_RX_BUFFER_SIZE]" zu zu 
greifen?

Vielen Dank.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

die for Schleife ist der falsche Ansatz. Sobald ein Zeichen zum abholen 
bereit liegt, holste das per uart_getc und schiebst das in deinen index 
basierten tmpBuff. Ohne for, wenn man mit if.

von Jan H. (janiiix3)


Lesenswert?

Es muss doch eine Möglichkeit geben, auf den Hauptpuffer direkt drauf zu 
zu greifen oder?

Das Ding ist, ich suche den ganzen Buffer nach einem String ab.

: Bearbeitet durch User
von Jan H. (janiiix3)


Lesenswert?

Wie sollte die "if" denn aussehen?

von Falk B. (falk)


Lesenswert?

@Jan H. (janiiix3)

>Es muss doch eine Möglichkeit geben, auf den Hauptpuffer direkt drauf zu
>zu greifen oder?

Sicher.

Die häßliche Version greift direkt auf die Variable bzw. das Array zu. 
Die schöne schreibt eine passende Funktion innerhalb der Bibliothek.

>Das Ding ist, ich suche den ganzen Buffer nach einem String ab.

Das geht so einfach nicht, denn der Puffer ist ein logischer Ringpuffer. 
D.h. dein String kann irgendwo in der Mitte des Arrays anfangen, über 
das Ende hinausgehen und dann wieder am Anfang weiter gehen. Alle 
normalen Stringfunktion kommen damit NICHT klar. Du mußt wohl oder übel 
einen Teil des FIFOs in ein normales Array kopieren und dann darin 
suchen. Das kann man natürlich performanter machen, als für jedes 
Zeichen die Funktion uart_getc() aufzurufen. Wie das geht sieht man 
hier.

https://www.mikrocontroller.net/articles/FIFO#FIFO_als_Bibliothek

von Johnny B. (johnnyb)


Lesenswert?

Also wenns sehr Resourcenschonend sein muss, dann musst Du halt 
massgeschneidert für Deine Aufgabe etwas entwickeln, welches die 
empfangenen Zeichen gleich beim Empfang verarbeitet. Dies geht recht gut 
mit einer kleinen State-Machine. Je nach dem sogar direkt im 
Empfangsinterrupt die gewünschten Zeichen einlesen und die anderen 
gleich verwerfen.

von Jan H. (janiiix3)


Lesenswert?

Falk B. schrieb:
> @Jan H. (janiiix3)
>
>>Es muss doch eine Möglichkeit geben, auf den Hauptpuffer direkt drauf zu
>>zu greifen oder?
>
> Sicher.
>
> Die häßliche Version greift direkt auf die Variable bzw. das Array zu.
> Die schöne schreibt eine passende Funktion innerhalb der Bibliothek.


Wie würde das aussehen??

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Falk B. schrieb:
> Die häßliche Version greift direkt auf die Variable bzw. das Array zu.

Dazu muss "nur" das Schlüsselwort static entfernt und eine 
korrespondierende /extern/-Deklaration in die zugehörige Headerdatei 
eingefügt werden.

Das aber macht den Pfusch nicht besser.

Wenn man ein Protokoll wie z.B. das AT-Protokoll von 
Modems/BT-Modulen/ESP8266 etc. interpretieren will, ist es sinnvoll, die 
logische Struktur zur Hilfe zu nehmen - so ein Protokoll enthält oft 
etwas, womit man "Telegramme", d.h. komplette Protokollblöcke 
voneinander separieren kann.

Im Falle des AT-Protokolls ist's CR bzw. LF, da das Protokoll 
zeilenorientiert ist.

Und damit holt man sich mit getc() solange Zeichen und trägt sie in 
einen Zeilenpuffer ein, bis ein Zeilenendezeichen (CR bzw. LF) 
auftritt bzw. eine vereinbarte maximale Zeilenlänge überschritten wird.

Diesen Puffer kann man dann nach irgendwelchen Strings durchsuchen.

Und das ganze macht man schön separat vom Interrupthandler und dessen 
Empfangsfifo, weil ja durchaus weiterhin Daten eintrudeln könnten.

Ein Gefummel direkt am Empfangsfifo würde das Konzept des 
Interrupthandlers irgendwie ziemlich ad absurdum führen.

von Jan H. (janiiix3)


Lesenswert?

Wieso kann ich durch das Schlüsselwort "static" nicht den Inhalt 
auslesen?

von Jan H. (janiiix3)


Lesenswert?

Rufus Τ. F. schrieb:
> Und damit holt man sich mit getc() solange Zeichen und trägt sie in
> einen Zeilenpuffer ein, bis ein Zeilenendezeichen (CR bzw. LF)
> auftritt bzw. eine vereinbarte maximale Zeilenlänge überschritten wird.
>
> Diesen Puffer kann man dann nach irgendwelchen Strings durchsuchen.
>
> Und das ganze macht man schön separat vom Interrupthandler und dessen
> Empfangsfifo, weil ja durchaus weiterhin Daten eintrudeln könnten.
>
> Ein Gefummel direkt am Empfangsfifo würde das Konzept des
> Interrupthandlers irgendwie ziemlich ad absurdum führen.

Du meinst so?
1
    char tmp = uart_getc();
2
    uint8_t cnt = 0x00;
3
    
4
    if(tmp)
5
    {
6
      while(tmp != '\r' || tmp != '\n')
7
      {
8
        tmp = uart_getc();
9
        tmpBuff[cnt++] = tmp;
10
      }
11
    }

von Falk B. (falk)


Lesenswert?

@Jan H. (janiiix3)

>Wieso kann ich durch das Schlüsselwort "static" nicht den Inhalt
>auslesen?

Schon mal ein C-Buch konsultiert?

Wenn globale Variablen als static deklariert/definiert werden, sind sie 
nur in der jeweiligen Datei sichtbar, nicht außerhalb.

von Jan H. (janiiix3)


Lesenswert?

Falk B. schrieb:
> @Jan H. (janiiix3)
>
>>Wieso kann ich durch das Schlüsselwort "static" nicht den Inhalt
>>auslesen?
>
> Schon mal ein C-Buch konsultiert?
>
> Wenn globale Variablen als static deklariert/definiert werden, sind sie
> nur in der jeweiligen Datei sichtbar, nicht außerhalb.

Ja schon. Ich dachte static sorgt dafür das die Variable immer den 
gleichen Speicherbereich hat?

von Nop (Gast)


Lesenswert?

Jan H. schrieb:

> Ja schon. Ich dachte static sorgt dafür das die Variable immer den
> gleichen Speicherbereich hat?

C-Buch also nicht gelesen. Nachholen.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

die Lib bringt ein Bsp. mit, schau dir das mal genau an.

Du brauchst einen Buffer nochmal extra womit du vom Ringbuffer in den 
eigenen Buffer einliest. Direkten Zugriff auf den Ringbuffer hast du 
nicht und brauchst du auch nicht.

Ich habe meinen Code mal stark eingekürzt, weil da Sachen drin sind die 
du nicht brauchst oder noch nicht brauchst, das Prinzip muss erstmal 
verstanden werden. Dauert eben immer eine Weile. Ist normal. Ich rufe 
aus dem Hauptprogramm immer nur handle_Serial() auf. Sobald die 
Übertragung komplett empfangen wurde kommt ein true zurück und die 
eigentliche Auswertung kann in dieser Funktion starten. Ohne 
Fehlerbehandlung reicht auch ein Byte für c zum einlesen. Muss erstmal 
kein int sein.

Die Byte kommen automatisch per USART Interrupt Nutzung in den 
Ringbuffer.
getc fragt den nur ab ob ein neues Byte da ist, wenn ja kopierst du dir 
das in deinen Buffer. Wenn die Übertragung komplett ist, kannst du 
deinen Buffer behandeln. Meinetwegen auf Strings durchsuchen wie du 
möchtest. Das machst du aber alles nur mit deinem Buffer. Der 
eigentliche Ringbuffer bleibt verborgen. Defaultmäßig hat der Platz für 
32 Bytes wenn mich nicht alles täuscht. Jeweils für senden und 
empfangen.

Das enthaltene Bsp. der Lib musst du auch anschauen, weil das 
funktioniert so wie es ist.


wie gesagt der Codeauszug kompiliert jetzt nicht, nur zur 
Prinzipdarstellung meinerseits. Durch die if Abfrage(n) blockiert der 
Code nicht und macht nur etwas wenn es wirklich etwas zu tun gibt. In 
der Lib sind jedenfalls paar Gimmicks eingebaut, die ist wirklich gut.
1
bool read_Ringbuffer()
2
{     
3
  static uint8_t index = 0;
4
  uint8_t length = sizeof(Nachricht)-1;
5
  
6
  uint16_t c = uart0_getc();  // nächstes Zeichen vom Ringbuffer holen
7
    
8
  if ( c & UART_NO_DATA) {  
9
    return false;            
10
  }
11
  
12
  if ( c > UART_NO_DATA) {       // irgendein UART Error
13
    index = 0;
14
    return false;  // es gibt nichts zum lesen
15
  }
16
  
17
// ------------------------------------------------------------------
18
  c &= 0xFF;    // High Byte nullen vor Weiterverarbeitung
19
    
20
  if (index < length) {  // Bytes einsortieren
21
    empfDaten[index++] = (uint8_t)c;
22
  }
23
  
24
  if (index >= length ) {    // Übertragungsende
25
    index = 0;
26
    return true;            
27
  }    
28
  return false;  // noch nicht fertig oder irgendwas ging schief
29
}
30
31
32
void handle_Serial()
33
{  
34
  if( read_Ringbuffer() == true)  {
35
      
36
    if (empfDaten == abc)  {
37
      ...
38
      ... mach was sinnvolles
39
      ...
40
    }
41
    else if (empfDaten == xyz)  {
42
      ...
43
      ... mach was sinnvolles
44
      ...
45
    }
46
    
47
  }
48
}

: Bearbeitet durch User
von Jan H. (janiiix3)


Lesenswert?

Veit D. schrieb:
> uint8_t length = sizeof(Nachricht)-1;

Moin.
Vielen Dank, dass du dir die Zeit nimmst.
Was jedoch ist "Nachricht"? soll das etwa die 32 Bits vom Buffer sein?
Oder soll da die Länge, von dem drin stehen was ich erwarte? Also die 
Länge meiner unterschiedlich langen Strings?

von c-hater (Gast)


Lesenswert?

Rufus Τ. F. schrieb:

> Ein Gefummel direkt am Empfangsfifo würde das Konzept des
> Interrupthandlers irgendwie ziemlich ad absurdum führen.

Nein, nicht, wenn es richtig implementiert ist. Der Bereich zwischen 
outptr und inptr gehört ja schliesslich dem Fifo-Reader, ihn anders zu 
nutzen, als immer nur einzelne Chars in der Reihenfolge ihres Eingangs 
daraus zu entnehmen, ändert am Konzept des Interrupthandlers absolut 
garnix.

Es ändert allerdings etwas am Fifo-Konzept, es ist dann eben kein Fifo, 
sondern nur noch ein allgemeiner Ringpuffer. Mit den entsprechenden 
Zugriffsfunktionen ist es völlig legitim, einen wahlfreien Zugriff auf 
dessen Pufferinhalt zu realisieren.

Das kann sehr gut für die Performance sein (das hängt von der Struktur 
und der Nutzung der übermittelten Daten ab), spart aber leider keinen 
Speicher, wie man vielleicht annehmen könnte, weil ja ein zusätzlicher 
Buffer auf der Empfängerseite entfällt.

Nö, um genau die Größe des dort eingesparten Buffers muss nämlich der 
Ringpuffer größer werden, um weiterhin im gleichen Umfang die Rolle 
spielen zu können, für die er gedacht war.

von Falk B. (falk)


Lesenswert?

In einem Projekt hab ich auch ein FIFO im UART drin. Die 
Empfangs-Interruptroutone empfängt die Zeichen und schreibt sie in den 
FIFO. Gleichzeitig prüft sie, ob ein Stringendezeichen empfangen wurde 
('\r'). Das wird durch '\0' ersetzt und in den FIFO geschrieben. 
Gleichzeitig wird ein Zähler (globale, volatile Variable) um 1 erhöht. 
Damit weiß das Hauptprogramm immer, wieviele, vollständige Strings im 
FIFO liegen und ausgelesen werden können. Das kann eine normale Funktion 
machen. Wenn es etwas performanter sein soll, packt man die in die 
FLeury-Lib und greift direkt und intelligent auf das FIFO-Array zu. Das 
ist dann eine saubere Lösung.

von Veit D. (devil-elec)


Lesenswert?

Hallo,
1
uint8_t length = sizeof(Nachricht)-1;

Nachricht ist der Name meiner Datenstruktur zum senden und empfangen.
Hier kannst du den Namen deines Empfangsarrays eintragen. Also deinen 
Buffer.

von Pandur S. (jetztnicht)


Lesenswert?

Kloppt blockierende Kommunikationsroutinen in die Tonne. Sowas ist kaum 
zu debuggen.

Falls man auf etwas wartet, sollte man eher mit einer Zustandsmaschine 
Arbeiten und sich repetitives durchsuchen und kopieren sparen.

von Jan H. (janiiix3)


Lesenswert?

Veit D. schrieb:
> Hallo,
>
>
1
> uint8_t length = sizeof(Nachricht)-1;
2
>
>
> Nachricht ist der Name meiner Datenstruktur zum senden und empfangen.
> Hier kannst du den Namen deines Empfangsarrays eintragen. Also deinen
> Buffer.

Das heißt, du erwartest so viele bytes wie die Struktur groß ist? Dann 
hast du immer eine feste Breite an Kommandos oder etc.?

von Veit D. (devil-elec)


Lesenswert?

Hallo,

@ Falk:
die uart Lib vom Peter schreibt auch den Null-Terminator in den 
Ringbuffer, sodass man beim auslesen bei einem String auch nur bis zum 
nächsten '\0' liest. Wollte das nur einmal nebenbei anmerken.

@ Jan:
muss wohl doch weiter ausholen. Seinen Buffer bzw. bei mir die 
Datenstruktur, legt man nur so groß an wie man benötigt + 1 Byte 
Reserve. Mach ich jedenfalls so. Man muss natürlich ein klares 
Übertragungsprotokoll für sich vorher festlegen. Ob das nur mit fester 
Länge ist, also die Anzahl der übertragenen Bytes, oder mit klarer Start 
und Endekennung oder beides zusammen ist erstmal egal. Nur muss man 
irgendwas haben zur Wiedererkennung. Sonst liest man ja nur wild mit und 
weiß gar nicht was man eigentlich gerade eingelesen hat. Wie beim 
Autorennen. Ohne Startnummern weiß niemand wer gerade führt, also wer 
die nächste Runde eingeleutet hat und wer dann damit als erster über die 
Linie fährt. Ohne Nummern würden nur irgendwelche Autos ihre Runden 
drehen. Der Zuschauer und die Rennleitung wüßte nicht wer wer ist. 
Genauso ginge es dir wenn du einfach nur einleist was gerade reinkommt.

Zurück zum Buffer und der Überprüfung der Bufferlänge. Das dient mit zum 
Überlaufschutz. Damit nicht über den Buffer hinaus ins Nirwana, 
geschrieben wird. Ansonsten landen die Daten unkontrolliert irgendwo im 
SRAM was unvorhersehbare Folgen haben kann. Bufferüberlauf eben. Davon 
liest man häufig wenn Sicherheitslücken von Programmen ausgenutzt wurden 
in der PC Netzwerkwelt u.ä.

Auf meinem µC habe ich eine klare Start und Endekennung die ich 
mitschicke, damit ich weiß wann was neues kommt und wann es zu Ende ist. 
Im Normfall sollte das ausreichend sein. Wenn aber bei der Übertragung 
was schief geht, kann ja sein durch Störungen oder was weiß ich, dann 
müssen Fangnetze herhalten. Wie eben zum Bsp. die Längenüberprüfung.

Nochmal zum Unterschied. Ich lese also nicht solange ein bis mein Buffer 
voll ist, sondern ich lese zwischen Start und Endekennung ein. Das der 
Buffer nur so groß ist wie maximal benötigt sollte klar sein, alles 
andere wäre Speicherverschwendung.

Wenn du nur Strings übertragen möchtest, dann reicht ein NL oder CR 
erstmal aus für den Anfang. Dann weiß dein Einlesecode das Ende ist 
erreicht und kann auswerten. Jedes Byte danach wäre dann wieder der 
Anfang einer neuen Übertragung.

Der Ringbuffer hat default übrigens nicht 32 Bit sondern 32 Byte.  :-)

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.