Ich erzeuge eine komplexe Reihe von Impulsen auf einem STM32F103, im Wesentlichen wie im ST-App-Hinweis AN4776 Allzweck-Timer-Kochbuch , Abschnitt 5.3, beschrieben. Als kurze Zusammenfassung bedeutet das, dass ich den DMA-Burst-Modus des Timers verwende, um nach jedem Update-Ereignis des Timers neue Werte an ARR, RCR und CCR1 zu übertragen. Eine ausführlichere Beschreibung finden Sie am Ende der Frage, falls Sie mit der App-Notiz nicht vertraut sind.
Mein Problem hängt mit der DMA-Bandbreite zusammen: Die von mir erzeugten Impulse können im Prinzip beliebig eng beabstandet sein, einschließlich nur 1 Taktzyklus (der Timer hat einen Vorteiler von 1). Natürlich kann es der DMA in diesem Fall unmöglich rechtzeitig schaffen, die Daten für den nächsten Impuls zu übertragen, und es wird eine Störung in der Ausgabe geben. Da meine Anwendung kleine, seltene Zeitfehler tolerieren kann, verarbeite ich meine Impulse so vor, dass das kleinste Impulsintervall ein festes Minimum ist (ich verwende im Moment 96 Takte), um dem DMA eine Chance zu geben. Ich bekomme jedoch immer noch Störungen, die meiner Meinung nach auf den DMA zurückzuführen sind, und das Erhöhen der Mindestzeit selbst auf sehr große Zahlen scheint nicht zu helfen.
Der Schlüsselteil des vorherigen Satzes ist "... ich denke ...". Also würde ich gerne einen Weg finden, um sicher zu wissen, ob der DMA seine Übertragung verpasst hat oder nicht, vorzugsweise etwas, das ich im Code lassen kann, der immer läuft, so dass ich sogar sehr seltene Störungen finden werde.
Was ich mir bisher überlegt/ausprobiert habe ist:
Natürlich könnten die Störungen, die ich sehe, auf einen Fehler im Code zurückzuführen sein, der die Puffer generiert, die der DMA an die Timer sendet. Aber genau deshalb würde ich gerne sicher wissen, ob dem DMA einige Übertragungen fehlen oder nicht, also würde ich wissen, ob ich auf der Suche nach einem Fehler in meinem Code bin oder nicht. Der Code selbst besteht eine einigermaßen umfassende Reihe von Komponententests, sodass der Fehler subtil wäre, wenn er vorhanden wäre.
Also, irgendwelche Ideen, um zu überprüfen, ob mein Problem auf DMA-Fehlschläge zurückzuführen ist oder nicht?
Ich erzeuge eine Reihe von Impulsen, die jeweils durch die Länge der "Ein"-Phase (Impulsbreite) und die Zeit (in Takten) bis zum nächsten Impuls bestimmt werden. In Bezug auf Datenstrukturen
struct pulse {
/*
* These are an image of the timer registers.
* We don't use repeats but it must be there
* since the DMA transfers it anyway
*
* TODO: support other capture/compare channels than 1
*/
uint32_t length; //ARR
uint32_t repeats; //RCR, we don't use this
uint32_t pulsewidth; //CCR1
};
//Check that pulse is of the correct type for the DMA stream
static_assert(std::is_pod<pulse>::value, "pulse must be POD");
static_assert(sizeof(pulse) == 12, "pulse must not be packed");
pulse pulseArray[MAX_PULSES];
Dann erlaubt uns der Burst-Modus von TIM8 (und entsprechend TIM1), das folgende Schema einzurichten:
pulseArray
im zirkulären Modus zum TIM8_DMAR-Register "DMA-Adresse für vollständige Übertragung" bewegt (also muss ich ihn natürlich auf ähnliche zirkuläre Weise mit neuen Daten füllen).length
, repeats
, und pulsewidth
, und der Burst-Modus leitet diese zu den Registern TIM8_ARR, TIM8_RCR und TIM8_CCR1 . Da wir das Vorladen sowohl für den Timer selbst (TIM8_CR1 Bit 7) als auch für den Vergleichskanal (TIM8_CCMR1 Bit 3) aktiviert haben, werden die Daten dann in das Vorladeregister übertragen und bei der nächsten Aktualisierung (d. h. wenn der aktuelle Impuls abgeschlossen ist) aktiv )Abbildung 30 und 33 in der App-Notiz sind sehr aufschlussreich für das Verständnis des oben Gesagten.
Und jetzt kann ich das Problem etwas detaillierter beschreiben: Angenommen length
, es ist zum Beispiel 1. Dann die Anzahl der Taktzyklen, die dem DMA zur Verfügung stehen, um den nächsten Impuls zu übertragen (der aufgrund des Vorladens tatsächlich 2 Positionen im Puffer voraus ist registriert, aber das ist hier nicht wichtig) ist eins (vorausgesetzt, der Timer-Prescaler ist 1, was in diesem Fall der Fall ist). Offensichtlich ist es dem DMA unmöglich, 3 16-Bit-Wörter in einem Taktzyklus zu übertragen, und daher werden die Werte des vorherigen Zyklus wiederholt. Nennen wir das in Ermangelung eines besseren Begriffs einen "DMA-Fehler".
Andererseits muss es ein Minimum geben length
, so dass der DMA während jedes längeren Impulses Zeit hat, alle Daten zu übertragen. length
Leider hängt dieses Minimum von den genauen Bus-Timings, anderem DMA-Verkehr und deren Prioritäten ab, daher ist es wirklich schwierig, diese Länge mit Stift und Papier zu bestimmen.
Ich würde also gerne einen Weg finden, mit so viel Sicherheit wie möglich zu erkennen, dass kein "DMA-Fehlschlag" aufgetreten ist, damit ich mein Minimum feinabstimmen length
und gleichzeitig sicher sein kann, dass einige andere Störungen auftreten sehe nicht auf einen "DMA-Miss" zurück.
Sie verwenden DMA im Zirkularmodus; Wie bestimmen Sie die Aktualisierungszeit der Register? Da Sie nur 1 Puffer verwenden, gibt es kein so triviales Timing, das keine Störungen verursacht. Sie werden nur näher an die Störungsfreiheit herankommen, indem Sie die Genauigkeit des Timings erhöhen, aber niemals genau störungsfrei in Bezug auf die Wahrscheinlichkeit.
Auf stm32f4-Geräten gibt es eine einfach zu verwendende doppelte Pufferfunktion. Aber auf stm32f1-Geräten gibt es Elemente davon, um es manuell zu tun. Sie haben dort auf dem DMA-Peripheriegerät die Interrupt-Trigger für die halbe Übertragung und das Übertragungsende. Entwerfen Sie den Ringpuffer als zwei Sätze von Registern, tauschen Sie wie bei jedem Interrupt den Pufferzeiger aus, von dem angenommen wurde, dass er von DMA gelesen und von der CPU aktualisiert wird.
Wenn Sie eine komplexe Reihe sich überschneidender Timing-Probleme haben, ist es am besten, einen Hypervisor einzurichten, einen „Boss der Bosse“. Sie erstellen eine 4-Phasen-Master-Semaphore, die die Zählung von 0-3 endlos wiederholt.
Ich nenne sie Phase A, B, C, D. Sie fungieren als Enabler für die Ausführung bestimmter Threads. Linux und Android haben dies eingebaut. Ich habe es in LabView und MPLAB verwendet. Jedes komplexe Projekt hat eine 4-Phasen-Uhr zur Zeitmessung von Ereignissen. Eine Defacto-RTC.
Im schlimmsten Fall muss ein Teil des Codes warten, bis er an der Reihe ist, aber er hat keine Konflikte, wenn er ausgeführt wird.
Phasen:
A. Protokoll des vorherigen Laufs lesen, dann Bedingungsprüfungen einrichten und entscheidende Werte voreinstellen/zurücksetzen. Fenster öffnen, damit ISRs nur während Phase A ausgeführt werden.
B. Führen Sie als Nächstes den entscheidenden deterministischen Code aus. Lesen Sie alle Ergebnisse von ISRs. Dies bestimmt einiges von dem, was als Nächstes ausgeführt wird, abhängig von den verbrauchten Taktzyklen und der Priorität. Fehlerhandler werden zuerst ausgeführt.
C. Führen Sie den Hauptcode basierend auf den Ergebnissen der Phasen A und B aus. Dies umfasst das Wiederaufnehmen oder Starten von DMA-Burst-Modi, jetzt, wo Sie Zeitschlitze basierend auf der Laufzeit von Phase C haben. Schreiben Sie Code, damit er DMA-Flags prüft, bevor Phase C abläuft.
D. DMA-Übertragungen anhalten. Auf Fehler prüfen. Überprüfen Sie die „Schatten“-Timer. Prüfen Sie auf Überläufe oder ob es Zeit für einen kurzen DMA-Burst ist, um ein Paket abzuschließen. Führen Sie CRC und alle Fehler-Flags durch, jetzt, wo Sie ein Zeitfenster dafür haben. Hier finden Sie einen Fehler von nicht übereinstimmendem DMA im Vergleich zu einem Schattenzähler usw.
Hinterlassen Sie ein Protokoll oder einen numerischen Code für den Pass/Fail-Status und ist es in Ordnung, dass Phase B den normalen Pfad oder einen Fehlerhandler ausführt.
jms
Timo
0___________
Timo
pgvoorhees
Tony Stewart EE75