Forum: Mikrocontroller und Digitale Elektronik Beste Lösung RX DMA Ringbuffer STM32


von Klaus (Gast)


Lesenswert?

Moin,

die Frage ist schon seht oft im Netz aufgetaucht und wurde immer recht 
unzureichend beantwortet. Ich möchte das Thema gern noch einmal 
aufgreifen.

Umgebung:
STM32 CortexM
System Workbench
CubeMX
HAL-lib

Ich benötige ein gutes Konzept, um UART-Zeichen ohne größere 
Prozessorlast zu empfangen. Problem ist dabei nicht ausschließlich die 
durchschnittliche CPU-Zeit die bei einem reinem Interruptverfahren 
"verloren" geht, es geht auch um die langen Ausführungszeiten der 
Interrupts durch intensiven HAL-Code, obwohl das eine Problem natürlich 
mit dem anderen Stark korreliert.

Ein Beispiel aus der Praxis: Um Implementierungsaufände zu sparen wurden 
keine Sleep-Handling eingesetzt, es war ausreichend den Prozessor bei 2 
MHz zu takten. Zwei UARTs mit FIFO im Interrupt-Betrieb wurden 
eingesetzt. Sind beide UARTs auf 9600 Baud eingestellt, kommt es zu 
Zeichenverlust auf den UARTs. Grund ist, dass der STM (jedenfalls den 
ich verwende) nur ein Zeichen RX "FIFO" besitzt. Zwei UART-Interrupts 
gefüllt mit HAL-Code führen bei dieser Konstellation schon zu 
Zeichenverlust.

Die Idee liegt natürlich auf der Hand -> DMA.
Der DMA muss aber ebenfalls irgendwann im Interrupt aufgefrischt werden. 
Fällt diese Zeit bei den zwei UARTs wieder genau aufeinander -> 
Zeichenverlust und diesmal wahrscheinlich weniger deterministisch.

Die meisten Leute die im Netz gefragt hatten, hatte andere Probleme beim 
DMA-Empfang... "Woher weiß ich vorher wie viele Zeichen über die UART 
kommen? Wie soll ich denn dann mein DMA einstellen?"
Das Problem ist recht einfach gelöst. Die Startadresse des DMAs ist 
bekannt und laut Datenblatt ist auch die aktuelle Schreibadresse während 
der DMA läuft valide. Ein DMA-FIFO kann also funktionieren.

Um keine Interrupts beim RX auslösen zu müssen, gibt es ja die 
Ringerbuffer Option. Die ist ja ganz gut und schön, aber damit kann ich 
nicht sicher stellen ob meine FIFO übergelaufen ist. Wenn das 
Hauptprogramm hängt und der Ringbuffer überläuft bekomme ich keine 
Rückmeldung.

Ich hatte die Idee mit dem Eventsystem irgendwie bei jedem DMA-Byte 
einen freien Timer als Counter zu benutzen und hochzuzählen. Damit 
könnte ich mit Ringbufferbetrieb Testen ob es einen Überlauf gab und das 
ohne Interrupts. Ich weiß nur nicht ob das funktioniert wie ich es mir 
vorstelle...

Weitere Idee ist ein Scatter-Gatter DMA-Verfahren, dass einfach die STM 
UART um ein paar mehr FIFO-Zeichen erweitert.

Was meint ihr? Einfach auf eine Überlauferkennung verzichten? Okay, 
hängt natürlich stark vom Einsatzzweck ab...

VG

Klaus

von Kanack (Gast)


Lesenswert?

Hier wurden schon mehrere Kriege geführt, da die altgebackenen die DMA 
per UART nicht akzeptieren...

> Was meint ihr?
Ich meine bei 9600 Baud stimmt was bei dir im Programm nicht. Das ist 
keine Geschwindigkeit für einen ARM. Du müssest eventuell die Interrupt 
Priorität beider UART ändern und den fifo buffer erhöhen.

> Einfach auf eine Überlauferkennung verzichten? Okay,
> hängt natürlich stark vom Einsatzzweck ab...

UART per DMA ist einfach, wenn du weisst wie viele Daten ankommen. Dies 
ist aber nicht sicher! Was passiert, wenn es zu wenige/viele Daten 
ankommen?! Aber auf die schnelle sicher hilfreich.

ST hat eine Appnote mit ein paar Vorschlägen wie man überprüft, ob die 
Übertragung zu Ende ist. Z.B. mit den systick timer kann man jede ms 
prüfen wie weit in den (circular) Buffer geschrieben wurde....

Diese Diskussion wäre interessant gewesen, wenn du >1 Mbaud transfers 
hättest, sonst vermute ich eher ein Fehler im Programm.

von Klaus (Gast)


Lesenswert?

Was hat das mit ARM oder nicht ARM zu tun? Das Verhältnis CPU-Speed zu 
UART-Speed ist entscheidend.
Wenn der nur mit 2 MHz schleicht, dann sind das nur 2083 Takte/Befehle 
pro UART Byte mit 9600 8N1 und dann schau dir mal den HAL-Quellcode in 
den Interrupts an!
Ich erinnere mich gemessen zu haben, dass die CPU alleine zu 1/4 mit 
empfangen + zweite UART weiterleiten ausgelastet war.

Priorität ändern? Dann sehe ich auf der Debug-UART nur noch quatsch und 
wunder mich...

Im Systick den Überlauffall abprüfen könnte natürlich funktionieren. 
Hast du einen Link zu der APP-Note?

VG

Basti

von Klaus (Gast)


Lesenswert?

Was hat das mit ARM oder nicht ARM zu tun? Das Verhältnis CPU-Speed zu 
UART-Speed ist entscheidend.
Wenn der nur mit 2 MHz schleicht, dann sind das nur 2083 Takte/Befehle 
pro UART Byte mit 9600 8N1 und dann schau dir mal den HAL-Quellcode in 
den Interrupts an!
Ich erinnere mich gemessen zu haben, dass die CPU alleine zu 1/4 mit 
empfangen + zweite UART weiterleiten ausgelastet war.

Priorität ändern? Dann sehe ich auf der Debug-UART nur noch quatsch und 
wunder mich...

Im Systick den Überlauffall abprüfen könnte natürlich funktionieren. 
Hast du einen Link zu der APP-Note?

VG

Klaus

von Kanack (Gast)


Lesenswert?

Ehrlich gesagt zwing dich niemand die HAL zu nutzen oder auch dein µC 
mit 2mhz zu betreiben und wenn das deine Vorgabe ist, dann hättest du es 
auch erwähnen können.

> Hast du einen Link zu der APP-Note?
Google einfach: stm32 dma uart application note
Keiner weiß welchen stm32 du hast.

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Hallo Klaus,

bei 2MHz Taktfrequenz solltest Du in der Tat deinen Code optimieren, 
dazu gehört fast notwendigerweise, den HAL zu strippen, da ist overhead 
ohne Ende drin.

Ersetze auch deinen Debug UART durch ITM Ausgaben, dann hast Du 
wenigstens keinen großen Heisenberg Effekt durch die Debuggerei.

DMA geht beim Empfangen, hat aber sehr viel versteckte Fallstricke. Du 
solltest Dich beim Aufsetzen des DMA nicht darauf verlassen, was das 
Protokoll als erwartete Zeichenanzahl liefert, da sonst bei Framing 
Errors o.ä. Zeichen fehlen und damit mglw. ein aufgesetzter DMA receive 
nicht wie erwartet zurückkommt. Dein Lösungsansatz mit dem Auslesen der 
Ringbufferadresse verstehe ich nicht; damit kannst Du zwar im ISR 
feststellen, wieviel Zeichen tatsächlich momentan kopiert wurden, aber 
was Du ja eigentlich willst, ist die Anzahl der Interrupts minimieren, 
oder? Und genau dazu willst Du ja eben vorher wissen, auf wievel 
Zeichen Du wartest...

Die Abfragerei über SysTick etc. erscheint mir auf den ersten Blick eher 
als Krücke. In 20 Jahren Kommunikationsentwicklung habe ich sowas noch 
nie benötigt.

DMA ist i.W. auf der Transferseite bei größeren Paketen interessant und 
kann die Tx Interrupts je nach Protokoll drastisch minimieren, was damit 
dann auch wieder den receive ISRs mehr Zeit gibt (so das Protokoll 
entweder Voll Duplex ist oder Du parallel auf mehreren UARTs arbeitest).

von Uwe B. (Firma: TU Darmstadt) (uwebonnes)


Lesenswert?

DMA bringt erst dann etwas, wenn man Blöcke bearbeitet. Mit hinreichend 
grossen DMA Puffer lässt man den DMA im Ringmodus laufen und schaut 
häufig genug auf den DMA Count. Hat sich der DMA Count geaendert, sind 
neue Zeichen da, die man verarbeitet, Danach merkt man sich den neuen 
DMA Zählerstand. Geht also im Prinzip ohne Interrupt. Wenn es hinreichen 
oft Pausen in den Eingangsdaten gibt, kann man sich auch einen Idle 
Interrupt geben lassen, um dann mit der Verarbeitung anzufangen.

von W.S. (Gast)


Lesenswert?

Klaus schrieb:
> Umgebung:
> STM32 CortexM
> System Workbench
> CubeMX
> HAL-lib

Ich meine, daß genau DAS der Fehler ist, den du da machst. Guck dir 
mal einen typischen UART-Interrupt-handler an:
1
__irq void USART1_IRQHandler (void)
2
{ char c;
3
  int  i, j;
4
5
  if (USART1_ISR & ((1<<5)|(1<<3)))     // RX: Zeichen empfangen oder Overrun
6
  { c = USART1_RDR;
7
    i = U1Buf.InWP;
8
    j = (i+1) & (IBLEN-1);
9
    if (j!=U1Buf.InRP)
10
    { U1Buf.InBuf[i] = c;
11
      U1Buf.InWP = j;
12
    }
13
  }
14
15
  if  (USART1_ISR & (1<<7))              // TX: Sendepuffer leer geworden
16
  { i = U1Buf.OutRP;
17
    if (i!=U1Buf.OutWP)                  // ob es was zu senden gibt
18
    { USART1_TDR  = U1Buf.OutBuf[i];
19
      U1Buf.OutRP = (i+1) & (OBLEN-1);
20
    }
21
    else
22
    { USART1_CR1 &= ~(3<<6);             // nö, TXEIE und TCIE ausschalten
23
    }
24
  }
25
}

Der erledigt beides: Empfangen und Senden - und wie man sieht, ist er 
ausgesprochen kurz. Was willst du da mit DMA verbessern? Du schaffst mit 
DMA nur eines: mehr Verwaltungsaufwand und mehr Komplikationen. Aber 
keine Erleichterung der Situation.

Aber um die Sache wie oben gezeigt eben besser zu lösen, müßtest du das 
aufgedunsene ST-Zeugs schlicht und einfach weglassen - und die meisten 
Leute, die mir hier in diesem Forum aufgefallen sind, können das nicht, 
weil sie einfach mental zu unselbständig sind.

W.S.

von STMler (Gast)


Lesenswert?

Kanack schrieb:
> ST hat eine Appnote mit ein paar Vorschlägen wie man überprüft, ob die
> Übertragung zu Ende ist. Z.B. mit den systick timer kann man jede ms
> prüfen wie weit in den (circular) Buffer geschrieben wurde....


http://www.st.com/content/ccc/resource/technical/document/application_note/d6/03/cb/dd/03/54/49/d6/CD00256689.pdf/files/CD00256689.pdf/jcr:content/translations/en.CD00256689.pdf

von Felix F. (wiesel8)


Lesenswert?

Ich nutze einen F103 mit 72 MHz. 2 USARTs schreiben/senden per Interrupt 
in 2 Puffer mit 9600 Baud und 230,4 KBaud. In der main werden zyklisch 
diverse Sensoren per SPI/I2C abgefragt. Mein Controller langweilt sich 
dabei...

mfg

von Marcus H. (Firma: www.harerod.de) (lungfish) Benutzerseite


Lesenswert?

Klaus schrieb:
> Ich hatte die Idee mit dem Eventsystem irgendwie bei jedem DMA-Byte
> einen freien Timer als Counter zu benutzen und hochzuzählen. Damit
> könnte ich mit Ringbufferbetrieb Testen ob es einen Überlauf gab und das
> ohne Interrupts. Ich weiß nur nicht ob das funktioniert wie ich es mir
> vorstelle...
Was spricht dagegen, statt des Timers direkt den DMA_CNDTR zu verwenden?
Oder blockt Dich da der HAL?
Ringpuffer groß genug machen und hinreichend oft nachschauen.

2MHz? Welche Maschine?
Wenn es Dir um die Energiebilanz geht - es ist oft effizienter, die MCU 
kurz schnell laufen zu lassen und dann wieder tief Schlafen zu schicken.

Wobei 2MHz für die bisher bekannte Aufgabenstellung kein Problem sein 
sollten. Alles in allem ist dieser Thread wieder mal ein Spitzenargument 
gegen HAL in Produktivsystemen.
HAL + Datasheet + Reference Manual -> Anleitung zum selber schreiben

von Klaus (Gast)


Lesenswert?

Moin,

> Was spricht dagegen, statt des Timers direkt den DMA_CNDTR zu verwenden?
> Oder blockt Dich da der HAL?
> Ringpuffer groß genug machen und hinreichend oft nachschauen.

Ja, dass wurde schon öfter genannt, wenn man einen Interrupt hat, der 
hinreichend oft genug schaut ob ein Überlauf eingetreten sein könnte, 
dann funktioniert das. Mit einer Main-Loop mit unbekannter 
Bearbeitungszeit müsste man wenigstens ein Rückmeldung bekommen, dass es 
auf dem UART-Bus gerade zu Problemen gekommen sein könnte. Funktioniert 
leider so nicht. Wenn es also keine besser Lösung für die von mir 
festgelegten Umgebungsbedinungen gibt, dann würde ich es mal so 
versuchen.

Oder habe ich was wesentliches Übersehen? Schön wäre also eine weitere 
Zählvariable die angibt wie oft der Rinbuffer DMA schon 
durch-/übergelaufen ist. Dann könnte man beim RX-FIFO-READ ganz einfach 
abprüfen ob etwas verloren gegangen ist.

Das die HAL natürlich nicht die schnellste und effizienteste ist, sollte 
ja klar sein...
Dafür soll die HAL viel zu viele Anwendungen erschlagen (RTOS etc.).
Die Vorgaben der neuen HAL kommen ja soweit ich weiß aus den Dogmas von 
ARM selbst. Ich bin selbst kein riesen Fan der HAL, aber in einem 
Betrieb wo es mehr als ein Programmier gibt, ist es wenig zielführend 
wenn jeder seine eigenes Süppchen kocht. Gerade wenn man von STMF0 bis 
STMF7 alles bedient.

@STMler Das Konzept aus der APPNOTE finde ich komisch bis schlecht. Aber 
danke für den Link.

@Felix F. Klingt als wenn du der CPU-Flüsterer bist... woher kommt 
dieses Gefühl das deine CPU sich langweilt? Vielleicht steht auch alles 
auf der Kippe und im richtigen Moment knallt es?

Immerhin hast du schon mal 3125 Takte pro Zeichen bei der schnellen UART 
Zeit, um ein Zeichen abzuholen. Da du nur eine schnelle UART hast, 
könntest du den Interrupt höher priorisieren und damit den Interrupt 
auch Nested aufrufen lassen.

@W.S. Es geht manchmal einfach nicht ums können...

VG

Klaus

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

>
> Das die HAL natürlich nicht die schnellste und effizienteste ist, sollte
> ja klar sein...
> Dafür soll die HAL viel zu viele Anwendungen erschlagen (RTOS etc.).
> Die Vorgaben der neuen HAL kommen ja soweit ich weiß aus den Dogmas von
> ARM selbst. Ich bin selbst kein riesen Fan der HAL, aber in einem
> Betrieb wo es mehr als ein Programmier gibt, ist es wenig zielführend
> wenn jeder seine eigenes Süppchen kocht. Gerade wenn man von STMF0 bis
> STMF7 alles bedient.
>

Nein nein, die "HAL" von ARM ist die CMSIS, die ARM pusht, um 
herstellerübergreifend kompatibel zu sein. HALs sind genau umgekehrt 
Abstraktionen, die dazu dienen sollen, Prozessorfamilien *eines 
Herstellers* möglichst interoperabel zu machen. Dass die beiden Layer 
manchmal der Formel "plus + plus = minus" folgen, weiss Jeder, der sich 
mal durch Beispielapps durchgeackert hat, in denen Beides nebenläufig 
vorkommt...

von W.S. (Gast)


Lesenswert?

Klaus schrieb:
> @W.S. Es geht manchmal einfach nicht ums können...

Und worum dann?

W.S.

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.