Teil 2: Verwenden der UDB-PSoC-Controller von Cypress, um die Anzahl der Interrupts in einem 3D-Drucker zu reduzieren



Beim letzten Mal haben wir die Möglichkeit in Betracht gezogen, Impulse fĂŒr Schrittmotoren zu erzeugen, die teilweise aus der Software auf Firmware-Ebene entfernt wurden. Im Falle eines vollstĂ€ndigen Erfolgs verspricht dies, dass keine Interrupts verarbeitet werden mĂŒssen, die mit einer Frequenz von bis zu 40 kHz ankommen. Diese Option weist jedoch eine Reihe offensichtlicher MĂ€ngel auf. Erstens werden Beschleunigungen dort nicht unterstĂŒtzt. Zweitens betrĂ€gt die GranularitĂ€t der zulĂ€ssigen Schrittfrequenzen in dieser Lösung Hunderte von Hertz (zum Beispiel ist es möglich, Frequenzen von 40.000 Hz und 39966 Hz zu erzeugen, aber es ist unmöglich, Frequenzen mit einer GrĂ¶ĂŸe zwischen diesen beiden Werten zu erzeugen).

Beschleunigungsimplementierung


Ist es möglich, die angegebenen Nachteile mit denselben UDB-Tools zu beseitigen, ohne das System zu komplizieren? Lass es uns richtig machen. Beginnen wir mit dem Schwierigsten - mit Beschleunigungen. Beschleunigungen werden am Anfang und Ende des Pfades hinzugefĂŒgt. Erstens, wenn Hochfrequenzimpulse sofort an den Schrittmotor angelegt werden, benötigt er einen grĂ¶ĂŸeren Strom, um den Betrieb zu starten. Ein hoher zulĂ€ssiger Strom ist ErwĂ€rmung und Rauschen, daher ist es besser, ihn zu begrenzen. Aber dann kann der Motor beim Start Schritte ĂŒberspringen. Es ist also besser, den Motor sanft zu beschleunigen. Zweitens, wenn ein schwerer Kopf abrupt stoppt, erfĂ€hrt er Transienten, die mit TrĂ€gheit verbunden sind. Wellen sind auf Plastik sichtbar. Daher ist es reibungslos notwendig, den Kopf nicht nur zu zerstreuen, sondern auch anzuhalten. Klassischerweise wird ein Diagramm der Motordrehzahl als Trapez dargestellt. Hier ist ein Fragment aus dem Quellcode der Marlin-Firmware:



Ich werde nicht einmal versuchen herauszufinden, ob es möglich ist, dies mit UDB zu implementieren. Dies liegt an der Tatsache, dass jetzt eine andere Art der Beschleunigung in Mode ist: nicht trapezförmig, sondern S-Kurve. Ihr Zeitplan sieht folgendermaßen aus:



Dies ist definitiv nicht fĂŒr UDB. Aufgeben? Überhaupt nicht! Ich habe bereits festgestellt, dass UDB keine Hardwareschnittstelle implementiert, sondern lediglich die Übertragung eines Teils des Codes von der Software auf die Firmware-Ebene ermöglicht. Lassen Sie das Profil den Zentralprozessor berechnen, und die Bildung von Schrittimpulsen fĂŒhrt immer noch UDB durch. Der Zentralprozessor hat viel Zeit fĂŒr Berechnungen. Die Aufgabe, hĂ€ufige Unterbrechungen zu vermeiden, wird weiterhin recht elegant gelöst, und niemand hat geplant, den Prozess vollstĂ€ndig auf Firmware-Ebene zu bringen.

NatĂŒrlich muss das Profil im Speicher vorbereitet werden, und UDB nimmt mit DMA Daten von dort auf. Aber wie viel Speicher wird benötigt? Ein Millimeter benötigt 200 Schritte. Jetzt mit 24-Bit-Codierung sind dies 600 Bytes pro 1 mm Kopfbewegung! Erinnern Sie sich noch einmal an nicht so hĂ€ufige, aber immer noch stĂ€ndige Unterbrechungen, um alles in Fragmenten zu ĂŒbertragen? Nicht wirklich! Tatsache ist, dass der DMA-Mechanismus von PSoC auf Deskriptoren basiert. Nachdem die Aufgabe von einem Deskriptor ausgefĂŒhrt wurde, fĂ€hrt der DMA-Controller mit dem nĂ€chsten fort. Entlang der Kette können Sie also viele Deskriptoren verwenden. Wir veranschaulichen dies anhand einer Zeichnung aus der offiziellen Dokumentation:



TatsÀchlich kann dieser Mechanismus auch verwendet werden, indem eine Kette von drei Deskriptoren erstellt wird:

Nein, nein.ErklÀrung
1Vom Speicher zum FIFO mit Adressinkrement. Zeigt einen Abschnitt mit einem Beschleunigungsprofil an.
2Vom Speicher zum FIFO ohne Adressinkrement. Sendet fĂŒr konstante Geschwindigkeit die ganze Zeit an dasselbe Wort im Speicher.
3Vom Speicher zum FIFO mit Adressinkrement. Zeigt einen Abschnitt mit einem Bremsprofil an.

Es stellt sich heraus, dass der Hauptpfad in Schritt 2 beschrieben wird und dort physikalisch dasselbe Wort verwendet wird, das die konstante Geschwindigkeit festlegt. Der Speicherverbrauch ist nicht groß. In der RealitĂ€t kann der zweite Deskriptor physikalisch durch zwei oder drei Deskriptoren dargestellt werden. Dies liegt an der Tatsache, dass die maximale PumplĂ€nge laut TRM 64 Kilobyte betragen kann (die Änderung wird geringer sein). Das sind 32.767 Wörter. Das bei 200 Schritten pro Millimeter entspricht einem Weg von 163 Millimetern. AbhĂ€ngig von der maximalen Entfernung, die der Motor gleichzeitig fahren kann, mĂŒssen Sie möglicherweise ein Segment aus zwei oder drei Teilen erstellen.

Um Speicherplatz (und die Kosten fĂŒr UDB-Blöcke) zu sparen, schlage ich dennoch vor, 24-Bit-DatapPath-Blöcke aufzugeben und auf wirtschaftlichere 16-Bit-Blöcke umzusteigen.

Also. Der erste Vorschlag zur Überarbeitung.

Im Speicher werden Arrays vorbereitet, die die Dauer der Schritte codieren. Ferner gehen diese Informationen unter Verwendung von DMA an UDB. Der geradlinige Abschnitt wird von einem Array aus einem Element codiert. Der DMA-Block erhöht die Adresse nicht und wĂ€hlt immer das gleiche Element aus. Beschleunigungs-, geradlinige und Bremsabschnitte werden ĂŒber die im DMA-Controller verfĂŒgbaren Mittel verbunden.

Feineinstellung des Mitteltonbereichs


Nun werden wir uns ĂŒberlegen, wie das Problem der FrequenzgranularitĂ€t ĂŒberwunden werden kann. NatĂŒrlich kann es nicht genau eingestellt werden. TatsĂ€chlich kann die ursprĂŒngliche "Firmware" dies jedoch auch nicht. Stattdessen verwenden sie den Bresenham-Algorithmus. Einige Schritte werden um eine Maßnahme verzögert. Infolgedessen wird die durchschnittliche Frequenz zwischen einem kleineren und einem grĂ¶ĂŸeren Wert dazwischen. Durch Anpassen des VerhĂ€ltnisses von regulĂ€ren und lĂ€ngeren Perioden können Sie die durchschnittliche Frequenz problemlos Ă€ndern. Wenn unsere Geschwindigkeit jetzt nicht ĂŒber das Datenregister eingestellt, sondern ĂŒber FIFO ĂŒbertragen wird und die Anzahl der Impulse im Allgemeinen ĂŒber die Anzahl der ĂŒber DMA ĂŒbertragenen Wörter eingestellt wird, werden beide Datenregister in UDB freigegeben. ZusĂ€tzlich wird eine der Batterien freigegeben, die die Anzahl der Impulse zĂ€hlt. Hier werden wir eine bestimmte PWM darauf aufbauen.

In der Regel vergleichen ALUs Register mit demselben Index und weisen sie zu. Wenn ein Register einen Index von 0 und das andere eine 1 hat, kann nicht jede Version der Operation implementiert werden. Aber ich habe es geschafft, den Solitaire aus den Registern zusammenzustellen, unter denen PWM durchgefĂŒhrt werden kann. Es stellte sich heraus, wie in der Abbildung gezeigt.



Wenn die Bedingung A0 <D1 erfĂŒllt ist, fĂŒgen wir der gegebenen PulslĂ€nge einen zusĂ€tzlichen Schlag hinzu. Wenn die Bedingung nicht erfĂŒllt ist, werden wir nicht.

Kugelpferd unter normalen Bedingungen


Daher beginnen wir, den entwickelten Block fĂŒr UDB unter BerĂŒcksichtigung der neuen Architektur zu modifizieren. Ersetzen Sie die Datapath-Bittiefe:



Wir werden viel mehr AusgÀnge von Datapath brauchen als beim letzten Mal.



Wenn Sie darauf doppelklicken, sehen Sie die Details:



Es gibt mehr Ziffern fĂŒr die Statusvariable. Vergessen Sie nicht, die Ă€ltere zu verbinden !!! In der alten Version gab es eine Konstante 0.



Das Übergangsdiagramm des Automaten, das ich erhalten habe, lautet wie folgt:



Wir befinden uns im Ruhezustand, wĂ€hrend FIFO1 leer ist. Die Arbeit mit FIFO1 und nicht mit FIFO0 ist ĂŒbrigens das Ergebnis der Bildung von Solitaire. Das Register A0 wird verwendet, um PWM zu implementieren, so dass die Impulsbreite durch das Register A1 bestimmt wird. Und ich kann es nur von FIFO1 herunterladen (vielleicht gibt es andere geheime Methoden, aber sie sind mir nicht bekannt). Daher lĂ€dt DMA Daten genau in FIFO1 hoch, und genau der Status "Nicht leer" fĂŒr FIFO1 verlĂ€sst den Ruhezustand.

ALU im IDLE- Status macht das Register A0 ungĂŒltig:



Dies ist notwendig, damit zu Beginn des PWM-Betriebs die Arbeit immer von vorne beginnt.
Aber die Daten kamen in den FIFO. Der Computer wechselt in den LoadData- Status:



In diesem Zustand lĂ€dt die ALU das nĂ€chste Wort vom FIFO in das Register A1. Um keine unnötigen ZustĂ€nde zu erzeugen, wird der Wert des ZĂ€hlers A0, der fĂŒr die Arbeit mit PWM verwendet wird, erhöht:



Wenn der ZÀhler A0 den Wert D0 noch nicht erreicht hat ( dh die Bedingung A0 <D0 wird ausgelöst, wobei das Flag NoNeedReloadA0 gespannt wird ), gehen wir zum Zustand Eins . Andernfalls lautet der Status ClearA0 .

Im ClearA0- Zustand setzt die ALU einfach den Wert von A0 auf Null und startet einen neuen PWM-Zyklus:



Danach geht die Maschine auch in einen Zustand ĂŒber, nur einen Schlag spĂ€ter.

Man kennt uns aus der alten Version der Maschine. Die darin enthaltene ALU fĂŒhrt keine Funktionen aus.

Und so - in diesem Zustand wird eine Einheit am Ausgang von Out_Step erzeugt (hier hat der Optimierer besser funktioniert, wenn die Einheit durch die Bedingung erzeugt wird, dies wurde empirisch erkannt).



Wir befinden uns in diesem Zustand, bis der uns bereits bekannte Sieben-Bit-ZĂ€hler auf Null zurĂŒckgesetzt wird. Aber wenn wir frĂŒher auf einem Weg aus diesem Zustand herausgekommen sind, kann es jetzt zwei Wege geben: direkt und verzögert im Takt.



Wir werden in den ExtraTick-Status versetzt, wenn das AddCycle- Flag gesetzt ist , das zugewiesen ist, um die Bedingung A0 <D1 zu erfĂŒllen. In diesem Zustand fĂŒhrt die ALU keine nĂŒtzlichen Aktionen aus. Es ist nur so, dass der Zyklus 1 Schlag lĂ€nger dauert. Außerdem konvergieren alle Pfade im Verzögerungszustand .

Dieser Zustand misst die Dauer des Impulses. Das Register A1 (geladen, wÀhrend es sich noch im Ladezustand befindet ) wird reduziert, bis es Null erreicht.



AbhÀngig davon, ob zusÀtzliche Daten im FIFO vorhanden sind oder nicht, wechselt die Maschine zum Abrufen des nÀchsten Abschnitts im Ladezustand oder im Ruhezustand . Schauen wir uns das nicht in der Abbildung an (es gibt lange Pfeile, alles wird klein sein), sondern in Form einer Tabelle, indem Sie auf den Verzögerungsstatus doppelklicken:



Beendet jetzt UDB. Ich habe das Flag "Im Ruhezustand" in einen asynchronen Vergleich umgewandelt (in der vorherigen Version gab es einen Trigger, der in verschiedenen ZustĂ€nden gespannt und zurĂŒckgesetzt wurde), da der Optimierer dafĂŒr das beste Ergebnis zeigte. Außerdem wurde das Hungry- Flag hinzugefĂŒgt, das der DMA-Einheit signalisiert, dass sie bereit ist, Daten zu empfangen. Es ist auf der Flagge „FIFO1 ist nicht ĂŒberfĂŒllt“ aufgewickelt. Da es nicht ĂŒberfĂŒllt ist, kann DMA dort ein anderes Datenwort laden.



Auf der automatischen Seite - das war's.

FĂŒgen Sie dem Hauptprojektdiagramm DMA-Blöcke hinzu. Vorerst habe ich angefangen, DMA-Beendigungsflags zu unterbrechen, aber nicht die Tatsache, dass dies korrekt ist. Wenn der Prozess des direkten Zugriffs auf den Speicher abgeschlossen ist, können Sie einen neuen Prozess starten, der sich auf dasselbe Segment bezieht. Sie können jedoch keine Informationen ĂŒber das neue Segment ausfĂŒllen. FIFO hat noch drei bis vier Elemente. Zu diesem Zeitpunkt ist es immer noch unmöglich, die Register D0 und D1 des Blocks basierend auf UDB neu zu programmieren, sie werden immer noch fĂŒr den Betrieb benötigt. Daher ist es möglich, dass Interrupts basierend auf den Out_Idle- AusgĂ€ngen spĂ€ter hinzugefĂŒgt werden. Diese KĂŒche wird sich jedoch nicht mehr auf die UDB-Blockprogrammierung beziehen, daher werden wir sie nur nebenbei erwĂ€hnen.



Software-Experimente


Da jetzt nicht alles bekannt ist, werden wir keine speziellen Funktionen schreiben. Alle Kontrollen werden "Auf der Stirn" durchgefĂŒhrt. Basierend auf erfolgreichen Experimenten können dann API-Funktionen geschrieben werden. Also. Wir machen die main () - Funktion minimal. Es richtet einfach das System ein und ruft den ausgewĂ€hlten Test auf.

int main(void) { CyGlobalIntEnable; /* Enable global interrupts. */ // isr_1_StartEx(StepperFinished); StepperController_X_Start(); StepperController_Y_Start(); StepperController_Z_Start(); StepperController_E0_Start(); StepperController_E1_Start(); // TestShortSteps(); TestWithPacking (); for(;;) { } 

Versuchen wir, ein Paket von Impulsen zu senden, indem wir eine Funktion aufrufen und die Tatsache ĂŒberprĂŒfen, dass ein zusĂ€tzlicher Impuls eingefĂŒgt wird. Der Funktionsaufruf ist einfach:

 TestShortSteps(); 

Aber der Körper bedarf einer ErklÀrung.
Ich werde zuerst die ganze Funktion geben
 void TestShortSteps() { //   ,   //      //   ,  DMA    !!! //    ,   !!! StepperController_X_SingleVibrator_WritePeriod (6); //     //    —   CY_SET_REG16(StepperController_X_Datapath_1_D0_PTR, 4); CY_SET_REG16(StepperController_X_Datapath_1_D1_PTR, 2); //         . //         static const uint16 steps[] = { 0x0001,0x0001,0x0001,0x0001,0x0001,0x0001,0x0001,0x0001,0x0001,0x0001, 0x0001,0x0001,0x0001,0x0001,0x0001,0x0001,0x0001,0x0001,0x0001,0x0001 }; //  DMA  ,      uint8 channel = DMA_X_DmaInitialize (sizeof(steps[0]),1,HI16(steps),HI16(StepperController_X_Datapath_1_F1_PTR)); CyDmaChRoundRobin (channel,true); //       ,       uint8 td = CyDmaTdAllocate(); //       .  ,    . CyDmaTdSetConfiguration(td, sizeof(steps), CY_DMA_DISABLE_TD, TD_INC_SRC_ADR | TD_AUTO_EXEC_NEXT); //       CyDmaTdSetAddress(td, LO16((uint32)steps), LO16((uint32)StepperController_X_Datapath_1_F1_PTR)); //      CyDmaChSetInitialTd(channel, td); //         CyDmaChEnable(channel, 1); } 


Betrachten Sie nun die wichtigen Teile.

Wenn die LĂ€nge des positiven Teils des Impulses 92 Taktzyklen entspricht, kann das Oszilloskop nicht erkennen, ob sich im negativen Teil ein Einzelzyklus-Einsatz befindet oder nicht. Die Skala wird nicht gleich sein. Es ist notwendig, den positiven Teil so kurz wie möglich zu machen, damit der Gesamtimpuls im Maßstab mit dem eingefĂŒgten Schlag vergleichbar ist. Daher Ă€ndere ich die Periode des ZĂ€hlers, die die Dauer des positiven Teils des Impulses festlegt, mit Nachdruck:

  //   ,   //      //   ,  DMA    !!! //    ,   !!! StepperController_X_SingleVibrator_WritePeriod (6); 

Aber warum sechs ganze Maßnahmen? Warum nicht drei? Warum nicht zwei? Warum doch keiner? Das ist eine traurige Geschichte. Wenn der positive Impuls kĂŒrzer als 6 Zyklen ist, funktioniert das System nicht. Langes Debuggen auf einem Oszilloskop mit der Ausgabe von Testleitungen nach außen zeigte, dass DMA keine schnelle Sache ist. Wenn die Maschine weniger als eine bestimmte Dauer lĂ€uft, ist das FIFO zum Zeitpunkt des Verlassens des Verzögerungsstatus meistens noch leer. Es darf noch kein einziges neues Datenwort platziert werden! Und nur wenn der positive Teil des Impulses eine Dauer von 6 Zyklen hat, hat FIFO garantiert Zeit zum Laden ...

Latenz Exkurs


Eine weitere feste Idee, die mir in den Sinn kommt, ist die Hardwarebeschleunigung bestimmter Funktionen des Kernels unseres RTOS MAX. Aber leider sind alle meine besten Ideen ĂŒber die gleichen Latenzen gebrochen.

Es gab einen Fall, in dem ich die Entwicklung von Bare-Metal-Anwendungen fĂŒr Cyclone V SoC untersucht habe. Es stellte sich jedoch heraus, dass die Arbeit mit einzelnen FPGA-Registern (wenn abwechselnd darauf geschrieben und dann von ihnen gelesen wird) die Kernoperation hunderte (!!!) Mal reduziert. Du hast richtig gehört. Es ist in Hunderten. DarĂŒber hinaus ist all dies schlecht dokumentiert, aber zuerst spĂŒrte ich innerlich und bewies dann anhand von Phrasenfetzen aus der Dokumentation, dass Latenzen schuldig waren, wenn Anfragen ĂŒber eine Reihe von BrĂŒcken weitergeleitet wurden. Wenn Sie ein großes Array austreiben mĂŒssen, tritt ebenfalls eine Latenz auf, die jedoch in Bezug auf ein gepumptes Wort nicht von Bedeutung ist. Wenn die Anforderungen einzeln sind (und die Hardwarebeschleunigung des Betriebssystemkerns nur diese impliziert), erfolgt die Verlangsamung genau hunderte Male. Es wird viel schneller sein, alles rein programmatisch zu erledigen, wenn das Programm mit rasendem Tempo mit dem Hauptspeicher durch den Cache arbeitet.

Auf PSoC hatte ich auch bestimmte PlĂ€ne. Im Erscheinungsbild können Sie mit DMA und UDB wunderbar nach Daten in einem Array suchen. Was ist wirklich da! Aufgrund der DMA-Deskriptorstruktur könnten diese Controller eine vollstĂ€ndige Hardware-Suche in verknĂŒpften Listen durchfĂŒhren! Nachdem ich den oben beschriebenen Stecker erhalten hatte, stellte ich fest, dass er auch mit Latenz verbunden ist. Hier wird diese Latenz in der Dokumentation sehr schön beschrieben. Sowohl in der Familie TRM als auch in einem separaten Dokument AN84810 - PSoC 3 und PSoC 5LP Advanced DMA Topics . Dort ist Abschnitt 3.2 diesem Thema gewidmet. Die nĂ€chste Hardwarebeschleunigung wird also abgebrochen. Schade. Aber wie Semyon Semyonovich Gorbunkov sagte: "Wir werden suchen."

Fortsetzung der Softwareexperimente


Als nÀchstes stelle ich die Parameter des Bresenham-Algorithmus ein:

  //     //    —   CY_SET_REG16(StepperController_X_Datapath_1_D0_PTR, 4); CY_SET_REG16(StepperController_X_Datapath_1_D1_PTR, 2); 

Nun, dann kommt der regulĂ€re Code, der ein Array von Wörtern ĂŒber DMA an FIFO1 des MotorsteuergerĂ€ts X ĂŒbertrĂ€gt.

Das Ergebnis bedarf einiger ErklÀrung. Da ist er:



Der Wert des ZĂ€hlers A0 wird rot angezeigt, wenn sich die Maschine im Zustand Eins befindet . Das grĂŒne Sternchen zeigt FĂ€lle an, in denen die Verzögerung eingefĂŒgt wird, weil sich das GerĂ€t im ExtraTick- Status befindet. Es gibt auch Balken, bei denen die Verzögerung auf den ClearA0- Status zurĂŒckzufĂŒhren ist. Sie sind mit einem blauen Raster gekennzeichnet.

Wie Sie sehen, geht beim ersten Eingeben die allererste Verzögerung verloren. Dies liegt an der Tatsache, dass A0 im Leerlauf zurĂŒckgesetzt wird , sich jedoch erhöht, wenn es LoadData eingibt . Bis zur Analyse (Austritt aus dem Zustand Eins ) ist es daher bereits gleich Einheit. Der Account beginnt bei ihr. Im Allgemeinen wirkt sich dies jedoch nicht auf die Mittelfrequenz aus. Es muss nur beachtet werden. Da zu beachten ist, dass beim ZurĂŒcksetzen von A0 auch die Uhr eingefĂŒgt wird. Dies muss bei der Berechnung der Durchschnittsfrequenz berĂŒcksichtigt werden.

Im Allgemeinen ist die Anzahl der Impulse jedoch korrekt. Ihre Dauer ist auch glaubwĂŒrdig.
Versuchen wir, eine realere Kette von Deskriptoren zu programmieren.

bestehend aus einer Phase des Beschleunigens, der linearen Bewegung und des Bremsens.
 void TestWithPacking(int countOnLinearStage) { //   ,   //     . //   ,  DMA    !!! //    ,   !!! StepperController_X_SingleVibrator_WritePeriod (6); //     //    —   CY_SET_REG16(StepperController_X_Datapath_1_D0_PTR, 4); CY_SET_REG16(StepperController_X_Datapath_1_D1_PTR, 2); //    static const uint16 accelerate[] = {0x0010,0x0008,0x0004}; //    static const uint16 deccelerate[] = {0x004,0x0008,0x0010}; //  .    . static const uint16 steps[] = {0x0001}; //  DMA  ,      uint8 channel = DMA_X_DmaInitialize (sizeof(steps[0]),1,HI16(steps),HI16(StepperController_X_Datapath_1_F1_PTR)); CyDmaChRoundRobin (channel,true); //   uint8 tdDeccelerate = CyDmaTdAllocate(); CyDmaTdSetConfiguration(tdDeccelerate, sizeof(deccelerate), CY_DMA_DISABLE_TD, TD_INC_SRC_ADR | TD_AUTO_EXEC_NEXT); CyDmaTdSetAddress(tdDeccelerate, LO16((uint32)deccelerate), LO16((uint32)StepperController_X_Datapath_1_F1_PTR)); //       uint8 tdSteps = CyDmaTdAllocate(); //   !!! //     !!! CyDmaTdSetConfiguration(tdSteps, countOnLinearStage, tdDeccelerate, /*TD_INC_SRC_ADR |*/ TD_AUTO_EXEC_NEXT); CyDmaTdSetAddress(tdSteps, LO16((uint32)steps), LO16((uint32)StepperController_X_Datapath_1_F1_PTR)); //   //     !!! uint8 tdAccelerate = CyDmaTdAllocate(); CyDmaTdSetConfiguration(tdAccelerate, sizeof(accelerate), tdSteps, TD_INC_SRC_ADR | TD_AUTO_EXEC_NEXT); CyDmaTdSetAddress(tdAccelerate, LO16((uint32)accelerate), LO16((uint32)StepperController_X_Datapath_1_F1_PTR)); //      CyDmaChSetInitialTd(channel, tdAccelerate); //         CyDmaChEnable(channel, 1); } 


Rufen Sie zunÀchst dieselben zehn Schritte auf (in DMA gehen tatsÀchlich 20 Bytes):

 TestWithPacking (20); 

Das Ergebnis ist wie erwartet. Zu Beginn ist eine Beschleunigung sichtbar. Und der Ausgang zu IDLE (blauer Strahl) erfolgt mit einer großen Verzögerung vom letzten Impuls, dann war der letzte Schritt vollstĂ€ndig abgeschlossen, sein Wert ist ungefĂ€hr gleich dem Wert des ersten.



Echtes Pferd unter normalen Bedingungen


Beim Umbau der AusrĂŒstung bin ich irgendwie von einer 24-Bit-Impulsbreite zu einem 16-Bit-Job gesprungen. Wir haben jedoch herausgefunden, dass dies nicht möglich ist: Die minimale Pulsfrequenz ist zu hoch. Ich habe es absichtlich gemacht. Tatsache ist, dass sich die Technik zum Erweitern der BitkapazitĂ€t eines 16-Bit-ZĂ€hlers als so kompliziert herausstellte, dass sie, wenn ich sie zusammen mit der Hauptmaschine beschrieben hĂ€tte, die ganze Aufmerksamkeit abgelenkt hĂ€tte. Daher betrachten wir es separat.

Wir haben eine 16-Bit-Batterie. Ich beschloss, den hohen Bits die Sieben-Bit-ZĂ€hler-StandardentitĂ€t hinzuzufĂŒgen. Was ist dieser Sieben-Bit-ZĂ€hler? Dies ist das Design, das in jedem UDB-Block verfĂŒgbar ist (der Basis-UDB-Block hat eine Bitbreite aller 8-Bit-Register, die Zunahme der Bittiefe wird durch die Kombination von Blöcken in Gruppen bestimmt). Von denselben Ressourcen können Steuer- / Statusregister implementiert werden. Jetzt haben wir einen ZĂ€hler und kein einziges Steuerungs- / Statuspaar fĂŒr 16 Datenbits. Wenn Sie dem System einen weiteren ZĂ€hler hinzufĂŒgen, werden wir die zusĂ€tzlichen Ressourcen nicht verzögern. Wir nehmen nur das, was uns bereits zugewiesen ist. Das ist schön! Wir machen das hohe Byte des ImpulsbreitenzĂ€hlers durch diesen Mechanismus und erhalten die Gesamtbreite des ImpulsbreitenzĂ€hlers gleich 23 Bit.



Zuerst werde ich sagen, was ich gedacht habe. Ich dachte, dass ich nach dem Verlassen des Verzögerungsstatus den Abschluss der ZĂ€hlung dieses zusĂ€tzlichen ZĂ€hlers ĂŒberprĂŒfen wĂŒrde. Wenn er nicht fertig gezĂ€hlt hat, werde ich seinen Wert reduzieren und wieder in den Verzögerungszustand wechseln. Wenn Sie gezĂ€hlt haben, bleibt die Logik unverĂ€ndert, ohne zusĂ€tzliche Zyklen hinzuzufĂŒgen.

DarĂŒber hinaus besagt die Dokumentation fĂŒr diesen ZĂ€hler, dass ich Recht habe. Wörtlich heißt es:
Zeitraum
Definiert den Anfangsperiodenregisterwert. FĂŒr eine Periode von N Takten sollte der Periodenwert auf den Wert von N-1 gesetzt werden. Der ZĂ€hler zĂ€hlt von N-1 bis 0, was zu einer N-Taktzyklusperiode fĂŒhrt. Ein Periodenregisterwert von 0 wird nicht unterstĂŒtzt und fĂŒhrt dazu, dass die TerminalzĂ€hlausgabe auf einem konstant hohen Zustand gehalten wird.
Das Leben hat gezeigt, dass alles anders ist. Ich habe den Zustand der TerminalzÀhllinie auf dem Oszilloskop abgeleitet und ihren Wert bei einer vorinstallierten Null in Periode und wÀhrend des Programmladens beobachtet. Ach und ah. Es gab keinen konstant hohen Zustand !

Durch Versuch und Irrtum gelang es mir, das System ordnungsgemĂ€ĂŸ zum Laufen zu bringen, aber dazu muss mindestens eine Subtraktion vom ZĂ€hler erfolgen! Der neue Zustand der "Subtraktion" ist nicht auf der Seite. Es musste in den erforderlichen Pfad eingeklemmt werden. Es befindet sich vor dem Verzögerungsstatus und heißt Next65536 .



ALU fĂŒhrt in diesem Zustand keine nĂŒtzlichen Aktionen aus. TatsĂ€chlich reagiert nur ein neuer ZĂ€hler auf die Tatsache, dass er sich in diesem Zustand befindet. Hier ist es im Diagramm:



Hier sind seine Eigenschaften im Detail:



Unter BerĂŒcksichtigung der vorherigen Artikel ist das Wesen dieses ZĂ€hlers im Allgemeinen klar. Nur die Enable- Linie leidet. Auch hier verstehe ich nicht ganz, warum es eingeschaltet werden sollte, wenn sich die Maschine im LoadData- Status befindet (dann lĂ€dt der ZĂ€hler den Periodenwert neu). Ich habe diesen Trick aus den Eigenschaften des ZĂ€hlers entlehnt, der die LEDs steuert, der dem englischen Autor der Steuereinheit fĂŒr diese LEDs entnommen wurde. Ohne sie funktioniert der Nullwert der Periode nicht. Sie arbeitet mit ihr.

Im API-Code fĂŒgen wir die Initialisierung eines neuen ZĂ€hlers hinzu. Jetzt sieht die Startfunktion folgendermaßen aus:

 void `$INSTANCE_NAME`_Start() { `$INSTANCE_NAME`_SingleVibrator_Start(); //"One" Generator start `$INSTANCE_NAME`_Plus65536_Start(); } 

Schauen wir uns das neue System an. Hier ist der Funktionscode zum Testen

(darin unterscheidet sich nur die erste Zeile von der bereits bekannten):
 void JustTest(int extra65536s) { //      65536  StepperController_X_Plus65536_WritePeriod((uint8) extra65536s); //     //    —   CY_SET_REG16(StepperController_X_Datapath_1_D0_PTR, 4); CY_SET_REG16(StepperController_X_Datapath_1_D1_PTR, 2); //         . //         static const uint16 steps[] = { 0x1000,0x1000,0x1000,0x1000 }; //  DMA  ,      uint8 channel = DMA_X_DmaInitialize (sizeof(steps[0]),1,HI16(steps),HI16(StepperController_X_Datapath_1_F1_PTR)); CyDmaChRoundRobin (channel,true); //       ,       uint8 td = CyDmaTdAllocate(); //       .  ,    . CyDmaTdSetConfiguration(td, sizeof(steps), CY_DMA_DISABLE_TD, TD_INC_SRC_ADR | TD_AUTO_EXEC_NEXT); //       CyDmaTdSetAddress(td, LO16((uint32)steps), LO16((uint32)StepperController_X_Datapath_1_F1_PTR)); //      CyDmaChSetInitialTd(channel, td); //         CyDmaChEnable(channel, 1); } 


Wir nennen es so:

  JustTest(0); 

Auf dem Oszilloskop sehen wir Folgendes (gelber Strahl - STEP-Ausgang, blauer Wert des ZĂ€hler-TC-Ausgangs fĂŒr die Prozesssteuerung). Die Pulsdauer wird durch das Schrittarray eingestellt . Bei jedem Schritt betrĂ€gt die Dauer 0x1000 Takte.



Wechseln Sie zu einem anderen Scan, damit verschiedene Ergebnisse kompatibel sind:



Ändern Sie den Funktionsaufruf in:

  JustTest(1); 

Das Ergebnis ist wie erwartet. Zuerst ist der TC-Ausgang fĂŒr 0x1000 Zyklen Null, dann - eine Einheit fĂŒr 0x10000 (65536d) Zyklen. Die Frequenz betrĂ€gt ungefĂ€hr 700 Hertz, wie wir im letzten Teil des Artikels herausgefunden haben, also ist alles richtig.



Nun, versuchen wir es mit einer Zwei:

  JustTest(2); 

Wir bekommen:



Alles ist richtig. Der TC-Ausgang wird in den letzten 65536-Taktzyklen auf eins gestellt. Davor war er fĂŒr 0x1000 + 0x10000 Zyklen bei Null.

Bei diesem Ansatz sollten natĂŒrlich alle Impulse den gleichen Wert wie der neue ZĂ€hler haben. Es ist unmöglich, wĂ€hrend der Beschleunigung einen Impuls mit dem höchsten Byte zu erzeugen, z. B. 3, dann 1, dann 0. TatsĂ€chlich haben Beschleunigungen bei so niedrigen Frequenzen (weniger als siebenhundert Hertz) keine physikalische Bedeutung, daher kann dieses Problem vernachlĂ€ssigt werden. Bei dieser Frequenz können Sie linear mit dem Motor arbeiten.

Fliege in die Salbe


Das TRM-Dokument fĂŒr die PSoC5LP-Familie lautet:
Jede Transaktion kann zwischen 1 und 64 KB groß sein
Aber in der bereits erwÀhnten AN84810 gibt es einen solchen Satz:
1. Wie können Sie mit DMA mehr als 4095 Bytes puffern?
Die maximale Übertragungszahl eines TD ist auf 4095 Byte begrenzt. Wenn Sie mehr als 4095 Bytes ĂŒber einen einzelnen DMA-Kanal ĂŒbertragen mĂŒssen, verwenden Sie mehrere TDs und verketten Sie sie wie in Beispiel 5 gezeigt.
Wer hat recht? Wenn Sie Experimente durchfĂŒhren, werden die Ergebnisse zugunsten der schlechtesten Aussagen geneigt sein, aber das Verhalten wird völlig unverstĂ€ndlich sein. Der ganze Fehler ist diese ÜberprĂŒfung in der API:



Gleicher Text.
 cystatus CyDmaTdSetConfiguration(uint8 tdHandle, uint16 transferCount, uint8 nextTd, uint8 configuration) \ { cystatus status = CYRET_BAD_PARAM; if((tdHandle < CY_DMA_NUMBEROF_TDS) && (0u == (0xF000u & transferCount))) { /* Set 12 bits transfer count. */ reg16 *convert = (reg16 *) &CY_DMA_TDMEM_STRUCT_PTR[tdHandle].TD0[0u]; CY_SET_REG16(convert, transferCount); /* Set Next TD pointer. */ CY_DMA_TDMEM_STRUCT_PTR[tdHandle].TD0[2u] = nextTd; /* Configure the TD */ CY_DMA_TDMEM_STRUCT_PTR[tdHandle].TD0[3u] = configuration; status = CYRET_SUCCESS; } return(status); } 


Wenn eine Transaktion angegeben wird, die lĂ€nger als 4095 Byte ist, wird die vorherige Einstellung verwendet. Ja, ich habe nicht daran gedacht, Fehlercodes zu ĂŒberprĂŒfen ...

Die Experimente haben gezeigt, dass, wenn Sie diese PrĂŒfung entfernen, die tatsĂ€chliche LĂ€nge mit der Maske 0xfff (4096 = 0x1000) abgeschnitten wird. Ach und ah. Alle Hoffnungen auf einen angenehmen Job brachen zusammen. Sie können in 4K natĂŒrlich Ketten verwandter Deskriptoren erstellen. Aber zum Beispiel sind 64K 16 Ketten. Drei aktive Motoren (Extruder haben weniger Stufen) - 48 Ketten. Im schlimmsten Fall sollte vor jedem Segment genau so viel gefĂŒllt werden. Vielleicht ist es rechtzeitig akzeptabel. Es stehen mindestens 127 Deskriptoren zur VerfĂŒgung, sodass auf jeden Fall genĂŒgend Speicher vorhanden ist.

Sie können die fehlenden Daten nach Bedarf senden. Es kam zu einer Unterbrechung, dass der DMA-Kanal die Arbeit abgeschlossen hatte. Wir ĂŒbertragen ein weiteres Segment darauf. In diesem Fall sind keine Berechnungen erforderlich, das Segment ist bereits gebildet, alles wird schnell sein. Und es gibt keine Leistungsanforderungen: Wenn eine Interrupt-Anforderung ausgegeben wird, gibt es 4 weitere Elemente im FIFO, die jeweils fĂŒr mehrere hundert oder sogar Tausende von Taktzyklen gewartet werden. Das heißt, alles ist real. Eine bestimmte Strategie lĂ€sst sich wĂ€hrend der eigentlichen Arbeit leichter auswĂ€hlen. Ein Fehler in der Dokumentation (TRM) trĂŒbte jedoch die ganze Stimmung. Wenn dies im Voraus bekannt wĂ€re, hĂ€tte ich die Methodik vielleicht nicht ĂŒberprĂŒft.

Fazit


In der Erscheinung wurde das entwickelte Hilfs-Firmware-Tool akzeptabel, so dass auf seiner Grundlage eine Version der „Firmware“ erstellt werden kann, beispielsweise Marlin, die sich nicht stĂ€ndig im Interrupt-Handler fĂŒr Schrittmotoren befindet. Soweit ich weiß, gilt dies insbesondere fĂŒr Delta-Drucker, bei denen der Bedarf an Computerressourcen recht hoch ist. Vielleicht wird dadurch der Zustrom beseitigt, der in meinem Delta an Stellen auftritt, an denen der Kopf stoppt. Auf dem MZ3D wird an denselben Stellen kein Zustrom beobachtet. Ob es wahr ist oder nicht, wird die Zeit zeigen, und der Bericht darĂŒber muss in einer völlig anderen Filiale veröffentlicht werden.

In der Zwischenzeit haben wir gesehen, dass es auf dem UDB-Block trotz seiner Einfachheit durchaus möglich ist, einen Coprozessor zu implementieren, der zusammen mit dem Hauptprozessor arbeitet und das Entladen ermöglicht. Und wenn es viele dieser Einheiten gibt, können Coprozessoren parallel arbeiten.

Ein Fehler in der Dokumentation fĂŒr den DMA-Controller hat das Ergebnis verwischt. Unterbrechungen sind dennoch erforderlich, jedoch nicht mit der gleichen HĂ€ufigkeit und nicht mit der zeitlichen KritikalitĂ€t, die in der Originalversion vorhanden war. Die Stimmung ist also verdorben, aber die Verwendung eines auf UDB basierenden „Coprozessors“ bringt im Vergleich zur reinen Softwarearbeit immer noch einen erheblichen Gewinn.

Auf dem Weg stellte sich heraus, dass DMA mit einer relativ geringen Geschwindigkeit arbeitet. Infolgedessen wurden einige Messungen sowohl am PSoC5LP als auch am STM32 durchgefĂŒhrt. Die Ergebnisse ziehen einen anderen Artikel. Vielleicht mache ich es eines Tages, wenn sich das Thema als interessant herausstellt.

Als Ergebnis der Experimente wurden zwei Testprojekte gleichzeitig erhalten. Das erste ist leichter zu verstehen. Sie können es hier nehmen . Der zweite wird vom ersten geerbt, aber beim HinzufĂŒgen eines Sieben-Bit-ZĂ€hlers und der zugehörigen Logik verwirrt. Sie können es hier nehmen . NatĂŒrlich sind diese Beispiele nur Testbeispiele. Es ist noch keine Zeit zum Einbetten in die echte „Firmware“. Im Rahmen dieser Artikel ist es jedoch wichtiger, die Arbeit mit UDB zu ĂŒben.

Source: https://habr.com/ru/post/de434742/


All Articles