Moin,
ich bin gerade dabei, auf einem STM32 eine möglichst effiziente
Implementierung für den Empfang von Daten über mehrere UARTs
auszuarbeiten. Es sind insgesamt drei UARTs aktiv, die mit hoher
Geschwindigkeit (mind. 230400 Baud) Daten empfangen und senden.
Um die Daten ohne andauernde Aktivierung der CPU zu empfangen, nutze ich
DMA im circular mode, d.h. als Ringpuffer. Das funktioniert prinzipiell
sehr gut. Die eigentlichen Empfänger der Daten sind mehrere
FreeRTOS-Prozesse.
Nun habe ich allerdings das Problem, dass ich den Prozessen mitteilen
muss, wenn neue Daten angekommen sind. Grundsätzlich nutze ich dafür
Semaphoren. Das heißt, die Prozesse lesen so viel wie möglich aus den
Ringpuffern (entsprechend dem NDTR-Register des DMA-Streams), und wenn
das nicht mehr möglich ist, warten sie auf einen Semaphor, der die
Ankunft neuer Daten ankündigt. Damit kann man auch Timeouts sehr einfach
realisieren.
Ich kann nun die Semaphoren mit Interrupts freigeben, wenn neue Daten
empfangen werden. Dafür nutze ich momentan "transfer complete",
"transfer half complete" des DMA-Streams und "idle" des UARTs. Das
heißt, immer wenn der Ringpuffer halb voll bzw voll ist oder der UART
keine neuen Daten mehr empfängt, wird der Semaphor freigegeben.
Ich habe nun das Problem, dass damit die CPU zu selten aufgeweckt wird
und nicht genügend Zeit bleibt, die neuen Daten zu verarbeiten. Es kommt
zum overrun! :(
Wenn ich alternativ den RXNE-Interrupt des UART verwende, um den
Semaphor freizugeben, gibt es das Problem nicht mehr. Aber dann gibt es
eine Flut an Interrupts, einer pro empfangenen Zeichen, und ich brauche
sehr viel CPU-Zeit. :(
Ich brauche also idealerweise eine Möglichkeit, per Interrupt regelmäßig
(bspw. alle n Zeichen) den Semaphor freizugeben.
Hat jemand eine Idee?
Stefan schrieb:> Wie groß sind Deine DMA-Puffer?
z.Z. 64 Byte. Ich kann die auch größer machen, dann dauert es nur etwas
länger, bis ein Overrun auftritt.
> Wie stellst Du den Overrun fest?
Noch gar nicht explizit, ich merk es nur daran, dass beim Empfang Mist
herauskommt und Bytes fehlen. Es sollte sich aber leicht daran
feststellen lassen, wenn der write pointer den read pointer "überholt".
Bist Du Dir sicher, daß die Tasks rechtzeitig bedient werden? Bei 64
Byte großen Puffern hast Du ab half-full und 230400 Baud noch 32 /
2304000 * 10 = ca. 1,4ms Zeit, bis der Puffer überläuft. Diese Zeit muß
für Deinen verarbeitenden Task und alle höherprioren ausreichen. Ggf.
diesen Task mal auf die höchste Prio setzen und schauen, ob das Problem
noch auftritt.
Wenn Du jedes Zeichen einzeln verarbeitest, dann gewinnst Du maximal
eine Verdopplung der Verarbeitungszeit. Das kannst Du genauso durch eine
Verdopplung des Eingangspuffers erreichen.
Die Möglichkeit "Interrupt nach n Zeichen" gibt es meines Wissens nicht.
Hätte ich auch schon gut gebrauchen können ;-)
Viele Grüße, Stefan
Du könntest mit einem auf die Baudraten angepassten Timer regelmäßig den
Füllstand des Puffers auslesen. Wenn der sich in einem gewissen Fenster
bewegt, gibst Du die Semaphore zum Auslesen frei.
Ping Pong spielen könnte helfen.
Du kannst am DMA Kontroller 2 Puffer anmelden.
Wenn ein Puffer voll ist kommt ein Interrupt. Der DMA Kontroller meldet
dann welcher der beiden Puffer voll wurde, mit dem kannst Du dann machen
was Du willst da der DMA Kontroller jetzt in dem anderen rum wickelt.
"Machen was Du willst" geht sogar so weit, dass Du den Zeiger auf den
gerade nicht vom DMA verwendeten Puffer auch ändern kannst. Damit wird
eine Puffer immer vom DMA verwendet und der andere steht ihm direkt im
Anschluss nahtlos zur Verfügung.
Meistens reichen diese 2 Puffer, der Inhalt muss ja auch irgendwann
abgearbeitet werden. Du kannst aber auch zwischen beliebig vielen
Puffern hin- und herschalten und die jeweils vollen z.B. an eine Message
Queue übergeben.
Wichtig ist dabei immer: Den Interrupt so schnell wie möglich
verarbeiten und die eigentliche Arbeit einen Thread machen lassen.
Gruß Martin
Martin K. schrieb:> Ping Pong spielen könnte helfen.
Eine ähnliche Rolle spielt bereits ein optionaler DMA-Interrupt des
STM32, der nach der Hälfte der programmierten Bytes auftritt. Der er
auch verwendet.
Stefan schrieb:> Bist Du Dir sicher, daß die Tasks rechtzeitig bedient werden? Bei 64> Byte großen Puffern hast Du ab half-full und 230400 Baud noch 32 /> 2304000 * 10 = ca. 1,4ms Zeit, bis der Puffer überläuft. Diese Zeit muß> für Deinen verarbeitenden Task und alle höherprioren ausreichen. Ggf.> diesen Task mal auf die höchste Prio setzen und schauen, ob das Problem> noch auftritt.
Guter Punkt, das wird tatsächlich recht knapp.
Gerd E. schrieb:> Du könntest mit einem auf die Baudraten angepassten Timer regelmäßig den> Füllstand des Puffers auslesen. Wenn der sich in einem gewissen Fenster> bewegt, gibst Du die Semaphore zum Auslesen frei.
Das klingt nach einem interessanten Ansatz, der sowas Ähnliches wie
"Interrupt nach n empfangenen Zeichen" umsetzt. Da habe ich nun aber
eine IMHO bessere Lösung für: ich lasse das Warten auf den Semaphor
einfach nach n Ticks auslaufen und probiere dann das Lesen. Praktisch
also eine Kombination mit Polling.
Letztendlich war das eigentliche Problem aber auch ein ganz anderes:
ich habe jedes gelesene Zeichen mit einer blockierenden Methode (durch
einen Semaphor für Transmit) wieder ausgegeben. Da das Schreiben so
mindestens so lange braucht wie das Lesen, tendenziell aber etwas länger
durch Overhead, konnte das auf Dauer natürlich nicht klappen und der
Puffer lief dann garantiert über.
Klarer Fall von PEBKAC also.