【Berry Pi】Verwenden der PIO-Zustandsmaschinenprogrammierung

1. Beschreibung

        Bei der Mikrocontroller-Programmierung kann die Anbindung an andere Hardware sehr einfach oder sehr anspruchsvoll sein. Wenn andere Hardware (z. B. Sensoren) Standardbussysteme wie I2C, SPI oder UART unterstützt, müssen Sie diese nur anschließen und Daten über das implementierte Bussystem lesen/schreiben. Wenn Sie andere Hardware anschließen müssen, müssen Sie präzise Zeitsignale implementieren, mehrere Pins zum Senden und Empfangen von Daten verwenden und die Signale interpretieren.

        Sie können diese Timing-Überlegungen in einfachem C programmieren, aber das bedeutet, dass Sie sehr sorgfältig programmieren müssen, da Sie an Prozessortaktzyklen gebunden sind und die Timing-Auswirkungen jeder Codezeile verstehen müssen.

        Um dieser Herausforderung gerecht zu werden, verfügt der Raspberry Pico über eine einzigartige Hardware-Erweiterung: PIO, kurz für Programmable Input/Output. Die PIOs sind als 4 unabhängige Zustandsmaschinen implementiert. Jede Zustandsmaschine ist mit einer FIFO-Warteschlange verbunden, um Daten mit dem Hauptprogramm auszutauschen. Zusätzlich zu Warteschlangen können Stata-Maschinen DMS verwenden und auf alle GPIOs zugreifen, jedoch nicht auf andere Hardware oder Protokolle.

        Die Pico-Community verwendet PIOs zur Ausgabe von Audio oder Video, zur Verbindung mit proprietären LCD-Systemen oder zur Verbindung mit anderer Hardware, die ein ganz bestimmtes Protokoll erfordert.

        Um Ihnen den Einstieg in PIO zu erleichtern, bietet dieser Artikel eine kurze Einführung: Verständnis der Grundlagen des Hardware-Teils, Verständnis, wie ein PIO-Programm aussieht und wie es mit einem C-Hauptprogramm interagiert, und schließlich ein tiefer Einblick in die Programmiersprache PIO .

Dieser Beitrag erschien ursprünglich auf meinem Blog .

2. Warum brauchen Sie PIO?

Wenn Sie eine Schnittstelle zu Hardware herstellen möchten, die keine Verbindung zu USB, I2C, SPI oder einem auf dem UART unterstützten Protokoll herstellen kann, müssen Sie zum Lesen und Schreiben von GPIO sehr zeitbeschränkten Code schreiben. Wenn die externe Hardware, die Sie anschließen, jedoch eine sehr langsame Datenübertragung erfordert, müssen Sie mit Interrupts oder langen Wartezeiten umgehen.

Im offiziellen C SDK-Leitfaden heißt es eindeutig, dass es unpraktisch wird, IRQs für Protokolle zu verwenden, die 1000x langsamer sind als der Hauptprozess, da die CPU sonst die meiste Zeit warten muss. Oder am anderen Ende des Spektrums verfügen Sie möglicherweise über eine Hardware mit hohen Zyklen und zwingen den Mikrocontroller, keinen Taktzyklus zu verpassen. Diese beiden Herausforderungen zwingen Sie im Grunde in die gleiche Situation: Ihre gesamten CPU-Ressourcen werden für die Verarbeitung oder das Warten auf die Verwendung nur einer einzigen externen Hardware aufgewendet. Sie können Pico für nichts anderes verwenden.

Das PIO-Subsystem bietet eine neue Lösung für dieses Problem. Oberflächlich betrachtet ähnelt es einem Field Programmable Gate Array (FGPA), indem es eine Programmierumgebung für den Aufbau komplexer Logik bereitstellt. Aber man entwirft einen integrierten Schaltkreis nicht in Software, und dann muss man Mikrocontroller-Software schreiben, die mit diesem Zustand interagiert. Stattdessen können Sie bis zu 4 verschiedene Zustandsmaschinen direkt programmieren. Jede Zustandsmaschine hat freien Zugriff auf GPIO-Pins zum Lesen und Schreiben von Daten, sie kann Daten vom Prozessor oder anderen DMAs puffern und den Prozessor über Interrupts oder Abfragen über ihre Berechnungsergebnisse informieren.

3. PIO-Beispielprogramm: blinkende LED

        Definieren wir eine einfache PIO-Startroutine, die eine LED blinken lässt. Wir müssen zwei Dateien definieren: eine PIO-Datei, die Assembler-ähnlichen Code enthält, und eine normale C-Datei mit Funktionen.main

        Schauen wir uns zunächst die PIO-Datei an. Eine PIO-Datei besteht aus zwei Teilen: einem Teil, der die PIO-Anweisungen definiert, und einem Teil, der die Funktionen enthält, die die PIO-Routinen für Programme verfügbar machen. Das Grundlayout ist wie folgt:programc-sdkmain

.program hello
...% c-sdk {
...
%} 

4. PIO-Programm

        Das PIO-Programm selbst ist tatsächlich in Assembler geschrieben, genauer gesagt in einer Teilmenge von Assembler-Anweisungen. Zum Ein- und Ausschalten der LED genügt folgende Vorgehensweise:

.program helloset pindirs, 1loop:
  set pins, 1 [31]
  set pins, 0 [31]
  jmp loop 

Lassen Sie uns das Programm Zeile für Zeile analysieren.

  • Zeile 1: Diese Anweisung beginnt mit der Deklaration des PIO-Programms. Es benötigt einen Bezeichner, der beim Kompilieren und Verknüpfen verwendet wird.program
  • Zeile 3: Die Direktive ist eine Mehrzweckanweisung. Diese Zeile bedeutet, dass wir alle konfigurierten Setup-Pins als Ausgang festlegenSET
  • Zeile 5: Bei dieser Deklaration handelt es sich um eine Freiformbezeichnung, mit der Teile eines größeren Programms gruppiert werden.loop
  • Zeile 6: Stellen Sie den konfigurierten LED-Pin so ein, dass er insgesamt 32 Taktzyklen lang einen hohen Wert ausgibt. Jede PIO-Anweisung wird in einem Taktzyklus ausgeführt und der zusätzliche 5-Bit-Wert kann zum Warten auf zusätzliche Zyklen verwendet werden.
  • Zeile 7: Stellen Sie den konfigurierten LED-Pin so ein, dass er insgesamt 32 Taktzyklen lang einen niedrigen Wert ausgibt.
  • Zeile 8: Wir kehren zum zuvor definierten Label zurück.JMPloop

5. C-SDK-Bindung

        Um dieses Programm auszuführen, müssen auch die C-SDK-Bindungen definiert werden. Im Wesentlichen ist eine Bindung eine Funktion innerhalb des PIO-Programms. Während der Kompilierung nimmt der Compiler es auf und gibt eine Header-Datei aus, die Sie in Ihr Hauptprogramm integrieren können.

        Fügen Sie den folgenden Code hinzu. Detaillierte Anweisungen finden Sie weiter unten in diesem Artikel.

% c-sdk {
static inline void hello_program_init(PIO pio, uint sm, uint offset, uint pin) {
  // 1. Define a config object
  pio_sm_config config = hello_program_get_default_config(offset);
  // 2. Set and initialize the output pins
  sm_config_set_set_pins(&config, pin, 1);
  // 3. Apply the configuration & activate the State Machine
  pio_sm_init(pio, sm, offset, &config);
  pio_sm_set_enabled(pio, sm, true);
}
%}

Sechs, das Hauptprogramm

        Zum Schluss fügen wir alles zur Hauptprogrammdatei hinzu.

#include <stdio.h>
#include <stdbool.h>
#include <pico/stdlib.h>
#include <hardware/pio.h>
#include <hello.pio.h>#define LED_BUILTIN 25;int main() {
  stdio_init_all();  PIO pio = pio0;
  uint state_machine_id = 0;
  uint offset = pio_add_program(pio, &hello_program);  hello_program_init(pio, state_machine_id, offset, LED_BUILTIN, 1);  while(1) {
    //do nothing
  }
} 

        Hier sehen wir folgende Details:

  • Zeile 4: Um die PIO-Zustandsmaschine verwenden zu können, müssen wir diesen speziellen Header einbinden
  • Zeile 5: Diese Anweisung enthält das während der Kompilierung zusammengestellte PIO-Programm. Dadurch wird die zuvor definierte Funktion verfügbar gemacht und der Zeiger auf das Programm als definiert. Beachten Sie die Namenskonvention!hello_program_inithello_program
  • Zeile 12: Pico verfügt über zwei verschiedene Zustandsmaschinenbusse und wir müssen die Zustandsmaschine als zu einem von ihnen gehörend definieren.
  • Zeile 13: Wir definieren die ID der Zustandsmaschine (4-Bit-Wert)
  • Zeile 14: Diese Anweisung weist dynamischen Speicher zu, der den Zustandsmaschinencode enthält. Es gibt einen Speicheroffsetwert zurück, den wir an die Initialisierung des Zustandsautomaten übergeben
  • Zeile 16: Wir initialisieren und starten das Programm

7. Technische Details zu PIO

         Nachdem wir uns die Beispiele angesehen haben, gehen wir nun auf die technischen Details ein.

7.1 PIO-Komponenten

        Pico stellt zwei PIO-Blöcke mit 4 Zustandsmaschinen in jedem Block bereit. Jede Zustandsmaschine stellt die folgenden Komponenten bereit.

  • TX FIFO/RX FIFO: 32-Bit-Werte vom/zum Hauptprogramm empfangen oder senden
  • Eingabeschieberegister (ISR)/Ausgabeschieberegister (OSR): Diese Register enthalten flüchtige Daten für den direkten Austausch zwischen der Zustandsmaschine und dem Hauptprogramm.
  • Scratch-Register: Flags und, diese 32-Bit-Register ermöglichen es Ihnen, alle anderen Daten zu speichern, die von der Zustandsmaschine benötigt werden.xy
  • Konfigurierbarer Taktteiler: Die Taktperiode von Pico beträgt 133 MHz, einstellbar bis zu 2000 Hz durch 16-Bit-Wert
  • Flexible GPIO-Zuordnung: Das Herzstück des Pico ist die Möglichkeit, auf GPIO-Pins zuzugreifen. Jede Zustandsmaschine kann vier verschiedene Sätze von GPIOs verwenden (Eingang, Ausgang, Satz, Sideset).
  • DMA-Zugriff: Direkter Zugriff auf den Speicher ohne Beteiligung des Host-Prozessors
  • IRQ-Flags: 8 globale Flags können gesetzt oder gelöscht werden, Interrupts sind für jede Zustandsmaschine und jedes Hauptprogramm sofort zugänglich

7.2 PIO-Assemblersprache

        Um PIO zu programmieren, müssen Sie einen speziellen Dialekt der Assemblersprache verwenden. Im Beispielprogramm haben wir gesehen, wie man Logikpegel auf Pins anwendet und wie man eine einfache Schleife definiert. Es gibt nur 9 Befehle in der Assemblersprache und einige zusätzliche Anweisungen für die Codestruktur. Ich werde alle diese Anweisungen kurz beschreiben, aber eine vollständige Definition aller Anweisungen finden Sie in Abschnitt 3.3.2 der offiziellen Dokumentation .

        Da die Sprache so komprimiert ist, erfüllen mehrere Anweisungen mehrere Funktionen. Insbesondere die korrekte Verwendung von GPIO-Pins kann schwierig sein. Daher gruppiere ich die Anweisungen in verschiedene Arten von Funktionen.

7.3 Programmstruktur

        Um ein Programm im Allgemeinen zu erstellen, können die folgenden Befehle verwendet werden.

  • .program NAME– den Namen des Programms und den Namen der Header-Datei, die während der Kompilierung generiert wird, um Ihnen Zugriff auf die Zustandsmaschine im Hauptprogramm zu ermöglichen
  • .define NAME VALUE- Ähnlich wie bei einem C-Programm können Sie Konstanten der obersten Ebene definieren, die in der Zustandsmaschine sichtbar sind
  • LABEL:- Labels sind syntaktische Gruppierungen zusammengehöriger Aussagen. Sie können eine beliebige Beschriftung definieren und zu dieser zurückkehren
  • ; COMMENT- Alles nach dem Semikolon ist ein Kommentar
  • .wrap_targetund – Anweisungen zum wiederholten Ausführen eines Teils des PIO-Programms.wrap
  • .word- Speichern Sie rohe 16-Bit-Werte als Anweisungen im Programm (jede PIO-Anweisung ist ein 16-Bit-Wert).
  • .side_set COUNT (opt)– Dieser Befehl konfiguriert auch den SIDE-Pin für dieses Programm. Der COUNT-Wert ist die Anzahl der Bits, die von der Anweisung subtrahiert werden sollen, und der opt-Wert bestimmt, ob die Anweisung im PIO-Programm optional oder erforderlich ist. Bei Verwendung dieser Anweisung können andere Befehle an alle Ausdrücke angehängt werden, beispielsweise das Verschieben eines Bits von FIFO RX und das Setzen des SIDE-Pins auf den Logikpegel LOW.sideout x, 1 side 0OSR

Verschieben Sie Daten innerhalb eines Schieberegisters

  • in SOURCE count- Übertragen Sie Daten in den ISR, wo SOURCE sein oder als zählen kannXYOSRISR0...32
  • out DESTINATION count- Daten vom OSR zum Ziel übertragen, ,XYISR
  • mov DESTINATION, SOURCE- Daten von einer Quelle (, oder) zu einem Ziel (, oder verschieben XYOSRISRXYOSRISR) verschieben
  • set DESTIANTION, data- 5-Bit-Datenwert in DESTIANTION (, XY) schreiben

Verschieben Sie Daten zwischen dem Schieberegister und dem Hauptprogramm

  • pull- Laden Sie Daten vom TX-FIFO in das OSR
  • push- Daten vom ISR zum RX-FIFO übertragen und dann ISR löschen
  • irq INDEX op- Ändern Sie die IRQ-Nummer in „clear()“ oder „set( indexop=0op=1)“ .

Daten auf den GPIO-Pin schreiben

Stift setzen

  • set PINDIRS, 1- Definieren Sie den konfigurierten SET-Pin als Ausgangspin
  • set PINS, value- Schreiben Sie HIGH() oder LOW() auf den SET-Pinvalue=1value=1

Ausgangspin

  • mov PINS, SOURCE- Schreiben Sie von der Quelle (,,,) auf den Ausgangspin (,,, XYOSRISRXYOSRISR)

Daten von GPIO-Pins lesen

Stift setzen

  • set PINDIRS, 0- Definieren Sie den konfigurierten SET-Pin als Eingangspin

Eingangspin

  • mov DESTINATION, PINS- Schreiben Sie vom IN-Pin (,,, und OUT XYOSRISRPINS) auf das Ziel.

Bedingte Anweisungen

  • jmp CONDITION LABEL- Gehen Sie zu, wenn der folgende Typ wahr istLABELCONDITION
  • !(X|Y|OSRE)- wahr, wenn leer istXYOSR
  • X-- | Y--)– true, wenn das Scratch-Register leer ist, andernfalls wird das Scratch-Register dekrementiert.
  • PIN– True, wenn der Jump-Pin den Logikpegel hoch hat
  • wait POLARITY TYPE NUMBER- Weiterverarbeitung verzögern, bis die Polarität übereinstimmt.
  • pin NUMBER- Eingangspin
  • gpio NUMBER- Absolut nummerierte GPIOs
  • irq NUMBER- IRQ-Nummer (wenn Polarität 1 ist, IRQ-Nummer löschen)
  • nop- nichts tun

7.4 PIO-Konfiguration

PIO-Programme sind hochgradig konfigurierbar. Der c-sdk-Teil von Pico definiert eine Wrapper-Funktion, die vom Pico-Assembler kompiliert wird. Diese Funktion ist vom Hauptprogramm aus zugänglich und kann beliebige Argumente empfangen.

Sie können in dieser Funktion eine schwindelerregende Reihe von Aspekten konfigurieren – in der folgenden Liste werden alle Optionen kurz beschrieben.

  • Definieren Sie Eingangspins, Ausgangspins und Seitenpins
  • Definieren Sie einen speziellen Pin für den BefehlJMP
  • Initialisieren Sie die Richtung des Eingangspins
  • Konfigurieren Sie Schieberichtung, Autoload und Bitgröße (bis zu 32 Bit) für Eingangs- und Ausgangsschieberegister
  • Konfigurieren Sie ein Eingangsschieberegister als zusätzliches Ausgangsschieberegister und umgekehrt
  • Der Taktteiler wird auf die Standardtaktzeit von 133 MHz (16-Bit-Wert) angewendet, sodass Sie die PIO-Taktperiode auf 2000 Hz verkleinern können, z. B. 0,492 ms.

Um alle Konfigurationsmöglichkeiten bei der Nutzung von PIO zu haben, verwende ich gerne die folgende Vorlage. Nach dieser Vorlage konfiguriere ich einfach, was ich anpassen muss, oder entferne, was nicht.

static inline void __program_init(PIO pio, uint sm, uint offset, uint in_pin, uint in_pin_count, uint out_pin, uint out_pin_count, float frequency) {
  // 1. Define a config object
  pio_sm_config config = __program_get_default_config(offset);
  // 2. Set and initialize the input pins
  sm_config_set_in_pins(&config, in_pin);
  pio_sm_set_consecutive_pindirs(pio, sm, in_pin, in_pin_count, 1);
  pio_gpio_init(pio, in_pin);
  // 3. Set and initialize the output pins
  sm_config_set_out_pins(&config, out_pin, out_pin_count);
  pio_sm_set_consecutive_pindirs(pio, sm, out_pin, out_pin_count, 0);
  // 4. Set clock divider
  if (frequency < 2000) {
    frequency = 2000;
  }
  float clock_divider = (float) clock_get_hz(clk_sys) / frequency * 1000;
  sm_config_set_clkdiv(&config, clock_divider);
  // 5. Configure input shift register
  // args: BOOL right_shift, BOOL auto_push, 1..32 push_threshold
  sm_config_set_in_shift(&config, true, false, 32);
  // 6. Configure output shift register
  // args: BOOL right_shift, BOOL auto_push, 1..32 push_threshold
  sm_config_set_out_shift(&config, true, false, 32);
  // 7. Join the ISR & OSR
  // PIO_FIFO_JOIN_NONE = 0, PIO_FIFO_JOIN_TX = 1, PIO_FIFO_JOIN_RX = 2
  sm_config_set_fifo_join(&config, PIO_FIFO_JOIN_NONE);
  // 8. Apply the configuration
  pio_sm_init(pio, sm, offset, &config);
  // 9. Activate the State Machine
  pio_sm_set_enabled(pio, sm, true);
}

8. Fazit

        PIO ist eine programmierbare Ein-/Ausgabe-Zustandsmaschine für den Raspberry Pico, eine neuartige Lösung zum Anschluss beliebiger Hardware. Anstatt CPU-Zyklen im Leerlauf zu verschwenden oder umgekehrt immer von der PIN zu lesen und zu schreiben, übernimmt die Zustandsmaschine die schwere Arbeit der Interaktion mit jeglicher Hardware. Sie können für den Betrieb von 2000 Hz bis 133 MHz mit freiem Zugriff auf alle GPIO-Pins konfiguriert werden, die in jedem Taktzyklus gelesen und beschrieben werden können. Mithilfe einer vereinfachten Assembler-ähnlichen Sprache können Sie diese Zustandsmaschinen so programmieren, dass sie bestimmte Zeitbeschränkungen einhalten und Datenbits mit dem Hauptprogramm austauschen. Dieser Artikel zeigt, wie PIO funktioniert, indem er die Komponenten und alle ihre Programmiersprachenanweisungen auflistet. Schließlich haben wir eine Reihe von Konfigurationsmöglichkeiten für die Zustandsmaschine gesehen. Sie können bis zu 8 Zustandsmaschinen aufrufen, um mit dem Hauptprogramm zu arbeiten – was ist Ihr Anwendungsfall?

 

Guess you like

Origin blog.csdn.net/gongdiwudu/article/details/131912987