Steuerung von RGB-LEDs durch die UDB-Einheit von PSoC-Mikrocontrollern von Cypress



    Einleitung


    Ich wollte schon lange lernen, wie man UDB-Blöcke in Cypress PSoC-Controllern programmiert, aber alle Hände haben irgendwie nicht erreicht. Es gab also ein Problem, bei dem dies möglich war. Als ich die Materialien aus dem Netzwerk verstand, erkannte ich, dass praktische Empfehlungen für die Arbeit mit UDB auf diese oder andere Variationen von Zählern und PWMs beschränkt sind. Aus irgendeinem Grund machen alle Autoren ihre eigenen Variationen dieser beiden kanonischen Beispiele, sodass die Beschreibung von etwas anderem für den Leser interessant sein kann.

    So Es gab ein Problem bei der dynamischen Steuerung einer langen Linie von R28-LEDs WS2812B. Klassische Ansätze zu diesem Fall sind bekannt. Sie können den banalen Arduino nehmen, aber dort wird die Ausgabe programmgesteuert, sodass während der Ausgabe der Daten alles andere inaktiv ist, andernfalls schlagen die Zeitdiagramme fehl. Sie können STM32 nehmen und Daten entweder über DMA an PWM oder über DMA an SPI ausgeben. Techniken sind bekannt. Ich habe sogar einmal persönlich durch SPI eine Reihe von sechzehn Dioden beherrscht. Aber der Aufwand ist großartig. Ein Datenbit in den LEDs benötigt für PWM 8 Bits und für SPI 3 bis 4 Bits (abhängig von der PLL-Rate in der Steuerung). Es gibt zwar nur wenige LEDs, aber es ist nicht beängstigend, aber wenn beispielsweise einige hundert sind, sollten 200 * 24 = 4800 Bits = 600 Bytes Nutzdaten physisch in einem Puffer gespeichert werden. Volumen von mehr als 4 Kilobyte für die PWM-Version oder mehr als 2 Kilobytes für die SPI-Option. Für die dynamische Anzeige sollten mehrere Puffer vorhanden sein, während der STM32F103 über RAM für alles verfügt, der ungefähr 20 Kilobyte groß ist. Es ist nicht so, dass wir uns gegen eine nicht realisierbare Aufgabe entschieden haben, aber der Grund für die Überprüfung, ob sie auf PSoC implementiert werden kann, ohne dass zusätzlicher Arbeitsspeicher benötigt wird, ist ziemlich wichtig.

    Verweise auf die Theorie


    Zuerst wollen wir herausfinden, was für ein Biest eine solche UDB ist und wie man damit umgehen kann. Dies wird großartige Lernfilme vom Hersteller von Controllern unterstützen.

    Beginnen Sie von hier aus , und am Ende jedes Videos finden Sie einen Link zur nächsten Serie. Schritt für Schritt erwerben Sie Grundkenntnisse und betrachten das kanonische Beispiel „Counter“. Nun, das Steuersystem der Ampel.

    Ungefähr dasselbe, aber in kleine Stücke gehackt, kann hier angesehen werden . Mein Video wurde nicht abgespielt, kann jedoch heruntergeladen und lokal angezeigt werden. Unter anderem gibt es ein kanonisches Beispiel für die Implementierung von PWM.

    Suchen Sie nach vorgefertigten Lösungen


    Um das Rad nicht neu zu erfinden (und umgekehrt - um die Technik aus Erfahrung eines anderen zu studieren), stöberte ich im Netzwerk auf der Suche nach vorgefertigten Lösungen zur Steuerung von RGB-LEDs. Die beliebteste Lösung ist StripLightLib.cylib. Er plant jedoch seit vielen Jahren, DMA zu unterstützen. Und ich möchte genau die Lösung ausprobieren, die nicht von der CPU abhängt. Ich möchte den Prozess starten und es vergessen, indem ich mich auf die Vorbereitung des nächsten Frames konzentriere.

    Die Lösung, die meinen Wünschen entspricht, wurde unter https://github.com/PolyVinalDistillate/PSoC_DMA_NeoPixel gefunden .

    Dort ist alles auf UDB implementiert (LEDs sind nur eine Ausrede, Ziel ist es, UDB zu studieren). Es gibt Unterstützung für DMA. Und das Projekt dort ist eindeutig schön organisiert.

    Probleme der gewählten Lösung


    Wie sich die "Firmware" im Projekt PSoC_DMA_NeoPixel befindet, kann jeder nach dem Lesen des Artikels sehen. Dies wird das Material reparieren. Bisher möchte ich nur sagen, dass ich zunächst die Logik der ursprünglichen Firmware vereinfacht habe, ohne die Ressourcen zu reduzieren (was jedoch verständlicher wurde). Dann begann er zu experimentieren mit dem Ersetzen der Logik des Automaten, was einen Gewinn an Ressourcen versprach, aber auf ein ernstes Problem stieß. Und so wurde entschieden - es wird nicht beseitigt! Und sie begannen, mich mit vagen Zweifeln zu quälen. Hat der englische Autor das gleiche Problem? Seine Demo blinkt sehr schön. Was aber, wenn wir die schöne Füllung durch „alle Einheiten“ ersetzen und die Ausgabe mit einem Oszilloskop steuern, nicht mit unseren Augen?
    So unhöflich wie möglich (man kann sogar "brutal" sagen), erzeugen wir Daten:

    memset (pPixelArray,0xff,sizeof(pPixelArray));
            //Call NeoPixel update function (non blocking) to trigger DMA pixel update
            NP_Update();

    Und wir sehen dieses Bild auf einem Oszilloskop:



    Beim ersten Bit unterscheidet sich die Breite von allen anderen. Ich bat darum, alle Einheiten zu schicken, aber nicht alle. Null war unter ihnen! Wir ändern den Scan:



    Die Breite ist für jedes achte Bit unterschiedlich.

    Im Allgemeinen ist dieses Beispiel als eigenständige Lösung nicht geeignet, sondern als Inspirationsquelle - einfach perfekt. Erstens ist seine Funktionsunfähigkeit für das Auge nicht sichtbar (die LEDs leuchten sowieso hell, das Auge sieht nicht, dass sie das halbe Maximum leuchten), aber der Code ist gut strukturiert, es ist angenehm, ihn als Grundlage zu nehmen. Zweitens bietet dieses Beispiel Raum für die Suche nach Möglichkeiten zur Vereinfachung, und drittens müssen Sie darüber nachdenken, wie Sie den Fehler beseitigen können. Am besten ist es, das Material zu begreifen! Deshalb empfehle ich Ihnen, nachdem Sie den Artikel gelesen haben, das ursprüngliche Beispiel zu zerlegen, da Sie verstanden haben, wie es funktioniert.

    Praktischer Teil


    Jetzt fangen wir an zu üben. Wir behandeln die wichtigsten Aspekte der UDB-Firmware-Entwicklung. Betrachten Sie die Beziehung und die grundlegenden Techniken. Öffnen Sie dazu meine Version des Projekts . Der linke Block speichert Informationen zu den Arbeitsdateien. Standardmäßig ist die Registerkarte Quelle geöffnet . Die Haupt-Source - Projekt - Datei main.c . Tatsächlich gibt es keine anderen Arbeitsdateien in der Gruppe Quelldateien .



    Die generierte Quellgruppe enthält Bibliotheksfunktionen. Es ist besser, sie nicht zu regieren. Nach jeder Änderung der UDB-Firmware wird diese Gruppe neu generiert. Wo ist also in dieser Idylle eine Beschreibung des Codes für UDB? Um es zu sehen, müssen Sie zur Registerkarte Komponenten wechseln :



    Der Autor des ursprünglichen Projekts hat zwei Komponenten auf zwei Ebenen erstellt. Auf der obersten Ebene befindet sich das Schema NeoPixel_v1_2.cysch . Dies ist aus dem Hauptschema ersichtlich:



    Die Komponente sieht wie folgt aus:



    Wir werden die Softwareunterstützung für dieses Schema später prüfen. In der Zwischenzeit stellen wir fest, dass sich der DMA-Block darauf und ein bestimmtes NeoPixDrv_v1- Symbol befindet . Dieser mysteriöse Block ist oben in der Baumstruktur beschrieben, der sich aus dem folgenden Tooltip ergibt:



    UDB-Firmware


    Öffnen Sie diese Komponente (Datei mit der Erweiterung .cyudb ). Die geöffnete Zeichnung ist einfach riesig. Wir beginnen zu verstehen, was was ist.



    Im Gegensatz zum Autor des ursprünglichen Projekts betrachte ich die Übertragung jedes Datenbits in Form von drei (zeitlich gleich großen) Teilen:

    1. Startteil (immer 1)
    2. Ein Teil der Daten
    3. Teil stoppen (immer 0)

    Bei diesem Ansatz ist eine große Anzahl von Zählern nicht erforderlich (im Original gab es bis zu drei Zähler, die eine große Menge an Ressourcen verbrauchten). Die Länge aller Teile ist gleich und kann mit einem Register eingestellt werden. Somit umfasst die Übergangsgraph Firmware Maschine folgende Zustände:

    Idle - Zustand ( Leerlauf ). Es bleibt in der Maschine, bis neue Daten im FIFO angekommen sind.



    Aus den Schulungsvideos war mir nicht ganz klar, wie die Zustände der Maschine mit der ALU zusammenhängen. Die Autoren benutzen die Verbindung als etwas Selbstverständliches, aber ich konnte es als Anfänger nicht sofort sehen. Lassen Sie uns sofort im Detail verstehen. Die Abbildung oben zeigt, dass der Zustand Leerlaufcodiert durch den Wert 1'b0. Es wird korrekter auf 3'b000 sein, aber der Editor wird immer noch alles wiederholen. Die Eingänge des Datapath- Blocks werden wie folgt beschrieben:



    Wenn Sie darauf doppelklicken , wird eine detailliertere Option angezeigt :



    Dies bedeutet, dass das Null-Bit der Adresse der ALU-Befehlsadresse dem Null-Bit der Variablen entspricht, die den Status der Maschine angibt. Der erste ist der erste, der zweite ist der zweite. Falls gewünscht, können die Bits der Adresse der ALU-Anweisung durch beliebige Variablen und sogar Ausdrücke abgeglichen werden (in der ursprünglichen Version war das zweite Bit der Adresse der Anweisung der ALU genau durch den Ausdruck abgestimmt, und in der aktuellen Version wird es eindeutig nicht verwendet, aber als abgehendes Gehirn ist das Beispiel sehr klar, dann können Sie nachsehen).

    So Bei der aktuellen Einstellung der Eingänge, die der binäre Statuscode des Automaten ist, wird ein solcher ALU-Befehl verwendet. Wenn wir uns im Ruhezustand befinden , der den Code 000 hat, wird der Nullbefehl verwendet. Hier ist es:



    Ich weiß bereits aus dieser Platte, dass dies ein banaler NOP ist. Man kann aber doppelt darauf klicken und die Vollversion lesen:



    NOPs werden überall geschrieben. Register sind nicht mit etwas gefüllt.

    Nun wollen wir mal sehen, was diese geheimnisvolle Flagge ist ! NoData , das Maschinengewehr zwingt den Ruhezustand zu verlassen. Dies ist die Ausgabe des Datenpfadblocks . Sie können bis zu sechs Ausgänge beschreiben. Nur DatenpfadEs können viele weitere Flags generiert werden, aber es gibt nicht genügend Verfolgungsressourcen. Daher müssen Sie auswählen, welche sechs (oder weniger) wir wirklich benötigen. Hier ist die Liste in der Abbildung:



    Wenn Sie darauf doppelklicken, werden die Details



    angezeigt: Hier ist die vollständige Liste der Flags, die angezeigt werden könnten:



    Wählen Sie das gewünschte Flag aus, und geben Sie ihm einen Namen. Ab diesem Zeitpunkt hat das System eine Flagge. Wie Sie sehen, ist das NoData- Flag der Name für die F0-Blockstatuskette (leer) . Dies ist ein Zeichen dafür, dass sich keine Daten im Eingabepuffer befinden. Ah ! NoDataentsprechend seine Umkehrung. Zeichen der Datenverfügbarkeit Sobald die Daten (programmgesteuert oder mit Hilfe von DMA) in den FIFO gelangen, wird das Flag gelöscht (und seine Inversion wird gesetzt), und der Rechner verlässt bei der nächsten Uhr den Ruhezustand und wechselt in den Zustand GetData .



    Wie Sie sehen, wird die Maschine definitiv aus diesem Zustand herauskommen, nachdem sie genau einen Zyklus darin geblieben ist. In diesem Übergangsbereich werden für diesen Status keine Aktionen angezeigt. Aber Sie müssen immer sehen, was die ALU tun wird. Der Statuscode ist 1'b1, dh 3'b001. Wir schauen uns die entsprechende Adresse in der ALU an:



    Es gibt etwas. Da wir keine Erfahrung mit dem Lesen des hier geschriebenen Textes haben, zeigen wir es durch Doppelklick auf die entsprechende Zelle an:



    Daraus folgt, dass die ALU selbst noch keine Aktionen ausführt. In das Register A0 wird jedoch der Inhalt von FIFO0 eingefügt, dh die Daten, die vom Programm oder vom DMA-Block kommen. Mit Blick auf die Zukunft werde ich sagen, dass A0 als Schieberegister verwendet wird, von dem das Byte sequentiell ausgegeben wird. Register A1 setzt den Wert von Register D1. Im Allgemeinen werden alle Register D vor dem aktiven Betrieb des Geräts normalerweise mit Software gefüllt. Bei der Betrachtung der API werden wir dann sehen, dass die Anzahl der Maschinen-Ticks, die die Dauer eines dritten Bits festlegt, in dieses Register eingetragen wird. So Der verschobene Wert ist in A0 gefallen und der Wert der Dauer des Anfangsteils des Bits in A1. Und im nächsten Zyklus wird die Maschine sicherlich in den Zustand Constant1 wechseln .



    Wie aus dem Namen des Bundesstaates hervorgeht, wird hier die Konstante 1 generiert, betrachten wir die Dokumentation der LED. So soll das Gerät übertragen werden:



    Und hier ist es - Null:



    Ich habe die roten Linien hinzugefügt. Wenn wir davon ausgehen, dass die Dauer der dritten Zeit gleich ist, sind die Anforderungen für die Dauer der Impulse (in derselben Dokumentation angegeben) erfüllt. Das heißt, jeder Impuls besteht aus einer Starteinheit, einem Datenbit und einem Stoppnullpunkt. Tatsächlich wird die Starteinheit übertragen, wenn sich die Maschine im Status Constant1 befindet .

    In diesem Zustand klickt die Maschine die Einheit in ihrem internen Auslöser. Triggername CurrentBit. Im ursprünglichen Projekt war es im Allgemeinen ein Auslöser, der den Status der Hilfsmaschine festlegt. Ich entschied, dass diese Maschine nur jeden verwirren würde, also habe ich einfach den Abzug gestartet. Es wird nirgendwo beschrieben. Wenn Sie jedoch die Eigenschaften des Zustands eingeben, ist der folgende Eintrag in der Tabelle sichtbar:



    Und unter dem Zustand in der Grafik befindet sich der folgende Text:



    Haben Sie keine Angst vor dem Symbol "Gleich". Dies sind Funktionen des Editors. Im resultierenden Verilog-Code (automatisch vom selben System erstellt) wird ein Pfeil angezeigt:

    Constant1 : 
            begin
                CurrentBit <= (1);
                if (( CycleTimeout ) == 1'b1)
                begin
                    MainState <= Setup1 ;
                end
            end

    Der in diesem Trigger eingerastete Wert ist das Ausgangssignal unserer gesamten Einheit:



    Wenn die Maschine in den Konstant1- Zustand wechselt , trifft eine Einheit auf den Ausgang der von uns entwickelten Einheit. Nun sehen wir uns an, wie die ALU für die Adresse 3'b010 programmiert ist:



    Dieses Element erweitern: Ein Element



    wird vom Register A1 abgezogen. Der Ausgabewert der ALU fällt in das Register A1. Wir haben oben betrachtet, dass A1 ein Taktzähler ist, der zum Einstellen der Dauer des Ausgangsimpulses verwendet wird. Ich möchte Sie daran erinnern, dass es im letzten Schritt von D1 geladen wurde.
    Was ist der Zustand des Staates? CycleTimeOut . Es wird unter den Ausgängen wie folgt beschrieben:



    Also bringen wir die Logik zusammen. Im vergangenen Zustand fiel der Inhalt des zuvor mit dem Programm gefüllten D1-Registers in das A1-Register. In diesem Schritt übersetzt die Maschine den CurrentBit- Trigger in einen und in der ALU nimmt das Register A1 in jedem Taktzyklus ab. Wenn A1 gleich Null wird, wird das Flag automatisch codiert, dem der Autor den Namen CycleTimeout gegeben hat , wodurch der Computer in den Zustand Setup1 wechselt .

    Der Zustand Setup1 bereitet Daten zum Senden eines Nutzimpulses auf.



    Wir betrachten die Anweisungen der ALU bei 3'b011. Ich werde es sofort öffnen:



    Es scheint, dass die ALU keine Aktion hat. Bedienung gleich nop. Und die Ausgabe der ALU geht nirgendwohin. Aber das ist nicht so. Eine äußerst wichtige Maßnahme ist die Verschiebung von Daten in der ALU. Die Tatsache , dass der Transport Bits unter Ausgängen mit unserer Kette verbunden ist ShiftOut :



    Als Ergebnis dieses Schaltvorganges selbst Wert überall verschoben wird nicht fallen, aber die Kette ShiftOut signifikanten A0 - Bit - Wert des Register nehmen. Das sind die Daten, die übertragen werden sollen. Im Zustand des Graphen ist es klar, dass dieser Wert, der von der ALU an die ShiftOut- Schaltung ausgegeben wurde , im CurrentBit- Trigger zwischengespeichert wird . Lassen Sie mich das Bild noch einmal zeigen, um den Artikel nicht aufzuziehen:



    Die Übertragung des zweiten Teils des Bits, der unmittelbare Wert 0 oder 1, beginnt.

    Wir kehren zu den Anweisungen für die ALU zurück. Zusätzlich zu dem, was bereits gesagt wurde, ist es klar, dass auf diesem Weg der Inhalt des Registers D1 wieder in das Register A1 eingefügt wird, so dass das zweite Drittel des Impulses erneut gemessen werden kann.

    Der DataStage- Status ist dem Constant1- Status sehr ähnlich . Die Maschine subtrahiert einfach die Einheit von A1 und geht in den nächsten Zustand, wenn sie Null erreicht. Lassen Sie mich es sogar so zeigen:



    so:



    Dann kommt der Zustand von Setup2 , dessen Essenz wir bereits kennen.



    In diesem Zustand den CurrentBit- Triggerwird auf Null zurückgesetzt (da das dritte Drittel des Impulses übertragen wird, ist der Stop-Teil immer Null). ALU lädt den Inhalt von D1 in A1. Sie können es sogar mit einem kurzen Auge mit einem kurzen Auge sehen:



    Der Constant0- Status ist völlig identisch mit den Constant1- und DataStage-Status . Eine Einheit von A1 abziehen. Wenn der Wert Null erreicht, verlassen wir den Status ShiftData : Der





    Status ShiftData ist komplexer. In den entsprechenden Anweisungen für die ALU werden die folgenden Aktionen ausgeführt: Das



    Register A0 wird um 1 Bit verschoben und die Ergebnisse werden wieder in A0 abgelegt. In A1 wird der Inhalt von D1 erneut gesetzt, um mit dem Messen des Start-Drittels für das nächste Datenbit zu beginnen.

    Ausgabepfeile sollten in Bezug auf Prioritäten betrachtet werden, für die wir den Zustand von ShiftData doppelt anklicken .



    Wenn nicht das letzte Bit übertragen wird (wie dieses Flag direkt darunter gebildet wird), wird ein Bit für das nächste Bit des aktuellen Bytes übertragen.

    Wenn das letzte Bit übertragen wird und sich keine Daten im FIFO befinden, gehen wir in einen Ruhezustand.

    Wenn schließlich das letzte Bit übertragen wird, sich jedoch Daten im FIFO befinden, wird das nächste Byte abgetastet und übertragen.

    Nun zum Bit-Zähler. Es gibt nur zwei Batterien in der ALU: A0 und A1. Sie sind bereits mit dem Schieberegister bzw. dem Verzögerungszähler belegt. Daher wird der Bitzähler extern verwendet.



    Doppelklicken Sie darauf:



    Der Wert beim Booten ist sechs. Es wird in das LoadCounter- Flag geladen .Im Abschnitt Variablen beschrieben:



    Wenn das nächste Datenbyte genommen wird, wird diese Konstante parallel geladen.

    Wenn ein Automat in den ShiftData- Status wechselt , verringert der Zähler den Wert. Wenn der Wert Null erreicht, wird der mit der FinalBit- Schaltung unserer Familie verbundene TerminalCount- Ausgang eingestellt . Diese Schaltung bestimmt, ob der Automat das nächste Bit des aktuellen Bytes sendet oder ein neues Byte sendet (na ja, oder wartet auf ein neues Datenpaket).

    Eigentlich aus Logik - alles. Wie das SpaceForData- Signal generiert wird , das den Status der Hungry- Ausgabe festlegt (indem der DMA-Block informiert wird, dass die nächsten Daten übertragen werden können), werden die Leser aufgefordert, sich selbst zu verfolgen.

    Software-Unterstützung


    Der Autor des ursprünglichen Projekts entschied sich für die Softwareunterstützung für das gesamte System in einem Block, der eine komplexe Lösung beschreibt. Ich möchte Sie daran erinnern, dass wir über diesen Block sprechen: Auf



    dieser Ebene wird sowohl der DMA-Bibliotheksblock als auch alle im UDB-Teil enthaltenen Teile verwaltet. Um die API zu implementieren, fügte der ursprüngliche Autor Header- und Programmdateien hinzu:



    Das Format des Körpers dieser Dateien ist deprimierend. Alles wegen der Liebe des Entwicklers PSoC Designer zu "pure sam". Daher die schrecklichen Makros und Namen der Kilometer. Die Klassenorganisation in C ++ wäre hier sehr willkommen. Zumindest haben wir es bei der Implementierung unseres RTOS MAX überprüft: Es war angenehm und komfortabel. Aber hier kann man viel reden und muss das, was von oben herabgelassen wird, benutzen. Ich werde nur kurz zeigen, wie die API-Funktion aussieht und die gleichen Makros enthält:

    volatilevoid* `$INSTANCE_NAME`_Start(unsignedint nNumberOfNeopixels, void* pBuffer, double fSpeedMHz)
    {
        //work out cycles required at specified clock speed...
        `$INSTANCE_NAME`_g_pFrameBuffer = NULL;
        if((0.3/(1.0/(fSpeedMHz))) > 255) returnNULL;
        unsignedchar fCyclesOn = (unsignedchar)(0.35/(1.0/(fSpeedMHz)));
        `$INSTANCE_NAME`_g_nFrameBufferSize = nNumberOfNeopixels*3;
        //Configure for 19.2 MHz operation
        `$INSTANCE_NAME`_Neo_BITCNT_Start();        //Counts bits in a byte//Sets bitrate frequency in number of clocks. Must be larger than largest of above two counter periods
        CY_SET_REG8(`$INSTANCE_NAME`_Neo_DPTH_D1_PTR, fCyclesOn+1);
        //Setup a DMA channel
        `$INSTANCE_NAME`_g_nDMA_Chan = `$INSTANCE_NAME`_DMA_DmaInitialize(`$INSTANCE_NAME`_DMA_BYTES_PER_BURST,
    `$INSTANCE_NAME`_DMA_REQUEST_PER_BURST, 
    HI16(`$INSTANCE_NAME`_DMA_SRC_BASE), 
    HI16(`$INSTANCE_NAME`_DMA_DST_BASE));
        if(pBuffer == NULL)
    ...

    Diese Spielregeln müssen akzeptieren. Jetzt wissen Sie, wo Sie sich beim Entwickeln Ihrer Funktionen inspirieren lassen können (am besten im ursprünglichen Projekt). Und ich würde es vorziehen, über die Details zu sprechen, wobei die vom Generator bereits verarbeitete Variante verwendet wird.

    Nach dem Generieren des Codes (unten beschrieben) wird diese Datei hier gespeichert:



    Und die Ansicht ist bereits perfekt lesbar. Bisher gibt es zwei Funktionen. Die erste initialisiert das System, die zweite beginnt mit der Datenübertragung vom Puffer zur LED-Zeile.

    Die Initialisierung betrifft alle Teile des Systems. Es gibt eine Initialisierung des im UDB-System enthaltenen Sieben-Bit-Zählers:

        NP_Neo_BITCNT_Start();        //Counts bits in a byte

    Es gibt eine Berechnung der Konstante, die in das D1-Register geladen werden sollte (ich erinnere daran, dass die Dauer jedes der dritten Bits festgelegt wird):

    unsignedchar fCyclesOn = (unsignedchar)(0.35/(1.0/(fSpeedMHz)));
        CY_SET_REG8(NP_Neo_DPTH_D1_PTR, fCyclesOn+1);
    

    Das Einrichten eines DMA-Blocks beansprucht den größten Teil dieser Funktion. Die Quelle ist ein Puffer, und der Empfänger ist der FIFO0 des UDB-Blocks (in Kilometerdatensätzen NP_Neo_DPTH_F0_PTR ). Der Autor dieser Einstellung befand sich in der Datenübertragungsfunktion. Meines Erachtens ist es jedoch zu verschwenderisch, alle Berechnungen für jede Übertragung durchzuführen. Vor allem, wenn man bedenkt, dass eine der Aktionen in der Funktion sehr, sehr umfangreich wirkt.

    //work out cycles required at specified clock speed...
        NP_g_pFrameBuffer = NULL;
        NP_g_nFrameBufferSize = nNumberOfNeopixels*3;
        //Setup a DMA channel
        NP_g_nDMA_Chan = NP_DMA_DmaInitialize(NP_DMA_BYTES_PER_BURST, 
    NP_DMA_REQUEST_PER_BURST, HI16(NP_DMA_SRC_BASE), HI16(NP_DMA_DST_BASE));
    	...    
        NP_g_nDMA_TD = CyDmaTdAllocate();
        CyDmaTdSetConfiguration(NP_g_nDMA_TD, NP_g_nFrameBufferSize, CY_DMA_DISABLE_TD, TD_INC_SRC_ADR | TD_AUTO_EXEC_NEXT);
        CyDmaTdSetAddress(NP_g_nDMA_TD, LO16((uint32)NP_g_pFrameBuffer), LO16((uint32)NP_Neo_DPTH_F0_PTR));
        CyDmaChSetInitialTd(NP_g_nDMA_Chan, NP_g_nDMA_TD);
    

    Die zweite Funktion vor dem Hintergrund der ersten ist die Höhe des Lakonismus. Nur die erste wird in der Initialisierungsphase aufgerufen, wenn die Geschwindigkeitsanforderungen völlig frei sind. Während des Arbeitens ist es besser, Prozessorzyklen nicht mit etwas anderem zu verschwenden:

    voidNP_Update(){
        if(NP_g_pFrameBuffer)
        {
            CyDmaChEnable(NP_g_nDMA_Chan, 1);
        }
    }
    

    Es gibt offensichtlich nicht genügend Funktionen für das Arbeiten mit mehreren Puffern (um doppelte Pufferung bereitzustellen), aber im Allgemeinen geht die Erörterung der API-Funktionalität über den Rahmen des Artikels hinaus. Nun geht es vor allem darum, zu zeigen, wie Software-Support zur entwickelten Firmware hinzugefügt wird. Jetzt wissen wir, wie es geht.

    Projektgenerierung


    Der gesamte Firmware-Teil ist also fertig, die API wird hinzugefügt. Was ist als Nächstes zu tun? Wählen Sie den Menüpunkt Build-> Application generieren .



    Wenn alles gut geht, können Sie die Registerkarte Ergebnisse öffnen und die Datei mit der Erweiterung rpt anzeigen .



    Es zeigt, wie viel Systemressourcen für die Implementierung des Firmware-Teils aufgewendet werden.





    Wenn ich die Ergebnisse mit denen des ursprünglichen Projekts vergleiche, wird es mir im Herzen wärmer.

    Gehen Sie jetzt zur Registerkarte Quelle und beginnen Sie mit dem Softwareteil zu arbeiten. Dies ist jedoch schon trivial und bedarf keiner besonderen Erklärung.



    Fazit


    Hoffentlich haben die Leser aus diesem Beispiel etwas Neues und Interessantes über die praktische Arbeit mit UDB-Blöcken gelernt. Ich habe versucht, mich sowohl auf die spezifische Aufgabe (LED-Steuerung) als auch auf die Entwurfsmethodik zu konzentrieren, da ich einige Aspekte verstehen musste, die für Fachleute offensichtlich sind. Ich habe versucht, sie zu markieren, während die Erinnerungen an die Suche frisch waren. Was das gelöste Problem angeht, so waren die Zeitdiagramme, die ich bekam, nicht so perfekt wie der Autor des ursprünglichen Designs, aber sie passten perfekt in die Toleranzen, die in der Dokumentation für die LEDs definiert sind, und die Systemressourcen waren erheblich geringer.

    Tatsächlich ist dies nur ein Teil der gefundenen Nichtstandardinformationen. Aus den meisten Materialien kann es scheinen, dass UDB nur mit seriellen Daten funktioniert. Dies ist jedoch nicht der Fall. Anwendungshinweis gefunden, der kurz zeigt, wie Sie Daten antreiben und parallel schalten können. Es könnte möglich sein, spezifische Beispiele zu betrachten, die auf diesen Informationen basieren (obwohl der FX2LP, ein anderer Controller von Cypress, zu überschatten ist, keinen Erfolg haben wird: PSoC hat eine niedrigere USB-Bus-Geschwindigkeit).

    Ich habe Ideen im Kopf, wie man das „Firmware“ -Problem eines 3D-Druckers lösen kann, der mich schon lange gequält hat. Dort werden Unterbrechungen für Schrittmotoren ausgeführt, die nur einen wahnsinnigen Prozentsatz der Prozessorzeit verschlingen. Im Allgemeinen habe ich viel über Unterbrechungen und Prozessorzeit in dem Artikel über das MAX-Echtzeitbetriebssystem gesprochen. Es gibt Schätzungen, dass es für die Wartung von Schrittmotoren möglich ist, alle Schutzräume vollständig der UDB zu überlassen, so dass der Prozessor eine reine Rechenaufgabe hat, ohne befürchten zu müssen, dass dies keine Zeit dafür ist.

    Über diese Dinge kann jedoch nur spekuliert werden, wenn sich das Thema als interessant herausstellt.

    Jetzt auch beliebt: