Wie konfiguriere ich DMA, um USART-Nachrichten mit variabler Länge zu empfangen?

Ich versuche, USART-Nachrichten auf einem Mikrocontroller zu empfangen, der vom PC stammt und ihm befiehlt, bestimmte Tests auszuführen. Ich verwende STM32F4, ich habe mich für die Verwendung von DMA entschieden, da Nachrichten auf demselben USART, die vom Mikrocontroller stammen, 2000000 bps haben müssten, da ich es nicht mit einer niedrigeren Bitrate oder ohne DMA zum Laufen bringen könnte. Ich verwende binär statt ASCII für beide Richtungen.

Was ich bisher entworfen habe, "funktioniert" (aktualisiert fast in Echtzeit), vorausgesetzt, die Rate der gesendeten Nachrichten ist konstant, da jede neue Nachricht die Löschung der vorherigen aus dem DMA-Register erzwingt. Wenn jedoch initiiert wird und nur ein einziger Anweisungs-/USART-Frame gesendet wird, der zufällig weniger als die Datenmenge ist, die für die empfangen werden soll HAL_UART_Receive_DMA(), bleibt die Nachricht hängen, bis sie von der nächsten Nachricht verschoben wird.

Geben Sie hier die Bildbeschreibung ein

Ein noch schlimmeres Szenario wäre, wenn USART_take_size100 ist und es einen einzelnen 10-Byte-Rahmen im DMA-Puffer gibt. In diesem Fall müsste ich auf weitere 9 (10 Byte) Frames warten, bevor ich sie alle auf einmal empfange.

So sieht mein Callback aktuell aus.

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    
    //              uart handle, global array, numb of bytes to trigger interrupt       
    HAL_UART_Receive_DMA (huart, raw_serial, USART_take_size);
    
    // function that adds received bytes to the circular buffer 
    AppendSerial(raw_serial,USART_take_size);
            
}

Ein Thread entnimmt Bytes aus dem Ringpuffer, durchläuft sie und extrahiert basierend auf SOF- und EOF-Bytes die Nachricht innerhalb des Rahmens.

Mein Problem ist, dass die ankommenden Frames unterschiedlich lang sind. Wenn USART_take_sizex festgelegt ist und der Frame ankommt und die Länge kleiner als x ist, muss er im Pufferarray im extrahierenden Thread sitzen, bis ein weiterer Frame ankommt lässt notwendige Bytes erscheinen, damit der Parser die eingebettete Nachricht erhält.

Ich sehe drei Möglichkeiten, dies zu lösen, und jede scheint nicht richtig zu sein:

  1. Auf 1 setzen USART_take_size, was den ganzen Punkt von DMA zunichte macht

  2. Haben Sie eine feste Rahmengröße und füllen Sie den Inhalt vor EOF mit Nullen auf, wenn der Inhalt kleiner als die USART_take_size. Das wirkt einfach schlampig und verschwenderisch. Es müsste auch bei jedem Frame ausgelöst werden, obwohl die Gesamtrate in diesem Fall viel höher sein könnte.

  3. Konfigurieren Sie den DMA so, dass er ISR bei einer variablen Anzahl von empfangenen Bytes auslöst, vorausgesetzt, es gab vorher eine gewisse Inaktivität.

Ich weiß nicht viel über DMA, und obwohl es ein Kompromiss zwischen der Anzahl der ausgelösten ISR und der Mindestnachricht zu sein scheint, die Sie lesen können, denke ich, dass es eine Möglichkeit geben sollte, Nachrichten nach einer Zeit der Inaktivität zu erhalten. Jeder andere Lösungsansatz wäre toll. Danke schön!

Welcher Mikrocontroller? Ist da ein FIFO drauf? Gibt es einen Referenzcode oder Anwendungshinweise, die Sie verwenden können?
Mit einem STM32 ist dies ein etwas schwierig zu lösendes Problem. Müssen Sie wirklich DMA verwenden?
@ pjc50 sein STM32F407, das tut es und der DMA hat zwei Register, eines für den halben Rückruf. Ich rette hauptsächlich Code von anderen, ich konnte keinen Code von AN finden, der meinen Bedürfnissen entsprechen würde.
@Peter Smith, warum ist STM32 gegenüber anderen MCUs schwierig? Ich muss häufig Nachrichten abrufen und konnte sie nur mit DMA bei 2000000 an den PC senden. Könnte möglicherweise eine noch höhere Bitrate versuchen, würde dann aber noch mehr Taktzyklen verbrauchen, wenn sie ohne DMA verwendet würde, und ich brauche etwas Ersatz für andere Logik für den Antrieb anderer Peripherien verwendet, die noch kommen werden.
(Antwort): Einige Mikrocontroller können einen Empfänger-Interrupt für ein programmiertes „Sonderzeichen“ (das alles sein kann) generieren, die STM32-Serie jedoch nicht. In solchen Situationen kann ich immer wissen, wann eine bestimmte Nachricht vollständig ist.
@PeterSmith Wenn sie das hätten, würde das bedeuten, dass ich sicherstellen müsste, dass keine der Steuernachrichten tatsächlich dieses Sonderzeichen hat, ich denke, es besteht keine Notwendigkeit für SOF/EOF, oder?
@PeterSmith Der STM32 UART hat einen Zeicheninterrupt. Ich verwende es für genau diesen Zweck zusammen mit DMA. Es heißt Zeichenabgleich und teilt Register mit Adressabgleich.

Antworten (2)

Ich hatte ein ähnliches Problem in einem meiner Projekte. Ich habe es gelöst, indem ich einen ausreichend langen Puffer erstellt habe, um die längstmögliche Nachricht zu speichern, und DMA zur Übertragung von USART verwendet habe. Um das Ende der Nachricht zu erkennen, habe ich einen Leerlaufleitungserkennungs-Interrupt verwendet, der ausgelöst wird, wenn der USART-Empfänger den Empfang von Daten einstellt.

Auch hier funktioniert die IDLE-Leitungserkennung wie ein Zauber mit DMA. Unterstützung dafür musste in der STM HAL-Bibliothek hinzugefügt werden. Sobald der IDLE-Interrupt ausgelöst wird, deaktiviere ich RX DMA und reaktiviere ihn mit einem anderen Puffer.

Der STM32 DMA kann nicht selbst das tun, was Sie wollen. Um mit Nachrichten variabler Länge umgehen zu können, müsste es in der Lage sein, die Nachricht intelligent zu parsen und die Länge unter Verwendung des von Ihnen gewählten Codierungsschemas zu bestimmen. Das geht weit über seine Möglichkeiten hinaus.

Mir fallen für dein Problem eigentlich nur zwei Möglichkeiten ein:

  1. Verarbeiten Sie einfach jedes Byte in der USART-Empfangs-ISR. Der STM32 hat einen ziemlich geringen Interrupt-Overhead. Wenn Ihre Anwendung damit umgehen kann, ist es manchmal am besten, einfach zu gehen, auch wenn es vielleicht nicht so elegant ist.

  2. Sie können den DMA einfach so konfigurieren, dass er als Fifo-Puffer arbeitet. Der DMA lädt Bytes in den Puffer und Sie richten dann einen periodischen Timer-Interrupt ein, um den Puffer auf Daten zu prüfen und Nachrichten zu analysieren. Dies ist ein Zwischenansatz im Vergleich zu Option 1, da Sie damit den Interrupt-Overhead auf Kosten der Latenzzeit reduzieren können. Sie können die Timer-Abfrageperiode gegen die Fifo-Größe abstimmen, um Ihre speziellen Anforderungen an die Echtzeitverarbeitung auszugleichen. Es gibt auch Tricks, die Sie mit den DMA-Level-Triggern machen können, obwohl die Nützlichkeit davon von Ihrem Nachrichtenformat abhängt.

#2 ist wahrscheinlich die beste Option. Es müsste nicht einmal ein Timer verwendet werden. Man könnte die Daten einfach dem DMA-FIFO überlassen und den FIFO-Puffer an irgendeinem Punkt während des Programmablaufs synchron abfragen, wo es angebracht ist. Oder man könnte den Zeitgeber nur beim Empfang des ersten Zeichens starten, dann den Puffer entleeren und den Zeitgeber stoppen, wenn der Zeitgeber abläuft.
@JimmyB Das Leeren des Puffers würde höchstwahrscheinlich nur kennzeichnen, dass er jetzt wieder beschrieben werden kann, möglicherweise durch Neustarten des DMA oder Verschieben eines Lese- / Schreibindex zurück an den Anfang. Ich frage synchron ein Flag zum Analysieren ab, das von DMA-Complete- oder Character-Match-Interrupts gesetzt wird, um zu wissen, wann eine vollständige Nachricht empfangen wird. Timer sind zu kostbar, um sie zu verschwenden.
@Toor Was ich mit "Entleeren" meinte, ist "alle Daten im Puffer extrahieren und verarbeiten", zB bereits empfangene (Teil-)Nachrichten analysieren.