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.
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.
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.
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.
Ich habe gerade ein anderes Problem. Er springt nicht mehr in die void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
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; }
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.
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.
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.
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.
hier gibts eine gute Anleitung zu DMA + variable Länge: https://stm32f4-discovery.net/2017/07/stm32-tutorial-efficiently-receive-uart-data-using-dma/
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.
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....
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.
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.
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). |
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.
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".
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
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.
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!
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
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.
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!
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.
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
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.
@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; }
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.