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?
Relevanter Code: Empfang von Daten im Prozess:
1 | static void read_fifo(struct uart_rtos *u, char *ptr, int len) |
2 | {
|
3 | do { |
4 | if ((UART_FIFO_SIZE - u->dmarx_handle.Instance->NDTR) != u->read_ptr) { |
5 | /* read a byte if we can */
|
6 | *ptr++ = u->rxbuf[u->read_ptr++]; |
7 | u->read_ptr %= UART_FIFO_SIZE; |
8 | len--; |
9 | } else { |
10 | /* or wait for new data */
|
11 | xSemaphoreTake(u->rx_mutex, portMAX_DELAY); |
12 | }
|
13 | } while (len > 0); |
14 | }
|
Interrupts, die den Semaphor bei "idle" freigeben:
1 | static void usart_fifo_irq(struct uart_rtos *u) |
2 | {
|
3 | BaseType_t woken; |
4 | |
5 | __HAL_UART_CLEAR_IDLEFLAG(&u->uart_handle); |
6 | xSemaphoreGiveFromISR(u->rx_mutex, &woken); |
7 | HAL_UART_IRQHandler(&u->uart_handle); |
8 | portYIELD_FROM_ISR(woken); |
9 | }
|
Interrupts für complete / half complete:
1 | void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) |
2 | {
|
3 | struct uart_rtos *uart = (struct uart_rtos *)huart; |
4 | BaseType_t woken; |
5 | |
6 | xSemaphoreGiveFromISR(uart->rx_mutex, &woken); |
7 | uart->error = 0; |
8 | portYIELD_FROM_ISR(woken); |
9 | }
|
10 | |
11 | void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart) |
12 | {
|
13 | struct uart_rtos *uart = (struct uart_rtos *)huart; |
14 | BaseType_t woken; |
15 | |
16 | xSemaphoreGiveFromISR(uart->rx_mutex, &woken); |
17 | uart->error = 0; |
18 | portYIELD_FROM_ISR(woken); |
19 | }
|
Das ist nicht vollständig, sollte aber reichen um eine Idee davon zu bekommen, wie es zur Zeit funktioniert.
Wie groß sind Deine DMA-Puffer? Wie stellst Du den Overrun fest? Gruß, Stefan
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
:
Bearbeitet durch User
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.
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.