In den vorherigen Folgen dieser Serie ging es um Grundsätzliches der Propeller 2 MCU und die Ansteuerung einer LED via SPIN2. Jetzt geht es darum, diese LED zum Blinken zu bringen. Dabei werden Funktionen der IO-Pins gestreift.

Eine einfache Lösung

Am einfachsten ist sicher das Schema „Aus- und wieder Einschalten“. Dazu braucht es folgende Zutaten:

  • pinwrite()
  • repeat()
  • waitms()

Die notwendigen Schritte umfassen:

  • Einschalten der LED
  • Warten für 500 ms
  • Ausschalten der LED
  • Warten für 500 ms
  • Wiederholung des Vorhergehenden

Der Code sieht dann so aus, wie in Bild 1. Sie können ihn von der Elektor-Webseite zu diesem Artikel herunterladen.

Parallax Propeller 2 and blinking an LED
Bild 1: Code zum Blinken einer LED.

Nachdem die Pins als Ausgang verwendet wurden, stellt sich eigentlich die Frage, wie man sie als Eingänge verwenden kann. Dieses Thema wird später behandelt. Zunächst geht es darum, eine Art seriellen Datenausgang zu realisieren. Dieser soll Daten an einen PC schicken können, denn so kann man sich auch anzeigen lassen, welchen Status ein I/O-Pin hat. Das bringt hier mehr, als lediglich eine LED blinken zu lassen. Außerdem lässt sich dieser Ausgang zum Versenden von Statusdaten nutzen und ein eine Art Debugging für den Code zu ermöglichen. Der nächste Punkt dreht sich daher um sogenannte Smart Pins.

Smart Pins

Heutzutage drapieren Marketing-Teams gerne alle möglichen und unmöglichen Produktemit dem Etikett „smart“. Smart Pins aber sind nicht bloß Marketing-Speak. Sie können weit mehr als gewöhnliche IO-Pins. Bei MCUs gibt es häufiger die Möglichkeit, aus mehreren Funktionen für einen Pin zu wählen. Manche MCUs wie der ESP32 haben sogar eine IO-Matrix, die jedes interne IO-Signal auf jeden beliebigen Pin routen kann. Doch selbst hier bleibt ein IO-Pin immer noch ein IO-Pin. Die eigentlichen Funktionen wie UART, SPI oder ADC befinden sich in dedizierten Hardware auf dem Die, die auf diese Weise schlicht mit Pins verbunden werden. Die Smart Pins des Propeller 2 sind aber anders: Die integrierte Peripherie besteht eben nicht aus dedizierten Funktionsblöcken der MCU, die durch eine IO-Matrix geroutet werden. Stattdessen sind viele Funktionen zumindest teilweise direkt in jeden IO-Pin integriert. Die Bezeichnung Smart Pin ist daher wirklich gerechtfertigt.

Netterweise hat der User rayman im Parallax Forum einen tollen Job gemacht, indem er für die Community einen Überblick über Smart-Pin-Interna gegeben hat. Sein Beitrag findet sich samt Bild unter [3]. Sie können auch einen Blick auf Bild 2 werfen.

Smart pin overview.
Bild 2: Smart-Pin-Übersicht. (Source: Rayman/Parallax, https://bit.ly/3qPhqRk).

Der Kasten Smart-Pin-Funktionen enthält einen Auszug aus dem Datenblatt. Sie sehen, dass ein Pin viele Funktionen haben kann. Im Moment aber interessiert vor allem 11110* = asynchrones serielles Senden, da man so Daten für Debugging-Zwecke verschicken kann. Hierzu sollte klar sein, wie man den passenden Modus für den Pin einstellt und ob SPIN2 dazu reicht, oder ob eine Prise Assembler nötig ist.

UART-Konfiguration

Dies ist der Punkt, wo man unwillkürlich die Augenbrauen hebt, wenn man zum ersten Mal mit SPIN2 und Propeller 2 zu tun hat. Das Datenblatt ist relativ vollständig, doch alles zu lesen und richtig zu verstehen kann länger dauern. Erstes Ziel ist daher eine einfache Funktion tx(), die ein Zeichen an einen als UART operierenden Pin schickt. Die Parameter sind: 115200 Baud, 8 Daten-Bits, keine Parität und ein Stopp-Bit.

Zunächst muss man den Pin konfigurieren:

  • Pin auf async serial transmit setzen
  • Einstellung der Baudrate und der Daten-Bits
  • Aktivieren des Smart Pins

Mit diesen drei Schritten wird ein sendender UART-Pin realisiert. Da der Code später wiederverwendet und modifiziert werden soll, wird er in eine Funktion verpackt. Eine Funktion enthält einfach Code, der innerhalb eines Programms häufiger verwendet wird. Das vermeidet viel redundantes „Copy and Paste“ von Code und erlaubt es, die Änderungen an einer einzigen Stelle vorzunehmen. Die Funktion erhält den Namen serial_start. Sie nimmt keine Argumente entgegen und konzentriert sich auf die drei oben genannten Schritte. Der verwendete Ausgang ist fest als Pin◦57 kodiert (einer der am Rand zugänglichen LED-Pins, siehe Bild 3).

Pinheader location
Bild 3: Lage der Stiftleiste für den Zugriff auf die LED-Pins.

Die Funktion beginnt mit einem PUB, gefolgt von ihrem Namen und leeren geschweiften Klammern. Letzteres indiziert, dass keine Argumente übernommen werden.

PUB serial_start()
WRPIN( 57, %01_11110_0  )         'set async tx mode for txpin
WXPIN( 57, ((217<<16) + (8-1 )) ) 'set baud rate to sysclock/115200 and word size to 8 bits
org                               'start of assembly part
dirh #57
end ' end of assembly 
part
 

Zeile 1 enthält den oben erwähnten Funktionskopf. Die nächste Zeile setzt Pin 57 in den Modus für asynchrones serielles Senden (via 11110). Das erste Bit ist immer Null; die beiden höchstwertigen Bits (hier 01) bedeuten, dass der Pin als GPIO- oder Smart-Pin angesteuert werden soll. Dies wird mit der SPIN2-Funktion WRPIN im ersten Schritt erreicht. Die nächste Funktion WXPIN konfiguriert den gewünschten Takt-Teiler und die zu verwendenden Daten-Bits für einen Smart Pin im asynchronen seriellen Modus. Die Sache mit dem Teiler für die Baudrate wird zunächst vertagt. Kurz: Der Wert für die Baudrate ergibt sich als Quotient von systemclock / baudrate - hier 25 MHz / 115.200 bd ≈ 217. Das Ergebnis muss noch um 16 nach rechts verschoben werden. Für die Anzahl der zu übertragenden Bits gilt die Formel gewünschte◦Bits - 1, also 8 – 1 Bits.

Mehr braucht es nicht, um die Übertragungsgeschwindigkeit und die Daten-Bits einzustellen. Die nächsten drei Zeilen unterscheiden sich vom bisherigen Code. Der hier auftauchende Assembler-Code bedarf wohl zusätzlicher Erklärung. Mit org startet ein Abschnitt mit Assembler-Befehlen. Der Assembler-Befehl dirh aktiviert die Smart-Pin-Funktionen, wie es für den dritten Schritt braucht. Die Festlegung der Nummer des genutzten Pins sieht etwas anders aus, an sie mit einem # begonnen werden muss.

Der Assembler-Abschnitt wird mmit einem end in der letzten Zeile abgeschlossen, was in diesem speziellen Fall auch gleich als Ende der Funktion selbst wirkt. Es wäre schön, wenn man Assembler vermeiden könnte, aber derzeit gibt es keine SPIN2-Entsprechung für die Assembler-Anweisung dirh. Bild 4 zeigt den fertigen, formatierte und Code.

serial_start() code
Bild 4: Code von serial_start().

Da der Pin jetzt konfiguriert ist, kann man eine Funktion einrichten, die ein Zeichen sendet und wartet, bis sie fertig ist. Die Funktion kann größtenteils aus Seite◦91 des Datenblatts [4] entnommen werden. Die vorige Funktion kommt ohne übergebene Argumente aus. Doch wenn ein Zeichen gesendet werden soll, wäre es von Vorteil, wenn es der Sende-Funktion übergeben werden könnte. Um Assembler möglichst zu vermeiden, werden hierzu die SPIN2-Funktionen verwendet.

Datenübertragung

Sie Sende-Funktion tx sieht serial_start() recht ähnlich, wie in Bild 5 beweist.

tx function
Bild 5: Code für die Funktion.

Außer dem anderen Namen befindet sich hier das Argument val innerhalb der Klammern. Dieser Parameter enthält nach Übergabe (bzw. Funktionsaufruf) das zu sendende Zeichen. Im Funktionsrumpf wird der Wert des Parameters mit dem Befehl WYPIN in das Übertragungsregister von Pin◦57 kopiert. Nun folgen wieder ein paar Zeilen Assembler-Code. Es wird gewartet, bis das Busy-Flag für den Sender nicht mehr gesetzt ist und die Übertragung somit abgeschlossen ist. Laut Datenblatt muss man zunächst drei CPU-Zyklen warten, um das Flag konsistent zu lesen. Dies wird durch die Anweisung waitx mit dem Parameter #1 erreicht, da dessen Ausführung zwei Zyklen + den der Funktion übergebenen Wert (hier ein Zyklus) benötigt. Die nächste Zeile ist ein Label wait, zu dem später gesprungen werden kann. RDPIN (Assembler) liest den Pin-Status mit Übertrag und wird durch WC am Ende der Anweisung abgeschlossen. Das Carry-Bit dient hier als Busy-Flag und zeigt an, ob die Übertragung abgeschlossen ist oder nicht.
RDPIN val, #57 WC liest den Status inklusive Carry-Bit in val ein. Da der Inhalt von val gerade übertragen wird, lässt sich sein Speicherplatz dazu nutzen, um den Smart-Pin-Status dorthin zu kopieren. Beim letzten Befehl IF_C JMP◦#wait handelt es sich um einen bedingten Sprung. In BASIC entspricht das dem berüchtigten GOTO kombiniert mit einer IF-Anweisung. Der Code übersetzt in Klartext:

Wurde irgendein Wert gelesen, bei dem das Carry-Bit (hier Busy-Flag) gesetzt ist? Wenn ja: Springe zurück zum Wait-Label und beginne von dort aus erneut. Wenn nein: Weitermachen. Die Übertragung ist beendet, wenn das Carry-Bit nicht mehr gesetzt ist. Dann wird die Funktion bis zu ihrem Ende laufen und schließlich dorthin zurückspringen, wo sie aufgerufen wurde.

Alle Teile zusammen

Wir können nun den Code zusammenbauen und nach jedem pinwrite() eine „0“ oder „1“ übertragen, indem wir an diesen Stellen ein tx("0") oder ein tx("1") im Code einfügen (siehe Bild 6).

Complete code
Bild◦6: Kompletter Code zusammengesetzt.

Um die Ausgaben an einen PC zu übertragen, schließt man nun noch einen Seriell/USB-Konverter an. Ich habe das mit einem Logic◦16 erledigt und so die LED-Ansteuerung via serieller Übertragung aufgezeichnet. Das Ergebnis sehen Sie in den Bildern 7 und 8.

Logic analyzer trace
Bild 7: Der Logik-Analyzer registriert alle 500 ms ein gesendetes Zeichen.
 Zoomed trace
Bild 8: Gezoomter Trace mit dem übertragenen Zeichen.

Doch was ist mit Strings? Wäre es nicht schön, einfach ein print("Hello World") über die serielle Schnittstelle zu übertragen, wie es in der Arduino-Welt möglich ist? Auch das ist möglich. In der nächsten Folge geht es um solche Funktionen.

Propeller 2: Übersicht der Smart-Pin-Funktionen:

00000= smart pin off (default)

00001= long repository(P[12:10] != %101)

00010= long repository(P[12:10] != %101)

00011= long repository(P[12:10] != %101)

00001= DAC noise(P[12:10]= %101)

00010= DAC 16-bit dither, noise(P[12:10]= %101)

00011= DAC 16-bit dither, PWM(P[12:10]= %101)

00100*= pulse/cycle output

00101*= transition output

00110*= NCO frequency

00111*= NCO duty

01000*= PWM triangle

01001*= PWM sawtooth

01010*= PWM switch-mode power supply, V and I feedback

01011= periodic/continuous: A-B quadrature encoder

01100= periodic/continuous: inc on A-rise & B-high

01101= periodic/continuous: inc on A-rise & B-high / dec on A-rise & B-low

01110= periodic/continuous: inc on A-rise {/ dec on B-rise}

01111= periodic/continuous: inc on A-high {/ dec on B-high}

10000= time A-states

10001= time A-highs

10010= time X A-highs/rises/edges -or- timeout on X A-high/rise/edge

10011= for X periods, count time

10100= for X periods, count states

10101= for periods in X+ clocks, count time

10110= for periods in X+ clocks, count states

10111= for periods in X+ clocks, count periods

11000= ADC sample/filter/capture, internally clocked

11001= ADC sample/filter/capture, externally clocked

11010= ADC scope with trigger

11011*= USB host/device(even/odd pin pair = DM/DP)

11100*= sync serial transmit(A-data, B-clock)

11101= sync serial receive(A-data, B-clock)

11110*= async serial transmit(baudrate)

11111= async serial receive(baudrate)

* OUT signal overridden

 

Übersetzung: Dr. Thomas Scherer 


Mehr zu Propeller 2 und ähnlichen Themen

Möchten Sie mehr über Themen wie den Propeller 2 von Parallax erfahren? Schließen Sie noch heute eine Elektor-Mitgliedschaft ab und verpassen Sie keinen Artikel, kein Projekt und kein Tutorial mehr.