Forum: Mikrocontroller und Digitale Elektronik STM32 UART DMA Circular Vertändnisproblem


von Heinz M. (subi)


Lesenswert?

Hallo,

ich verwende schon sehr lange DMA mit I2C und UART_TX. Jetzt wollte ich 
in Angriff nehmen auch Befehle vom PC zu verarbeiten und zwischen 
Mikrocontrollern Befehle zu senden.

Dass UART Receive bei der HAL schlecht umgesetzt ist, habe ich schon 
gelesen. Im Prinzip sind alle Möglichkeiten
- IdleLine
- Timeout
- Start/Stop Zeichen Erkennung
- Interruptmodus
gar nicht oder so schlecht umgesetzt, dass es nicht benutzbar ist.

Ich wollte es so machen, wie von ST gedacht, dass man nach der halben 
Übertragung sich die erste Hälfte der Übertragung anschaut und nach der 
vollen Übertragung die zweite Hälfte, während die nächste erste Hälfte 
schon wieder empfangen wird. Das wäre auch nicht das Problem.

Mein Problem besteht darin, dass HAL_UART_Receive_DMA eine Anzahl an zu 
empfangenden Bytes verlangt. Das heißt es müsste jedes Mal am Ende der 
Übertragung erst eine neue gestartet werden. Direkt in der dafür 
gedachten Funktion geht das nicht:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  flagUartEingabe = 7; //Daten auswerten
  HAL_UART_Receive_DMA(&huart2,(uint8_t*)UartBufferEingabe,40);
}

Damit müsste man HAL_UART_Receive_DMA in der mainloop neu aufrufen. Ich 
habe zwar meine mainloop auf möglichst kurze Durchläufe für 
zeitkritische Operationen optimiert, aber ist damit die Gefahr nicht 
viel zu hoch ein Zeichen zu verpassen?

Gibt es keinen Weg UART DMA in einem echten Circular free Running Modus 
zu betreiben, wie es beim ADC der Fall ist?

Geflame über HAL und CubeMX könnt Ihr euch sparen. Ich betreibe das als 
Hobby und habe weder die Zeit, das Wissen, noch die Ausbildung soetwas 
wie die HAL nachzubauen.

von Harry L. (mysth)


Angehängte Dateien:

Lesenswert?

DMA für asynchrone Übertragungen ist selten sinnvoll.

Der Interruptmode funktioniert ganz augezeichnet.(auch mit HAL)
Was sollte daran schlecht umgesetzt sein?

Funktionierender Beispiel-Code im Anhang.

von Heinz M. (subi)


Lesenswert?

Warum soll DMA für asynchrone Übertragung nicht sinnvoll sein? Ob 
asynchron oder nicht ist doch nur für die UART entscheidend. Der DMA 
nimmt die Daten die er bekommt. DMA und asynchrone UART funktioniert 
bereits bei mir sehr gut.

Nur wo das Ende der Nachricht ist, also theoretisch hinter UART und DMA. 
Da happerts, weil die nötige Info von DMA bzw. UART fehlt, dass keine 
Daten mehr kommen. Oder eben ein frei laufender Modus, den man nicht 
ständig anstoßen muss.

HAL_UART_Receive_IT hing bei mir immer und im Internet habe ich 
gefunden, dass dies ein Bug der HAL sei. Vielleicht wurde es inzwischen 
ja doch mal gefixt. Da diese Funktion ebenfalls eine Anzahl an Zeichen 
voraussetzt, wäre es das gleiche wie DMA.

von Harry L. (mysth)


Lesenswert?

Heinz M. schrieb:
> Nur wo das Ende der Nachricht ist, also theoretisch hinter UART und DMA.
> Da happerts, weil die nötige Info von DMA bzw. UART fehlt, dass keine
> Daten mehr kommen.

Genau deshalb ist DMA hier nicht sinnvoll.

Heinz M. schrieb:
> HAL_UART_Receive_IT hing bei mir immer und im Internet habe ich
> gefunden, dass dies ein Bug der HAL sei.

Dann hast du offensichtlich was falsch gemacht.

Mein Code von Oben läuft unverändert von F0 bis F7 stabil und 
problemlos.

von Heinz M. (subi)


Lesenswert?

Ich habe gerade ein anderes Problem.

Er springt nicht mehr in die
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

von Heinz M. (subi)


Lesenswert?

Also ich finde einfach nicht, warum er das plötzlich nicht mehr machen 
will. Ich hab schon ein neues Projekt in CubeMX erzeugt und den von mir 
geschriebenen Code neu rein kopiert. Wenn ich das auskommentierte // 
flagUartEingabe = 7; rein mache, dann empfängt er und geht auch durch 
die HAL_UART_RxCpltCallback Funktion. Wenn ich es rausnehme, dann macht 
er es nicht mehr. Bzw. völlig unvorhersehbar beim Reset sporadisch. 
Global Interrupt für UART2 ist eingeschalten.

Unter /* USER CODE BEGIN 0 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart2)
{
  flagUartEingabe = 7;
   HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8); // LED zur Kontrolle ob sich 
was tut
}

mainloop:
if (flagUartEingabe == 8)
{
 if (uartTimeOut == 100000)// Prüfung ob Zeit abgelaufen
 {
  // HAL_UART_DMAStop(&huart2);
  // flagUartEingabe = 7;
 }
 else
  uartTimeOut++;
}

if (flagUartEingabe == 7)
{
 for (uint8_t a = 0; a <= 5; a++) //Anzahl Zeichen
 {
  UartWert1 [a]  = UartBufferEingabe [a];
 }
 flagUartEingabe = 6;
}

if (flagUartEingabe == 6)
{
 flagUartEingabe = 0;
}

if (flagUartEingabe == 0)
{
HAL_UART_Receive_DMA(&huart2,(uint8_t*)UartBufferEingabe,3);
flagUartEingabe = 8;
uartTimeOut = 0;
}

von Gerald M. (gerald_m17)


Lesenswert?

DMA kann auf jeden Fall helfen die Interruptzeiten zu reduzieren, falls 
du Pakete mit festen Längen verschickst:
https://visualgdb.com/tutorials/arm/stm32/uart/hal/

Hier siehst du auch wie der DMA mit einem Circular Buffer genutzt wird. 
Zwar ohne CubeMX, aber das muss man ja auch dort korrekt einstellen.

von Heinz M. (subi)


Lesenswert?

Die Seite kannte ich schon. Danke.

Senden ist überhaupt kein Problem. Auch mit flexibler Länge.

Feste Länge empfangen hat funktioniert, nur will ich mir das nicht 
antun, dass ich die Zeichen vor dem senden zählen muss, um auf die feste 
Länge zu kommen. Aktuell funktioniert selbst das leider nicht mehr. 
Komme Heute und morgen jedoch nicht dazu den Fehler zu suchen.

von Stefan F. (Gast)


Lesenswert?

Heinz M. schrieb:
> Im Prinzip sind alle Möglichkeiten ...
> gar nicht oder so schlecht umgesetzt, dass es nicht benutzbar ist.

Das passiert, wenn man versucht, die Hardware universell zu 
abstrahieren.

Ich habe über dein Problem auch eine ganze Weile lang gegrübelt und bin 
zu dem Schluss gekommen, dass "normale" asynchrone UART Kommunikation 
mit der HAL nur von hinten herum durch die Brust umsetzbar ist.

Der Quelltext von Harry sieht allerdings auf den ersten Blick deutlich 
kompakter und verständlicher aus, als mein damaliges gemurkse. Den muss 
ich mir bei Gelegenheit mal genauer anschauen. Danke Harry.

von W.S. (Gast)


Lesenswert?

Heinz M. schrieb:
> Warum soll DMA für asynchrone Übertragung nicht sinnvoll sein? Ob
> asynchron oder nicht ist doch nur für die UART entscheidend. Der DMA
> nimmt die Daten die er bekommt. DMA und asynchrone UART funktioniert
> bereits bei mir sehr gut.
>
> Nur wo das Ende der Nachricht ist, also theoretisch hinter UART und DMA.
> Da happerts, weil die nötige Info von DMA bzw. UART fehlt, dass keine
> Daten mehr kommen.

Tja, da hast du dir richtige Antwort dir ja bereits selber gegeben. DMA 
ist schlichtweg dumm, denn es kann nur von A nach B schaufeln und es 
kann nichts bewerten oder organisieren. Für sowas ist eine CPU vonnöten.

Bedenke mal, daß serieller Datenverkehr per UART eigentlich IMMER in 
allen Dingen asynchron ist. Nicht nur der Transfer eines Zeichens, 
sondern auch der Datehverkehr insgesamt. Man hat nie und nimmer von 
hause aus eine feste und abzählbare Blockstruktur. Die kann man nur per 
Software erzeugen und muß da obendrein auch noch daran denken, die zu 
erwartenen Übertragungsfehler auszubügeln.

Es sind ja nicht ohne Grund diverse Verfahren (Kermit, ZModem und wie 
sie alle heißen) ersonnen worden.

Deshalb ist die serielle Übertragung von hause aus als eine 
Einzelzeichen-Übertragung mit möglichem Datenverlust oder Datenfehlern 
zu betrachten. Und  genau DAS spricht vehement gegen DMA und gegen 
irgendwelche HAL-Funktionen, die eine vorgegebene Blocklänge verlangen.

W.S.

von Johannes S. (Gast)


Lesenswert?


von Paul S. (mrpaul)


Lesenswert?

Johannes S. schrieb:
> hier gibts eine gute Anleitung zu DMA + variable Länge:
> 
https://stm32f4-discovery.net/2017/07/stm32-tutorial-efficiently-receive-uart-data-using-dma/

Da bist du mir zuvorgekommen - genau auf den gleichen Artikel wollte ich 
auch gerade verweisen :D .


W.S. schrieb:
> ...DMA ist schlichtweg dumm ...
Da kommt mir direkt der Spruch von Forest Gump in den Sinn...
Die Funktion, Daten im Hintergund von der Peripherie in den Speicher zu 
kopieren ohne dabei CPU-Last zu erzeugen, würde ich nicht als dumm 
bezeichnen, man muss sie nur zu Nutzen wissen.
Mit DMA und etwas Geschick (siehe der Beitrag von ST) erhält man meiner 
Meinung nach einen recht leichtgewichtigen UART-Empfänger.

von Harry L. (mysth)


Lesenswert?

Paul S. schrieb:
> Die Funktion, Daten im Hintergund von der Peripherie in den Speicher zu
> kopieren ohne dabei CPU-Last zu erzeugen, würde ich nicht als dumm
> bezeichnen, man muss sie nur zu Nutzen wissen.
Dann rechne mir doch bitte mal vor, wie hoch die CPU-Last (anteilig in 
Prozent oder Promille) bei 115200 Baud und einem kontinuierlichen 
Datenstrom ist wenn man das per Interrupt macht.

Du wirst dich wundern, wie gering die ist.
Der ganze DMA-Firlefanz kostet ganz sicher mehr Takt-Zyklen und birgt 
erheblich mehr Fallstricke für potentielles Fehlverhalten.

Paul S. schrieb:
> Mit DMA und etwas Geschick (siehe der Beitrag von ST) erhält man meiner
> Meinung nach einen recht leichtgewichtigen UART-Empfänger.

Tja, so ist das mit "Meinungen".....für Alle ausser dir selbst ziemlich 
wertlos. (und im konkreten Fall sogar vollkommen falsch)

Aber könntest du das auch belegen, wäre es ja keine "Meinung" mehr....

von W.S. (Gast)


Lesenswert?

Paul S. schrieb:
> Da kommt mir direkt der Spruch von Forest Gump in den Sinn...
> Die Funktion, Daten im Hintergund von der Peripherie in den Speicher zu
> kopieren ohne dabei CPU-Last zu erzeugen, würde ich nicht als dumm
> bezeichnen, man muss sie nur zu Nutzen wissen.

Erstens: DMA ist tatsächlich dumm: sie kann nur Daten bewegen, aber 
nicht bewerten und nicht behandeln. Ja, DMA kann nichtmal sowas wie 
solche Funktionen bieten:
bool Char_Available()
oder
char Get_Char()
sondern einzig und allein die Daten von A nach B schaufeln. Nicht einmal 
eine Bewertung (Parity, Overflow, Formatfehler usw.) kann DMA machen. 
Dafür ist wieder mal der Lowlevel-Treiber vonnöten.

Wenn man mal die ISR für einen normalen UART anschaut, dann sieht man, 
wie kurz diese ist, selbst wenn sie beide Richtungen bedient. Das 
erzeugt eine fast verschwindend geringe CPU-Last. Da ist dann das 
Aufsetzen und Verwalten eines DMA mindestens genauso aufwendig, 
vermutlich ist das Ganze per DMA deutlich umfängliche und 
taktzeitraubender als der Lowlevel-Treiber, der ohne DMA werkelt.

Eines sehe ich jedoch: Wer für jeden Furz irgendwelche ellenlangen 
HAL-Bibliotheken benutzt oder früher die ST-Lib benutzte und für jede 
Kleinigkeit umfängliche InitStruct's fleißig befüllt hat, der braucht 
für alles noch viel viel viel mehr an CPU-Zeit und Speicherplatz.

W.S.

von Paul S. (mrpaul)


Lesenswert?

Harry L. schrieb:
> Paul S. schrieb:
>> Die Funktion, Daten im Hintergund von der Peripherie in den Speicher zu
>> kopieren ohne dabei CPU-Last zu erzeugen, würde ich nicht als dumm
>> bezeichnen, man muss sie nur zu Nutzen wissen.
> Dann rechne mir doch bitte mal vor, wie hoch die CPU-Last (anteilig in
> Prozent oder Promille) bei 115200 Baud und einem kontinuierlichen
> Datenstrom ist wenn man das per Interrupt macht.
> Du wirst dich wundern, wie gering die ist.

Ich kann es ja mal versuchen, vllt. gelingt es mir ja, meine "Meinung" 
etwas zu untermauen.

Nicht-DMA-Variante
115200 Baud erzeugen 115200 Interrupts/Sek (die Länge des Interrupts 
erstmal außen vor gelassen) -> jedes empfangene Byte wird in einem 
Interrupt in den Speicher geschrieben

Bei der Variante mit DMA:
- Speicher des DMA: 256 Byte (Annahme)
- DMA im Circular-Mode
- es werden die Interrupts für "HalfTransfer" und "FullTransfer" genutzt
bei 115200 Baud wird der DMA-Puffer 115200/256=450 Mal pro Sek. gefüllt.
Macht mit den beiden Interrupts 900 Interrupts/s
Dazu kommen noch die UART-Receiver-Idle-Interrupts, welche aber abh. 
davon sind, ob der Datenstrom ununterbrochen anliegt oder nicht. Aber 
nehmen wir mal an, ein Block sei 8 Byte lang danach kommt eine "kurze" 
Pause. Dann macht das 115200/8=14400 Blöcke pro Sek. (eig. weniger, da 
ja durch die Pause die Baudrate sinkt - aber sei es drum)
Wären in Summe 14400+900=15300 Interrupts/s

Kommen wir zu den Punkt der Länge des Interrupts:
für den Fall des DMA sind die Interrupts eigentlich nur zwei Befehle 
lang:
- Interrupt-Flag löschen
- ein anderes Bit/Flag setzen, welches einer Funktion in der 
while(1)-Schleife des Hauptprogramms mitteilt, dass es etwas auszuwerten 
gibt.
Ein "Neukonfigurieren" des DMA ist nicht notwendig, das macht er im 
Zirkularbetrieb von selbst.

für die Variante ohne DMA dürfte die ISR nicht kürzer ausfallen
- das Interruptflag muss gelöscht werden
- das Empfangsregister muss gelesen werden
- das Gelesene muss irgendwo hingeschrieben werden
- und es folgt sicherlich noch etwas Zeigerarithmetik zum Hochzählen des 
Speicherzeigers inkl. Abfangen des Bereichsüberlaufs

D.h. Vorteile der DMA-Variante:
- weniger Interrupts (in meiner Bsp-Rechnung nur 15300/115200= ca.13% im 
Vergl. zur nicht-DMA-Variante)
- die ISR ist nicht wirklich länger


> Der ganze DMA-Firlefanz kostet ganz sicher mehr Takt-Zyklen und birgt
> erheblich mehr Fallstricke für potentielles Fehlverhalten.
Hier muss man unterscheiden:
Ja, sicherlich ist es aufwändiger, DMA ordentlich zu nutzen und zu 
konfigurieren und zu erproben bzw. Fehler zu beheben. Da stimme ich zu. 
Auch entsteht mehr Quellcode, da man neben dem UART auch noch den DMA 
konfigurieren muss.
Das ist ja aber Code, der nur einmalig nach den Start des uC durchlaufen 
wird. Danach eig. nie wieder.
D.h. am "Ende" holt man das Ganze durch die selteneren ISR wieder auf 
bzw. hat "mehr" Zeit für anderes.

Aber eins muss ich an der Stelle zugeben: Ich nutze in meinen Projekten 
aktuell auch kein DMA für den Uart-Eingang :D
Aber das ist ja nicht Gegenstand dieses Themas. Der Fragensteller wollte 
ja wissen, wie man es macht - nicht ob es sinnvoll ist.

W.S. schrieb:
> Eines sehe ich jedoch: Wer für jeden Furz irgendwelche ellenlangen
> HAL-Bibliotheken benutzt oder früher die ST-Lib benutzte und für jede
> Kleinigkeit umfängliche InitStruct's fleißig befüllt hat, der braucht
> für alles noch viel viel viel mehr an CPU-Zeit und Speicherplatz.
Da stimme ich zu.
Aber zum Lernen sind die Bibliotheken schon nicht verkehrt, da es Fehler 
durch den Programmierer reduziert.

Harry L. schrieb:
> Aber könntest du das auch belegen, wäre es ja keine "Meinung" mehr....
Ich habe meine Sichtweise nun dargelegt. Wenn ich bei meiner 
"Berechnung" Fehler gemacht habe, dann bin ich für Korrekturen offen. 
Ich möchte hier ja auch gern was lernen.

von Paul S. (mrpaul)


Lesenswert?

Heinz M. schrieb:
> Nur wo das Ende der Nachricht ist, also theoretisch hinter UART und DMA.
> Da happerts, weil die nötige Info von DMA bzw. UART fehlt, dass keine
> Daten mehr kommen. Oder eben ein frei laufender Modus, den man nicht
> ständig anstoßen muss.

Dafür sollte doch das Idle-Interrupt geeignet sein!?
1
Bit 4IDLE: IDLE line detected
2
This bit is set by hardware when an Idle Line is detected. An interrupt is generated if the IDLEIE=1 in the USART_CR1 register. It is cleared by a software sequence (an read to the USART_SR register followed by a read to the USART_DR register). 0: No Idle Line is detected1: Idle Line is detectedNote:   The IDLE bit will not be set again until the RXNE bit has been set itself (i.e. a new idle line occurs).

von Harry L. (mysth)


Lesenswert?

Paul S. schrieb:
> 115200 Baud erzeugen 115200 Interrupts/Sek

Das ist schon mal falsch!
Ein Zeichen besteht (bei 8N1) aus Startbit, 8 Datenbits und einem 
Stopbit. (10 bit)

Da der Interrupt pro Zeichen und nicht pro Bit ausgelöst wird, kann er 
also max 11520 mal pro s auftreten.

Paul S. schrieb:
> - es werden die Interrupts für "HalfTransfer" und "FullTransfer" genutzt

Wurde bereits mehrfach erläutert, warum das so nicht funktioniert.
Du weist nie ob und wann das nä. Zeichen eintrudelt. (deshalb nennt man 
es asynchrone Übertragung)

Wenn du trotzdem den RXcomplete Interrupt verwendest, um nach jedem 
Zeichen der DMA auf die Finger zu schauen, kannst du das vielleicht 
umschiffen, aber wozu?
Das bringt mehr Stress und Last als ein nackter Interrupt-Betrieb.

Beitrag #6564191 wurde vom Autor gelöscht.
von Paul S. (mrpaul)


Lesenswert?

Harry L. schrieb:
> Paul S. schrieb:
>> 115200 Baud erzeugen 115200 Interrupts/Sek
>
> Das ist schon mal falsch!
> Ein Zeichen besteht (bei 8N1) aus Startbit, 8 Datenbits und einem
> Stopbit. (10 bit)

Ok, dann sind die von mir angegebenen Interrupts/s alle um Faktor 10 
kleiner!?
Also Nicht-DMA: 11520 Interrupts/s
Mit DMA: 90+1440=1530 Interrupts/s


> Paul S. schrieb:
>> - es werden die Interrupts für "HalfTransfer" und "FullTransfer" genutzt
>
> Wurde bereits mehrfach erläutert, warum das so nicht funktioniert.
> Du weist nie ob und wann das nä. Zeichen eintrudelt. (deshalb nennt man
> es asynchrone Übertragung)
>
> Wenn du trotzdem den RXcomplete Interrupt verwendest, um nach jedem
> Zeichen der DMA auf die Finger zu schauen, kannst du das vielleicht
> umschiffen, aber wozu?
> Das bringt mehr Stress und Last als ein nackter Interrupt-Betrieb.
Es gibt doch ein IDLE-interrupt. Dieses symbolisiert doch, dass auf der 
Leitung "nichts mehr los ist". D.h. wenn nicht gerade das 
HalfTransfer-Interrupt oder das FullTransfer-Interrupt den Empfang von 
Daten anzeigen, dann doch aber diese Bit, denn das wird ja nur (neu 
gesetzt) wenn seit dem letzten Setzen mind. ein Byte empfangen wurde, 
oder verstehe ich das falsch?
Demzufolge gibt es drei Quellen, die die Info über empfangene Daten 
bereitstellen:
2x DMA je nach Pufferfüllstand (Half/Full)
1x das Idle-Interrupt des Uart, indem er signalisiert, dass nach zuvor 
empfangenen Daten (die u.U. weder das Half- noch das 
FullTransfer-Interrupt ausgelöst haben) nun nichts mehr kommt. D.h. für 
den Anwender: "Da kam mal was".

von Harry L. (mysth)


Lesenswert?

Paul S. schrieb:
> Es gibt doch ein IDLE-interrupt. Dieses symbolisiert doch, dass auf der
> Leitung "nichts mehr los ist"

Damit erkenn ich aber kein <CR> in einem Datenstrom, wenn ich eine 
Text-Datei auf der Seriellen dumpe. - also nur in sehr spezifischen 
Scenarien nutzbar.
Idle gibts nur, wenn keine Daten gesendet werden.

Wenn ich z.B. ein UI an einem Gerät habe, wo ich über RS232 auf einer 
Konsole Kommandos eingeben kann die man mit <CR> abschliessen muß, mag 
das bei händischer Eingabe ja funktionieren, da zwischen den Zeichen 
immer zeitliche Lücken auftreten.

Wenn ich aber scripten will, und die Kommandos aus einer Datei mit max. 
Geschwindigkeit gesendet werden, gibt kein Idle.
Wie erkenn ich da das Zeilenende? (<CR>)


Ich versteh ehrlich gesagt die ganze Diskussion nicht.

Was soll das bringen?

Serielle Kommunikation ist bereits seit den 70er (bzw. noch früher) 
Standard, und verändert hat sich da nicht viel.
Bereits damals hat man das mit Interrupts gemacht, und das hat sich 
bewährt.
Jeder Firmware-Entwickler hat ein Set aus funktionierenden Funktionen 
für die serielle Kommunikation in seinem persönlichen Wekzeugkasten, und 
ich sehe da keinen Grund, das Rad neu zu erfinden.

Wenn ein paar Tausend Taktzyklen das Zünglein an der Waage sind, würde 
ich mir eher Gedanken darüber machen, ob es nicht sinnvoller wäre, 
nochmal bei Null zu beginnen, das Pflichtenheft nochmal intensiver zu 
studieren und die Auswahl der Hardware zu hinterfragen.

: Bearbeitet durch User
von Heinz M. (subi)


Lesenswert?

Die ganzen Links und Code muss ich in Ruhe durchgehen. Da scheint 
hilfreiches dabei zu sein.

Paul S. schrieb:
> Ein "Neukonfigurieren" des DMA ist nicht notwendig, das macht er im
> Zirkularbetrieb von selbst.

Und genau das ist meine Verständnisproblem. Bei ADC kann ich einstellen, 
dass er nach dem letzten Kanal direkt mit dem ersten wieder anfängt, 
ohne DMA oder ADC neu zu starten. Oder macht das neu starten etwa der 
ADC? Weil wenn es in Software wäre, dann warum beim ADC und bei UART 
nicht? Bei I2C ist logisch, dass es nicht geht und keinen Sinn macht. 
Weil dort wird bidirektional übertragen und die Daten angefordert.

Paul S. schrieb:
> Das ist ja aber Code, der nur einmalig nach den Start des uC durchlaufen
> wird. Danach eig. nie wieder.

Genau, ich will ein Mal aufrufen und nur die Daten entnehmen, weil ein 
durchlaufender DMA kann nichts verpassen und der Mikrocontroller 
schaufelt Daten raus, wenn er grad Zeit hat. Alternativ was du später 
geschrieben hast mit Idle Line. In dem Fall würde ich den DMA stoppen 
und zurücksetzen. Dann habe ich die Eingabe gleich passend im Buffer 
stehen ohne Zeichenerkennung. Das schaue ich mir mal genauer an was du 
zum Idle Line geschrieben hast, das sieht fundiert aus.

Paul S. schrieb:
> Aber das ist ja nicht Gegenstand dieses Themas. Der Fragensteller wollte
> ja wissen, wie man es macht - nicht ob es sinnvoll ist.

Dito.

Deswegen zitiere mich mal selbst aus meinem Eröffnungspost, da es von 
zweien entweder nicht gelesen, nicht verstanden oder schon wieder 
vergessen wurde:

Heinz M. schrieb:
> Geflame über HAL und CubeMX könnt Ihr euch sparen. Ich betreibe das als
> Hobby und habe weder die Zeit, das Wissen, noch die Ausbildung soetwas
> wie die HAL nachzubauen.

@W.S.: Dein gejammere interessiert mich nicht. Ich lege dir jedoch nahe, 
dich mit DMA zu beschäftigen, wozu es wirklich gedacht ist, bevor du 
dermaßen darüber herziehst. Weil dann kommt man ganz schnell zu dem 
Punkt, dass egal wie schlecht die HAL ist, sobald der DMA damit loslegt 
wofür er gedacht ist, kommt kein Mikrocontroller der noch so gut 
programmiert ist mit. Weil Mikrocontroller und DMA grundverschiedene 
Dinge sind, die sich perfekt ergänzen und nicht wie von dir dargestellt 
gegeneinander konkurieren. Ich habe es deutlich beim ADC und noch viel 
deutlicher bei SSD1306 gesehen. Dort waren nicht ein paar Prozent CPU 
Zeit ausschlaggebend, sondern die um Größenordnungen schnellere 
Befeuerung des Displays. Also Faktor 10 oder mehr ... ist schon ein 
Stück her. I2C und ADC nutze ich nicht mehr ohne DMA und sehe auch 
keinen Grund davon abzuweichen.

Harry L. schrieb:
> Jeder Firmware-Entwickler hat ein Set aus funktionierenden Funktionen
> für die serielle Kommunikation in seinem persönlichen Wekzeugkasten, und
> ich sehe da keinen Grund, das Rad neu zu erfinden.
>
> Wenn ein paar Tausend Taktzyklen das Zünglein an der Waage sind, würde
> ich mir eher Gedanken darüber machen, ob es nicht sinnvoller wäre,
> nochmal bei Null zu beginnen, das Pflichtenheft nochmal intensiver zu
> studieren und die Auswahl der Hardware zu hinterfragen.

Da ist der springende Punkt. Ich bin kein Firmware-Entwickler. Ich habe 
kein Pflichtenheft. Und ich bin nicht viel von null entfernt. Ich habe 
beruflich minimalst mit Elektronik und null mit Programmierung zu tun. 
Diese "funktionierenden Funktionen" baue ich mir gerade auf. Und wenn es 
heutzutage DMA und Idle Line Detection gibt und ich es gerade aufbaue, 
warum soll ich es nach uralten Prinzipien aufbauen? Ich kann verstehen, 
dass wenn man sich selbst einen passenden Werkzeugkasten erstellt hat, 
dass man den nicht so schnell wieder verändert. Mache ich selbst so. 
Aber deswegen muss man doch nicht anderen vorschreiben wie die ihren 
Werkzeugkasten aufbauen sollen. Wie gesagt besteht mein ADC und I2C 
Werkzeug aus DMA. Also warum nicht UART auch mit DMA. Ist doch nur 
konsequent.

von Harry L. (mysth)


Lesenswert?

Heinz M. schrieb:
> Diese "funktionierenden Funktionen" baue ich mir gerade auf.

Dann nimm doch den fertigen und erprobten Code, den ich dir oben 
gepostet hab, und hör auf rum zu jammern!

von Stefan (Gast)


Lesenswert?

Heinz M. schrieb:
> ... Alternativ was du später
> geschrieben hast mit Idle Line. In dem Fall würde ich den DMA stoppen
> und zurücksetzen.

Das ist der springende Punkt. Du setzt den DMA eben nicht zurück, 
sondern läßt ihn einfach weiterlaufen. Die Interrupts HC, TC und Idle 
informieren lediglich darüber daß Daten eingetroffen sind. Der 
(DMA-)Puffer wird als Ring (1) betrachtet mit einem Anfang und einem 
Ende. In den ISRs werden im einfachsten Fall die eingetroffenen Daten in 
einen zweiten (Ring-)Puffer kopiert und in der Hauptschleife 
verarbeitet.
In dem Code von Herrn Majerle (2) wird das schön demonstriert.

Ich verwende eine Variante davon um einen STM32L431 mit einem STM32G071 
mit 4MBit und einem NanoPi mit 1,5MBit und minimalen Overhead 
kommunizieren zu lassen. Natürlich mit variablen Paketlängen und alles 
gleichzeitig über zwei UARTs.


1) https://de.wikipedia.org/wiki/Warteschlange_(Datenstruktur)
2) https://github.com/MaJerle/stm32-usart-uart-dma-rx-tx

von Heinz M. (subi)


Lesenswert?

Hatte ich vielleicht nicht deutlich geschrieben. Ich sehe 2 Szenarien:

1. Kontinuierlicher Datenstrom
Z.B. für Messdaten übertragen, aus einer Datei übertragen, ...

DMA starten, und unendlich laufen lassen. Wenn DMA Speicherbereich voll, 
dann setzt der DMA automatisch wieder an den Startpunkt und 
überschreibt.

Der Mikrocontroller muss entsprechend der Flags des DMA abholen, den 
Startpunkt suchen und dann interpretieren.

So ist es von ST vorgesehen. Jedoch verlangt die Funktion 
HAL_UART_Receive_DMA(&huart2,(uint8_t*)UartBufferEingabe,zeichenZahl); 
eine definierte Anzahl an zu empfangenden Zeichen. Was sich 
widerspricht. Worauf sich meine eigentliche Frage bezieht.

2. Einzelne Pakete
Z.B. für Befehlseingabe am PC oder Befehlsübertragung zwischen 
Mikrocontrollern

DMA startet mit definierter Länge ohne Ringbuffer. Zusätzlich läuft Idle 
Line Detect. Nach jedem Empfang wird am Anfang des DMA Speicherbereiches 
angefangen zu schreiben.

Der Mikrocontroller muss den Startpunkt nicht suchen, da der Befehl 
bereits passend im Speicher liegt.


Ersteres ist natürlich universeller, hat jedoch mehr Code und mehr für 
den Mikrocontroller zu tun. Zweiteres ist eher mein Einsatzgebiet.

von o0 (Gast)


Lesenswert?

W.S. schrieb:
> Wenn man mal die ISR für einen normalen UART anschaut, dann sieht man,
> wie kurz diese ist, selbst wenn sie beide Richtungen bedient. Das
> erzeugt eine fast verschwindend geringe CPU-Last.

Irgendwie misst du mit zweierlei Maß.
Bei DMA ist dein Argument immer, dass die CPU bei den paar Requests 
keine hohe Last bekommt.
Aber beim Tastenentprellen ist dein Argument für eine 
Hardwareentprellung, dass der Timer IRQ zu viel CPU Last braucht.

Ja was stimmt denn jetzt?
IRQ Last 115200Baud/s -> 11520Hz
IRQ Last mit 1kHz Entprellteimer -> 1000Hz

Die Enprellroutinen sind auch nicht länger als ein in die FIFO packen.
Das nenne ich inkonsequent!

von Paul S. (mrpaul)


Lesenswert?

Heinz M. schrieb:
> Paul S. schrieb:
>> Ein "Neukonfigurieren" des DMA ist nicht notwendig, das macht er im
>> Zirkularbetrieb von selbst.
>
> Und genau das ist meine Verständnisproblem. Bei ADC kann ich einstellen,
> dass er nach dem letzten Kanal direkt mit dem ersten wieder anfängt,
> ohne DMA oder ADC neu zu starten. Oder macht das neu starten etwa der
> ADC? Weil wenn es in Software wäre, dann warum beim ADC und bei UART
> nicht? Bei I2C ist logisch, dass es nicht geht und keinen Sinn macht.
> Weil dort wird bidirektional übertragen und die Daten angefordert.

Das macht der DMA von selbst.
Im STM32F4 Ref. Manual heißt das Bit "CIRC" im DMA_SxCR-Register.
Der Uart feuert fleißig jedea Mal den DMA-Trigger, wenn er was empfängt.

Heinz M. schrieb:
> So ist es von ST vorgesehen. Jedoch verlangt die Funktion
> HAL_UART_Receive_DMA(&huart2,(uint8_t*)UartBufferEingabe,zeichenZahl);
> eine definierte Anzahl an zu empfangenden Zeichen. Was sich
> widerspricht. Worauf sich meine eigentliche Frage bezieht.

Ich kenne die HAL-Bibliothek nicht. Mit der früheren Standard-Bibliothek 
geht es umzusetzen. Wenn es die HAL-Bibliothek ohne Verrenkungen nicht 
her gibt, spricht sicherlich auch nichts dagegen, einfach direkt die 
Register zu beschreiben.

Heinz M. schrieb:
> 1. Kontinuierlicher Datenstrom
> Z.B. für Messdaten übertragen, aus einer Datei übertragen, ...
>
> DMA starten, und unendlich laufen lassen. Wenn DMA Speicherbereich voll,
> dann setzt der DMA automatisch wieder an den Startpunkt und
> überschreibt.
Ohne IDLE-Interrupt wird das nicht klappen, denn dann müsste definitiv 
jedes Byte ankommen und jeder Block/Kommando oder was auch immer du 
schickst, müsste ein EXAKTES Vielfaches der halben DMA-Pufferlänge sein.

Variante zwei wäre möglich, setzt aber voraus, dass ein Block/Kommando 
NIE länger ist, als dein DMA-Puffer groß. Im Sinne einer späteren 
Wiederverwendung eine recht harte Bedingung.

Im Prinzip braucht du zwei Puffer:
Einen "kleinen" Puffer A für den DMA und einen etwas größen Puffer B für 
das Sammeln, bis du Zeit hast, das Empfangene auszuwerten.
Dazu bekommt der DMA-Puffer A noch einen Lesezeiger ptrA (der zu Beginn 
auf den Anfang von A zeigt) und einen Schreibzeiger für den Ringpuffer 
B. B ist im Prinzip von der gleichen Struktur, wie man den 
Empfangspuffer für den einfachen Interruptbetrieb ohne DMA anlegt.

Beim HalfTransfer-Interrupt ließt du die Daten beginnend von ptrA bis 
Puffermitte von A aus und schiebst das in B (ptrA zeigt dann auf das 
erste Element im zweiten Teil des Puffer A). Beim FullTransfer-Interrupt
von ptrA bis Pufferende (ptrA danach auf Anfang von A setzen).
Endet ein Block nun so, dass weder Half- noch FullTransfer-Interrupt 
kommen, sondern irgendwann das IDLE-Interrupt, dann muss du schauen, wo 
der Zähler des DMA steht. Dieser verrät dir, wie viele Elemente im 
Puffer A noch "fehlen" bis er voll ist. D.h. in diesem Fall muss du von 
prtA bis bis zum Ende von A abzügl. des DMA-Zählers (&A + Länge_A - 
Zähler_DMA). In diesem Fall steht dein ptrA dann irgendwo im Puffer, 
macht aber nichts. Der DMA setzt ja beim nächsten eintreffenden Byte an 
dieser Stelle fort.

von Paul S. (mrpaul)


Lesenswert?

Harry L. schrieb:
> Wenn ich aber scripten will, und die Kommandos aus einer Datei mit max.
> Geschwindigkeit gesendet werden, gibt kein Idle.
> Wie erkenn ich da das Zeilenende? (<CR>)

Bei der Variante (und wenn ich deinen oben geposteten HAL-basierenden 
Code richtig verstanden habe) machst du die <CR> Erkennung ja innerhalb 
der ISR während du die Daten vom Uart abholst und bevor du sie in dein 
Befehlspuffer packst. D.h. die Erkennung ist kein Automatismus, sondern 
von dir geschriebener Code, der INNERHALB der ISR läuft.

Beim Empfang mit DMA hast du recht, da bekommst du den Empfang eines 
<CR> nicht sofort mit, da die empfangenen Daten ja nur verschoben, aber 
nie von der CPU gesehen werden.
Um nun an dein <CR> zu kommen, machst du im Prinzip die gleiche 
Auswertung, wie du sie in deiner ISR machst, nur halt während du die 
Daten aus dem kleinen DMA-Puffer holst (d.h. nach einem der drei 
Interrupts). Hier erfolgt die Auswertung aber nicht zwingend während der 
ISR.

: Bearbeitet durch User
von Harry L. (mysth)


Lesenswert?

Paul S. schrieb:
> D.h. die Erkennung ist kein Automatismus, sondern
> von dir geschriebener Code, der INNERHALB der ISR läuft.

Ja, sicher!
Wie denn sonst? Von Automatismus war nie die Rede.
Genau dafür ist die ISR da.

von Heinz M. (subi)


Lesenswert?

@Paul S.:
Vielen Dank für die Ausführlichen Erklärungen. Einen Teil werde ich 
sicher nutzen.

@Harry:
Das ist der Vorteil bei DMA. Es muss eben nicht in der ISR erfolgen. Das 
bringt mich auf eine Idee. Aber erst mal zurück zu dir. Ich benutze in 
den ISRs nach Möglichkeit nur ganz kurze Berechnungen und Flags. Die 
eigentliche Berechnung erfolgt in der mainloop, die ich ebenfalls so 
aufbaue, dass sie schnell durchläuft. Zum Beispiel einige Sachen wie 
Display Pixelberechnung auf mehrere Durchläufe verteilt. Dadurch gibt es 
weniger Konflikte und schnelle Reaktionen ohne für alles ISRs oder Timer 
zu benötigen.


Die Frage war ja, warum ST den Befehl nicht durchlaufend macht. Paul S. 
hat schön erklärt, dass man normalerweise in der ISR nur den DMA Puffer 
freischaufelt. Was aber den Sinn von DMA schmälert. Statt einem kleinen 
DMA Puffer und einem großen Sammelpuffer, könnte man auch mehrere kleine 
Puffer verwenden. Der DMA Empfangsbefehl würde zyklisch zwischen den 
Puffern wechseln. Dadurch enfällt das rüberschaufeln und DMA wird 
richtig ausgenutzt. Wer sich richtig gut auskennt, könnte sogar die 
Anzahl der nötigen Puffer dynamisch machen....weit über meinem Niveau.

Unter /* USER CODE BEGIN 0 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart2)
{
  flagUartEingabe = 1;
  BufferNummer++;
  HAL_UART_Receive_DMA(&huart2,(uint8_t*)UartBufferEingabe[BufferNummer],3 
);
}

mainloop:
if (flagUartEingabe == 1)
{
  ...Auswertung wie von Paul S. beschrieben
  flagUartEingabe = 0;
}

von Harry L. (mysth)


Lesenswert?

Heinz M. schrieb:
> Ich benutze in
> den ISRs nach Möglichkeit nur ganz kurze Berechnungen und Flags. Die
> eigentliche Berechnung erfolgt in der mainloop,

JA und?
Was willst du mir damit sagen?
Diese Vorgehensweise war bei 8bit AVRs sinnvoll weil da alle Interrupts 
mit der selben Priorität laufen und sich nicht gegenseitig unterbrechen 
können.
Bei ARM mit NVIC sind solche Klimmzüge nicht erforderlich. Da darf eine 
ISR durchaus etwas umfangreicher werden.

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.