Warum nicht immer DMA zugunsten von Interrupts mit UART auf STM32 verwenden? [abgeschlossen]

Ich habe letzten Monat viel Zeit damit verbracht, UART (für MIDI) dazu zu bringen, mit einem STM (STM32F103C8T6) unter Verwendung von Interrupts zu arbeiten, ohne großen Erfolg.

Heute Abend mit DMA funktionierte es jedoch recht schnell.

Da, soweit ich gelesen habe, DMA schneller ist und die CPU entlastet, warum nicht immer DMA zugunsten von Interrupts verwenden? Zumal es auf dem STM32 einige Probleme zu geben scheint.

Ich verwende STM32CubeMx/HAL.

Nicht alle µC haben DMA. Wenn Sie es verwenden können, dann großartig, verwenden Sie es, wenn Sie die Geschwindigkeit benötigen .
@HarrySvensson Ich weiß nicht, ob ich die Geschwindigkeit wirklich brauche, aber DMA Ich habe es in ein paar Stunden zum Laufen gebracht, während ich mehrere Wochen lang Interrupts ausprobiert habe (freie Hobbyzeit). Ich dachte, es wäre am besten, es zuerst direkt zu versuchen (das funktionierte), dann Interrupts (funktionierte nicht gut), als DMA (unter Verwendung von Interrupts).
Warum nicht? Das ist entweder eine Meinungsfrage, eine Frage, um zu erraten, welcher mögliche technische Grund vorliegt, oder auf die gleiche Weise zu weit gefasst und daher keine Frage, die hierher gehört. Um nur ein zufälliges Beispiel zu nennen, DMA bedeutet mehr Latenz beim Abrufen der Daten, zumal Sie keinen wirklichen Nutzen daraus ziehen, wenn Sie nicht zulassen, dass mehrere Zeichen gesammelt werden. Oft mag das in Ordnung sein, manchmal nicht.
Wenn es Wochen gedauert hat, Interrupts zum Laufen zu bringen, liegt das daran, dass Sie die Aufgabe falsch angegangen sind. Es könnte durchaus länger dauern, DMA zum Laufen zu bringen - es ist eigentlich eine komplexere Aufgabe, so dass die scheinbare Leichtigkeit der komplexeren Aufgabe gegenüber der einfacheren vermutlich auf die Ressourcen zurückzuführen ist, die Sie für die jeweilige Anleitung verwendet haben, nicht auf den Mechanismus selbst.
@Michel Keijzers: Sie haben die Bitrate Ihrer Anwendung nie mitgeteilt, oder ich habe sie verpasst. Ich bin ziemlich überrascht, dass Interrupts bei Ihnen nicht funktioniert haben. Bei 72MHz und 115200 Baud hat man satte 5000 Takte pro Zeichen.
Gehen Sie niemals davon aus, dass dma die CPU freigibt, manchmal ja, die CPU läuft weiter, manchmal nein, der Prozessor ist eingefroren, um den Bus für die dma-Engine zu halten. Trivial, dies mit einer Arm-Implementierung zu tun, also kann ich nicht einfach sagen, dass alle Arme so und alle x86 so sind oder was auch immer, es ist nicht so einfach, Sie müssen immer das Systemdesign untersuchen und vielleicht ein bisschen hacken. Der Chip, den Sie haben, kann sehr wohl den Armkern freigeben, dies ist nur ein Kommentar zu dma. Was Ihre Frage angeht, macht es keinen Sinn, dass Sie nicht mithalten konnten, und dma + int ist wahrscheinlich die vollständige Lösung, wenn Sie nicht einfach abfragen können.
Da Sie eine HAL und/oder eine vom Hersteller bereitgestellte Bibliothek verwenden, liegt das Problem möglicherweise nicht im Chip, sondern in der bereitgestellten HAL oder Bibliothek. Haben Sie jeden Zentimeter der Bibliothek sorgfältig untersucht, um sicherzustellen, dass das Problem nicht im Code oder in einer Kombination aus Ihrem Code und deren und dem Compiler lag?
Beachten Sie, dass MIDI 31250 Baud ist, also kommt der Arm wahrscheinlich überhaupt nicht ins Schwitzen, wenn er damit umgeht.
@old_timer ... auch nicht, wenn ich jeweils 1 Byte empfange (dass das Einrichten des neuen DMA-Interrupts zu lange dauert?)
Interrupts sind an der seriellen STM32F-Schnittstelle ziemlich trivial. Warum posten Sie nicht eine Frage mit Ihrem Code, damit einige von uns versuchen können, herauszufinden, wo Sie falsch liegen? Es ist nie eine gute Idee, Code zu hacken, bis er funktioniert, ohne zu verstehen, was das zugrunde liegende Problem war.
@ Jon Völlig wahr ... Ich werde heute Abend (jetzt nicht zu Hause) sein. Nun, angesichts der vielen Posts über UART mit Interrupts (nicht DMA) auf STM ist es nicht wirklich trivial.
Meiner (nicht so) bescheidenen Meinung nach ist dies einer der Nachteile bei der Verwendung des schrecklichen, aufgeblähten Würfels. Schreiben Sie die Software von Grund auf neu, Sie werden genau lernen, wie der UART funktioniert (weil Sie müssen), Sie werden die Peripherie viel besser verstehen und auf lange Sicht wird es Ihnen so viel Zeit sparen.
@Jon ... vielleicht warte ich noch ein bisschen ... zuerst möchte ich eine funktionierende Version mit DMA haben, danach werde ich auf Interrupts zurückgreifen, um zu sehen, ob es funktioniert oder nicht ... dann werde ich die Frage stellen (I habe sehr wenig Zeit, tut mir leid).
@DiBosco stimmt ... aber ich werde zuerst einige Überprüfungen mit dem DMA fortsetzen (da es zu funktionieren scheint), dann zu Interrupts zurückkehren und dann wahrscheinlich den Weg auf niedriger Ebene verwenden.

Antworten (6)

Während DMA die CPU entlastet und somit die Latenz anderer Interrupt-gesteuerter Anwendungen, die auf demselben Kern laufen, verringern kann, sind damit Kosten verbunden:

  • Es gibt nur eine begrenzte Anzahl von DMA-Kanälen und es gibt Beschränkungen, wie diese Kanäle mit den verschiedenen Peripheriegeräten interagieren können. Ein anderes Peripheriegerät auf demselben Kanal ist möglicherweise besser für die DMA-Nutzung geeignet.

    Wenn Sie beispielsweise alle 5 ms eine Massen-I2C-Übertragung haben, scheint dies ein besserer Kandidat für DMA zu sein als ein gelegentlicher Debug-Befehl, der auf UART2 eintrifft.

  • Das Einrichten und Verwalten von DMA ist ein Kostenfaktor für sich. (Normalerweise wird das Einrichten von DMA als komplexer angesehen als das Einrichten einer normalen Interrupt-gesteuerten Übertragung pro Zeichen, aufgrund der Speicherverwaltung, mehr beteiligter Peripheriegeräte, DMA, das Interrupts selbst verwendet, und der Möglichkeit, dass Sie die ersten paar Zeichen außerhalb von DMA analysieren müssen wie auch immer, siehe unten.)

  • DMA kann zusätzliche Energie verbrauchen , da es noch eine weitere Domäne des Kerns ist, die getaktet werden muss. Andererseits können Sie die CPU während der DMA-Übertragung aussetzen, wenn der Kern dies unterstützt.

  • Für DMA sind Speicherpuffer erforderlich (es sei denn, Sie führen Peripherie-zu-Peripherie-DMA durch), sodass einige Speicherkosten damit verbunden sind.

    (Die Speicherkosten können auch bei der Verwendung von zeichenweisen Interrupts anfallen, aber sie können auch viel kleiner sein oder ganz verschwinden, wenn die Nachrichten sofort innerhalb des Interrupts interpretiert werden.)

  • DMA erzeugt eine Latenz , da die CPU nur benachrichtigt wird, wenn die Übertragung abgeschlossen/halb abgeschlossen ist (siehe die anderen Antworten).

  • Außer beim Streamen von Daten in/aus einem Ringpuffer müssen Sie im Voraus wissen, wie viele Daten Sie empfangen/senden werden.

    • Dies kann bedeuten, dass die ersten Zeichen einer Nachricht mit zeichenweisen Interrupts verarbeitet werden müssen: Wenn Sie beispielsweise mit einem XBee verbunden sind, lesen Sie zuerst den Pakettyp und die Größe und lösen dann eine DMA-Übertragung in einen zugewiesenen Puffer aus.

    • Bei anderen Protokollen ist dies möglicherweise überhaupt nicht möglich, wenn sie nur End-of-Message-Trennzeichen verwenden: zum Beispiel textbasierte Protokolle, die '\n'als Trennzeichen verwenden. (Es sei denn, das DMA-Peripheriegerät unterstützt die Übereinstimmung mit einem Zeichen.)

Wie Sie sehen können, gibt es hier viele Kompromisse zu berücksichtigen. Einige beziehen sich auf Hardwarebeschränkungen (Anzahl der Kanäle, Konflikte mit anderen Peripheriegeräten, übereinstimmende Zeichen), andere basieren auf dem verwendeten Protokoll (Trennzeichen, bekannte Länge, Speicherpuffer).

Um einige anekdotische Beweise hinzuzufügen, ich habe all diese Kompromisse in einem Hobbyprojekt erlebt, bei dem viele verschiedene Peripheriegeräte mit sehr unterschiedlichen Protokollen verwendet wurden. Es gab einige Kompromisse zu machen, hauptsächlich basierend auf der Frage „Wie viele Daten übertrage ich und wie oft werde ich das tun?“. Dies gibt Ihnen im Wesentlichen eine grobe Schätzung der Auswirkungen einer einfachen Interrupt-gesteuerten Übertragung auf die CPU. Ich habe daher der oben erwähnten I2C-Übertragung alle 5 ms Vorrang vor der UART-Übertragung alle paar Sekunden gegeben, die denselben DMA-Kanal verwendet. Eine andere UART-Übertragung, die häufiger und mit mehr Daten stattfindet, hat dagegen Vorrang vor einer anderen I2C-Übertragung, die seltener stattfindet. Es sind alles Kompromisse.

Natürlich hat die Verwendung von DMA auch Vorteile, aber das ist nicht das, wonach Sie gefragt haben.

Danke für deine ausführliche Antwort. MIDI wird der kritischste Teil sein, also denke ich, dass DMA dafür geeignet ist (obwohl die Geschwindigkeit niedrig ist: 31250 Baud). Ich habe genug DMA-Kanäle, später werde ich einen anderen STM32 verwenden, wenn ich 4 USARTs verwende. Ich muss die CPU nicht aussetzen, da sie über 5 V USB-Strom verfügt, und ich muss die Verarbeitung zwischen den Nachrichten durchführen (um die Nachrichten in der Hauptschleife zu verarbeiten). Ich habe einen 256-Byte-Lese- und 256-Byte-Sendepuffer. Bei Bedarf kann ich es später erhöhen. Der STM32f103c8t6 hat 20 KB RAM, der eventuelle STM, den ich verwenden werde, hat 192 KB.
Und Sie geben mir eine sehr gute Idee, wie ich mich verbessern kann. Bisher lese ich immer 1 Byte und prüfe laufend, wann eine komplette (MIDI-)Nachricht empfangen wird. Aber ich kann das erste Byte lesen, und abhängig davon ist meistens die Größe bekannt und kann nach dem Rest fragen. Das hat mich noch einen kleinen Puffer gekostet, aber das ist ok.
Das Lesen einzelner Bytes mit DMA ist sehr ineffizient. Für geringere Latenz und höhere Effizienz wäre es vorteilhaft, Interrupts pro Zeichen zu verwenden, bis Sie die Größe kennen, und dann auf DMA umzuschalten.
Nun, ich hatte viele Probleme mit Interrupts (ohne DMA), ich denke, ich werde einen 1-Byte-DMA-Empfang verwenden, und danach weiß ich, wie viele Bytes ich erwarten werde, und mache eine DMA-Anfrage, um mehr zu bekommen.
Das ist wahrscheinlich ein Fehler - Sie sollten Ihren einfachen Interrupt-Code ohne DMA korrigieren.

Die Verwendung von DMA bedeutet normalerweise, dass Sie nicht mehr bei jedem Zeichen einen Interrupt nehmen, sondern erst, nachdem ein "Puffer voll" von Zeichen empfangen (oder gesendet) wurde. Dies erhöht die Latenz bei der Verarbeitung dieser Zeichen – das erste Zeichen wird erst verarbeitet, nachdem das letzte Zeichen im Puffer empfangen wurde.

Diese Latenz kann eine schlechte Sache sein, insbesondere in einer latenzempfindlichen Anwendung wie MIDI, wo ein paar ms hier und da zu ernsthaften Spielbarkeitsproblemen bei Live-Auftritten führen können.

Ich empfange jeweils 1 Byte (also einen 'DMA'-Puffer von 1 Byte) und speichere es nach jedem DMA-Rückruf dieses einen Bytes in einem Ringpuffer, den ich manuell handhabe. In meiner Hauptschleife beabsichtige ich, nach vollständigen MIDI-Nachrichten zu suchen und diese zu verarbeiten.
DMA wird normalerweise verwendet, um mehrere Bytes zu erhalten und nur zu unterbrechen, wenn sie alle empfangen wurden. Das Unterbrechen nach nur einem Byte ist normal, wenn DMA nicht verwendet wird, daher frage ich mich: Was ist der Sinn der zusätzlichen Komplikation bei der Verwendung von DMA dafür?
@MichelKeijzers Dann ist das, was Sie tun, ziemlich genau dasselbe, was Sie in reinen Interrupt-gesteuerten Implementierungen tun würden. Daher hat die Verwendung von DMA in diesem Fall keinen Vorteil, und Ihr ursprüngliches Problem wird wahrscheinlich nicht durch DMA gelöst, sondern durch Ihr Umschreiben Ihres (ISR-, Setup-) Codes.
@JimmyB ... danke ... aber aufgrund der Antwort von Jonas unten werde ich eine Verbesserung vornehmen, um so viele Bytes zu lesen, da die Nachricht lang ist. Ich weiß das, nachdem ich das erste Byte erhalten habe (in den meisten Fällen). Dann wird es mehr Vorteile bringen, DMA über Interrupts zu verwenden.

DMA ist kein Ersatz für Interrupts – sie werden normalerweise zusammen verwendet! Wenn Sie beispielsweise DMA verwenden, um Daten über einen UART zu senden, benötigen Sie immer noch einen Interrupt, der Ihnen mitteilt, wann der Sendevorgang abgeschlossen ist.

Stimmt in der Tat, vielleicht ist gerade auf dem STM32 der (reine Nicht-DMA-) Interrupt-Mechanismus im Vergleich zu direktem DMA etwas ungeschickt.
@duskwuff Nicht wirklich; Sie können abfragen, wann der DMA fertig ist, und das möchten Sie vielleicht auch, denn einer der Hauptgründe für die Verwendung von DMA ist, dass Sie sich nicht um die serielle Schnittstelle kümmern müssen, bis Ihr Programm in einem Zustand ist, in dem es auf die empfangenen Daten reagieren kann Daten. Oder Sie können für ausgehenden DMA einfach abfragen, ob es möglich ist, mehr zum Sendepuffer hinzuzufügen.
@MichelKeijzers: IDK der spezifische Chip, aber normalerweise ist die Alternative zu DMA nicht buchstäblich Interrupts, sondern programmierte E / A (wobei Sie CPU-Anweisungen verwenden, um Daten von / in ein E / A-Register zu lesen / schreiben). In einem Interrupt-Handler würden Sie normalerweise einen Lesevorgang durchführen und dann vielleicht einen weiteren, falls ein Zeichen hereinkam, während Sie den ersten lasen, insbesondere wenn dies keinen weiteren Interrupt auslöst. Oder lesen, bis ein interner Puffer leer war, falls es einen solchen Puffer gibt. Offensichtlich benötigen Sie mehr Interrupts für PIO und richten sie anders ein.
@ChrisStratton Guter Punkt ... bisher habe ich nicht überprüft, ob eine Übertragung möglich ist. Ich übertrage nur etwas und überprüfe nicht, ob es in Ordnung ist. Wahrscheinlich, wenn nicht, versuche ich es später noch einmal.
@PeterCordes Es scheint, dass der STM32 genug Interrupts für DMA hat und ich lese jedes Mal nur 1 Byte. Selbst der einfachste STM32 (F103c8t6) hat genügend DMA-Ports/Interrupts zur Verfügung.
Das Lesen eines Bytes mit DMA hat einen begrenzten Nutzen; wahrscheinlich für den Fall reserviert, in dem das Peripheriegerät kein Halteregister hat (im Gegensatz zu den meisten modernen MCUs) und das empfangene Byte daher sofort extrahiert werden muss, um Platz für den Rest zu schaffen, der Prozessor jedoch aus irgendeinem Grund keinen Interrupt bedienen kann. Beachten Sie, dass DMA das Timing des Hauptprogramms immer noch verzerren kann, da es die Busarbitrierung erzwingt, wenn die CPU auch auf RAM oder denselben Peripheriebus zugreifen muss - wenn auch nicht so stark wie die Bedienung eines Interrupts.

Die Verwendung von DMA führt zu einigen interessanten Fragen und Herausforderungen, die über alle anderen Überlegungen zur Verwendung von UART-Peripheriegeräten hinausgehen. Ich gebe Ihnen ein paar Beispiele: Angenommen, Ihr uC sitzt mit anderen Geräten auf einem RS485-Bus (oder was auch immer). Es gibt viele Nachrichten im Bus, einige sind für Ihr uC bestimmt, andere nicht. Nehmen Sie außerdem an, dass diese Busnachbarn alle ein unterschiedliches Datenprotokoll sprechen, was impliziert, dass die Nachrichtenlängen unterschiedlich sind.

Einige Fragen, die nur bei der Verwendung von DMA auftauchen, sind:

  • Wann unterbreche ich?
    • DMAs unterbrechen nur dann wirklich gerne, wenn sie eine voreingestellte Datenmenge übertragen haben.
    • Was tun Sie, wenn Sie nie genug Daten erhalten, um einen DMA-Interrupt auszulösen?
  • Was passiert, wenn Sie nur eine Teilnachricht erhalten, wenn der DMA unterbricht?
  • Wie sehen deine RX-Puffer aus? Sind sie linear oder kreisförmig?
    • DMA kann ein widerspenstiger Ringpufferteilnehmer in dem Sinne sein, dass er nur der Adressgrenze gehorcht, aber kein Problem hat, an den anderen Zeigern im Ringpuffersystem vorbeizublasen.

Jedenfalls nur ein Denkanstoß.

Danke für diese Überlegungen. Momentan erhalte ich immer 1 Byte und speichere es in einem Ringpuffer, da ja meine Nachrichten (MIDI) unterschiedlich lang sein können und ich nicht weiß, was ich als nächstes bekomme. In meiner Hauptschleife suche ich nach vollständigen Nachrichten, um sie zu verarbeiten (und wenn sie vollständig sind, entferne ich sie aus dem Ringpuffer). So bekomme ich immer genug Daten (es sei denn, ich würde Bytes verpassen, das muss ich überprüfen). Mein RX-Puffer ist nur 1 Byte groß, aber ich kopiere ihn in einen Ring-/Kreispuffer. Ich habe nicht überprüft, ob es voll ist (muss hinzugefügt werden).
He, keine Sorge. Ich bin sicher, Ihre Bewerbung wird gut programmiert sein. Wie andere bereits erwähnt haben, ist DMA großartig, aber es ist nicht alles kostenlos. Es führt zusätzliche Überlegungen in das System ein, die nicht existieren, wenn Sie davonkommen, ohne es zu verwenden.
naja ich hoffe ich bin noch anfänger.

Auf der Empfangsseite (soweit ich mich erinnere) endet DMA entweder bei einer Zeichenübereinstimmung oder bei der Endzählung. Einige Protokolle und viele interaktive Anwendungen passen nicht so einfach in dieses Modell, und Sie müssen die Dinge wirklich Zeichen für Zeichen handhaben. Die DMA-Techniken können auch spröde sein, wenn die Kommunikationsverbindung unzuverlässig ist. Der Verlust eines einzigen Zeichens im Stream kann Ihre DMA-Zustandsmaschine leicht durcheinander bringen.

Ich empfange tatsächlich Byte für Byte und kopiere es manuell in einen Ringpuffer, um es später zu verarbeiten.

Ich habe den STM32CubeMx/HAL jetzt in einigen Projekten verwendet und festgestellt, dass die von ihm generierte UART-Handhabungssoftware auf der Empfangsseite eindeutige Mängel aufweist.

Beim Senden möchten Sie normalerweise einen Datenblock oder eine Textzeile senden. In diesem Fall wissen Sie im Voraus, wie lange die Datenübertragung dauert, und daher ist die Verwendung des DMA eine naheliegende Lösung. Sie erhalten einen Interrupt, sobald die Übertragung abgeschlossen ist, und können die UART TX Complete Callback-Funktion verwenden, um Ihrem Hauptcode anzuzeigen, dass die Übertragung abgeschlossen ist, und Sie können einen weiteren Datenblock senden.

Wenn es um den Datenempfang geht, setzen die von ST bereitgestellten Funktionen alle voraus, dass Sie wissen, wie viele Zeichen das sendende Gerät Ihnen geben wird, bevor es mit dem Senden beginnt. Normalerweise ist dies nicht bekannt. Die Interrupt-Funktion legt die empfangenen Daten in einen Puffer und zeigt nur an, dass Daten verfügbar sind, wenn die vordefinierte Anzahl von Zeichen empfangen wurde. Wenn Sie versuchen, die DMA- oder Interrupt-Funktion zu verwenden, um Daten zu empfangen, indem Sie sequentielle Einzelzeichenübertragungen einrichten, bedeutet die Einrichtungszeit für jede davon, dass Sie Zeichen bei etwas anderem als den langsamsten Datenraten verlieren (der Baudrate, die Sie verwenden beginnen, Daten zu verlieren, hängt von Ihrer Prozessortaktgeschwindigkeit ab) und wird den Prozessor übermäßig belasten, sodass keine Befehlszyklen für andere Verarbeitungen übrig bleiben

Um dies zu umgehen, habe ich meine eigene Interrupt-Handler-Funktion geschrieben, die die Daten in einem kleinen lokalen Ringpuffer speichert und einen Zähler setzt, der vom Hauptcode gelesen wird (ein RTOS-Zählsemaphor), um anzuzeigen, dass empfangene Daten bereit sind. Der Hauptcode kann dann nach Belieben die Daten aus diesem Puffer sammeln, es spielt keine Rolle, ob es eine gewisse Verzögerung beim Sammeln der Daten gibt, vorausgesetzt, dass der lokale Puffer nicht überläuft, bevor die Daten gesammelt werden.

Ich mache es genau so (glaube ich). Ich lese jeweils 1 Byte und speichere es in einem zyklischen Puffer, und ich beabsichtige, in der Hauptschleife nach vollständigen Nachrichten zu suchen. Kann aber noch etwas gesteigert werden.
Glaubst du, ich könnte auf das Problem stoßen, dass jedes Mal, wenn ich den DMA einrichte, mein Prozessor überlastet wird/Zeichen bei 31.250 Baud fehlen?
Solange Sie den DMA so einrichten, dass er mehrere Zeichen gleichzeitig überträgt, ist dies kein Problem. Ich habe 4 UARTs mit 115200 und höher und I2C mit DMA ohne Probleme. Die UART-Übertragungen sind alle ~20 Bytes oder länger. Das Problem war die Verwendung von DMA für den Empfang auf dem UART (L4-Prozessor bei 80 MHz, 9600 Baud).
Derzeit setze ich es auf jeweils 1 Byte, aber ich kann es verbessern (indem ich das erste Byte mache und dann prüfe, wie viele weitere Bytes benötigt werden).