STM32 USART mit DMA – welche Interrupts verwenden?

Ich arbeite an der Firmware für einen STM32F103, der über RS232 mit 115200 Baud mit einer Motorsteuerung kommuniziert. Die Motorsteuerung (ein Copley Xenus XTL ) arbeitet nach einem "Sprechen, wenn angesprochen"-Protokoll. Ich verwende die ASCII-Programmierschnittstelle in den verlinkten Dokumenten. Der STM32 sendet immer denselben Befehl ("g r0x18"), um ein Register abzufragen, und die Motorsteuerung antwortet mit einer variablen Anzahl (~4-10 Bytes) von Zeichen, die mit einem Wagenrücklauf abgeschlossen werden ("v 12345", wobei die Anzahl der Stellen ist variabel). Ich habe Code, um die Antwort zu analysieren und einen numerischen Wert daraus zu ziehen. Sobald die Antwort geparst ist, sollte der Register-Poll-Befehl erneut an die Motorsteuerung übertragen werden. Der STM32 liest auch einen ADC-Kanal über DMA im Zirkularmodus im Hintergrund.

Ich würde dies gerne mit dem DMA-Controller implementieren, um alles so blockierungsfrei wie möglich zu machen, aber ich bin etwas verwirrt darüber, welche Interrupts ich verwenden sollte und wann sie ausgelöst werden. Ohne Verwendung des DMA-Controllers befindet sich der Parsing-Code derzeit im USART-RXNE-Interrupt. Angenommen, ich sende einen Befehl und die Motorsteuerung beginnt zu antworten. Ich glaube, der RXNE-Interrupt wird für jedes empfangene Byte ausgelöst, aber was ist mit dem DMA-Transfer-Complete-Interrupt? Gibt es in diesem Fall überhaupt einen funktionalen Unterschied zwischen der Verwendung des DMA TC-Interrupts und des USART RXNE-Interrupts?

"Die Motorsteuerung antwortet mit einer variablen Anzahl (~ 4-10 Bytes) von Zeichen, die durch einen Wagenrücklauf abgeschlossen werden." -- Ich glaube, das schließt jede vernünftige Verwendung von DMA dafür aus. Sie müssen nach jedem empfangenen Byte einen benutzerdefinierten Code ausführen, um festzustellen, ob die Übertragung abgeschlossen ist. der DMA-Controller wird hier nicht helfen. Verwenden Sie weiterhin RXNE, aber reduzieren Sie die ISR möglicherweise darauf, nur ein Byte zu akzeptieren, es in Ihren Puffer zu legen, zu prüfen, ob es CR ist, und wenn dies der Fall ist, setzen Sie ein Signal für die weitere Verarbeitung / Analyse.
the parsing code currently resides in the USART RXNE interruptEin besserer Weg wäre, nur den RX-Interrupt zu verwenden, um das aktuelle Byte in einen Ringpuffer zu legen. Überprüfen Sie dann in der Hauptschleife, ob der Puffer eine vollständige Nachricht enthält, und analysieren Sie diese Nachricht.
Außerdem sind 115 kBaud ungefähr 11 kByte/s, was bedeutet, dass bei 66 MHz CPU-Takt kaum mehr als ein IRQ alle 6000 CPU-Zyklen vorhanden ist; das sollte etwa 1% CPU-Last sein und nur während des tatsächlichen Datenempfangs.
Es ist schwieriger, nur mit einem Teil der Informationen zu helfen. Würden Sie bitte einen Link zum Motorcontroller und seinem Protokoll posten. Bitte geben Sie auch genau an, welche Befehle Sie an die Motorsteuerung senden. Ich stimme den beiden Kommentaren zu, a. 1 % CPU-Overhead ist nicht viel, und b. Nehmen Sie den Parsing-Code aus der Interrupt-Service-Routine. Die Interrupt-Service-Routine muss so kurz wie möglich sein, um das Blockieren zu minimieren. Außerdem ähnelt es eher einer DMA-Übertragung, wenn alles, was es tut, Bytes im RAM speichert. Ich würde erwarten, die Last durch die Verwendung von DMA zu reduzieren, aber es ist komplexer.
Obwohl DMA wahrscheinlich nicht erforderlich ist, können Sie es möglicherweise verwenden, indem Sie eine Übertragung für die maximal mögliche Länge einrichten und sie anhalten lassen, wenn die Daten ausgehen, und dann einen Timer verwenden, der eingerichtet wurde, als Sie den Befehl zum Anhalten des DMA gesendet haben und die Ergebnisse verarbeiten. Wenn Sie die Motorcontroller-Firmware ändern können, können Sie die Anzahl der zu sendenden Bytes zuerst setzen und nur einen UART RX-Interrupt haben, der das abfängt und den DMA programmiert. Oder Sie könnten vielleicht eine Unterbrechungsbedingung senden, um die Nachricht zu beenden, und darauf einen Interrupt auslösen, um das Ergebnis auszuwerten.
@gbulmer Ich habe die Informationen über die Motorsteuerung hinzugefügt. Es klingt, als würde ich vielleicht die falsche Frage stellen. Ich habe noch nie mit einem DMA-Controller auf einem Mikro gearbeitet, daher wäre es sinnvoll, "dafür kein DMA zu verwenden" als Antwort zu posten.
@m.Alin A better way would be to only use the RX interrupt to put the current byte in a circular buffer.- Dies gilt nur, wenn Sie 1. innerhalb eines begrenzten Zeitrahmens auf bestimmte Ereignisse reagieren müssen und 2. nicht über die Möglichkeiten für verschachtelte Interrupts verfügen . Da sich dies auf einem STM32 befindet, verfügt es über ein NVIC mit mehreren Interrupt-Prioritäten, sodass eine signifikante Logik in den Interrupt-Handlern durchaus akzeptabel ist.

Antworten (2)

Sie können DMA so konfigurieren, dass es im zyklischen Modus arbeitet, und mit einem angemessen großen Puffer können Sie Zeichen mit einer einfachen pollFunktion so oft abrufen, wie Sie möchten. Speichern Sie einfach den Index des letzten abgerufenen Zeichens und verwenden Sie das DMA-Register, um zu prüfen, wie viele Zeichen es bis zum Rollover empfangen muss (das AFAIR-Register hat NDTRin seinem Namen), und verarbeiten Sie die seit dem letzten Aufruf empfangenen Zeichen, und aktualisieren Sie dann den Index des letzten abgerufenen Zeichens.

Die beschriebene DMA-Nutzung macht Sie pollunabhängig von dem Kontext, aus dem Sie es aufrufen, solange Sie eine Präemption verhindern. Dann können Sie den RX-Interrupt verwenden, um Abfrageaufrufe auszulösen, oder Sie können dies in einem anderen Kontext tun (z. B. mit einigen periodischen Ereignissen).

Dies ist im Allgemeinen effizient, wenn Frames lang genug sind, die Baudrate groß ist und die Interrupt-Latenz größer ist als die Empfangszeit eines einzelnen Zeichens.

Wenn die Daten jedoch langsam genug empfangen werden, können Sie sie auf die derzeitige Weise zeichenweise verarbeiten, und die Verwendung von DMA kann übertrieben sein.

Wie @Chris Stratton betonte, können Sie DMA auch einfach für die Einzelübertragung einstellen und lange genug warten, das Protokoll ändern oder ein anderes Signal als "Ende der Übertragung" verwenden - und dann den Frame im DMA-Puffer verarbeiten.

"Dann können Sie den RX-Interrupt verwenden, um Abfrageanrufe auszulösen." Das ist ein guter Weg, aber haben Sie jemals versucht, es so zu machen wie in der STM32F-Familie? Ich habe es versucht und es gibt ein Problem. Wenn ich den DMA-Modus aktiviere, indem ich DMAR in USART_CR3 schreibe, kommen die RX-Interrupts nicht. Im it's RefMan gibt es dazu einen kleinen Hinweis: "If DMA is used for Receive, don't enable the RXNEIE bit." Das heißt, verwenden Sie nicht die RX-Interrupts. Leider scheint es unmöglich zu sein.

Sie sollten DMA-Interrupts verwenden. Ich verwende STM32F02 mit STM32-STL-Peripheriebibliotheken, aber die Idee ist für F01 ziemlich gleich:

  • Initialisieren Sie Ihren UART
  • Initialisieren Sie DMA (vergessen Sie nicht, das CLK-Signal für das Peripheriegerät bereitzustellen, BEVOR Sie DMA_Init aufrufen!!)
  • Konfigurieren Sie NVIC- und DMA-Interrupts und aktivieren Sie DMA wie folgt:

 NVIC_InitTypeDef NVIC_InitStructure;
 //Enable DMA1 channel IRQ Channel
 //Note: maybe in your implementation you don't have DMAx_Streamy, look for channel/ stream configurations in your microcontroller manual

 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream1_IRQn; // This could be different in your implementation

 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
 NVIC_Init(&NVIC_InitStructure);

 // Enable DMA1 Channel Transfer Complete interrupt
 DMA_ITConfig(DMA1_Stream1, DMA_IT_TC, ENABLE);

 USART_DMACmd(ACCESSORY_UART, USART_DMAReq_Rx, ENABLE);
 DMA_Cmd(DMA1_Stream1, ENABLE);

Die Funktion USART_DMACmd bindet UART an DMA, und ich denke, das ist Ihre Antwort. Auf diese Weise kopiert der DMA jedes Mal, wenn ein RXNE-Ereignis ausgelöst wird, ein Byte in den von Ihnen in der Konfiguration bereitgestellten Zeiger, unterbricht jedoch NUR, wenn ein Ereignis „Transfer Complete“ (DMA_IT_TC) ausgelöst wird, und ruft die entsprechende DMA-Funktion gemäß dem konfigurierten Kanal auf / streamen.