In de vorige artikelen in deze serie zijn we begonnen met het coderen van de Propeller 2 MCU en hebben we een LED laten branden met SPIN2. Nu zal ik een oplossing geven om hem te laten knipperen en daarna gaan we verder met de I/O-pinnen.

Een eenvoudige oplossing

Je kunt een eenvoudig 'uit - aan'-schema volgen. Daarvoor hebben we de volgende ingrediënten nodig:

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

De te nemen stappen zijn:

  • Zet de LED aan
  • Wacht 500ms
  • Zet de LED uit
  • Wacht 500ms
  • Herhaal

De code staat in figuur 1 en is te downloaden van de webpagina van dit artikel [1].

Parallax Propeller 2 and blinking an LED
Figuur 1: Code om een LED te laten knipperen.

Nadat we pinnen als output hebben gebruikt, zou de volgende stap zijn te laten zien hoe we ze als inputs kunnen gebruiken. Dat gaan we op dit moment niet doen, we komen daar later op terug. Het hebben van een soort seriële data-uitgang om met een PC te praten maakt het tonen van de status die we van een I/O-pin hebben gelezen een stuk leuker dan alleen maar een LED te laten knipperen. Bovendien kunnen we deze uitgang gebruiken om statusgegevens te versturen en wat code te debuggen. De volgende stop in deze serie zijn daarom de smart pins.

Slimme pinnen

Tegenwoordig noemen marketingteams producten graag 'smart' (b.v. 'smart city', 'smart data' en 'smart contracts'). Met de Smart Pins is het een ander verhaal, omdat ze veel meer kunnen zijn dan gewone  I/O-pinnen. Op andere MCU's heb je soms de mogelijkheid om voor één pin uit meerdere functies te kiezen. Sommige MCU's, zoals de ESP32, hebben een I/O-matrix die elk intern peripheral I/O-signaal naar elke pin kan leiden. In deze gevallen is een I/O-pin nog steeds gewoon een I/O-pin, de functies van bijvoorbeeld UART, SPI of ADC bevinden zich in een speciaal blok hardware dat gewoon met de pin zelf verbonden is. Met de Smart Pins op de Propeller 2 verandert dit, omdat de speciale peripherals niet langer speciale functieblokken in de MCU zijn die door een I/O-matrix worden geleid, maar geïntegreerd zijn, althans gedeeltelijk, in elke I/O-pin. Vandaar de term Smart Pin.

Gebruiker 'rayman' heeft op het Parallax Forum [2] goed werk verricht door de gemeenschap te voorzien van een overzicht van de smart pin internals. U kunt zijn post over de Propeller 2 en de afbeelding vinden op [3] of u kunt ook een kijkje nemen in figuur 2.

Smart pin overview.
Figuur 2: Smart Pin overzicht. (Bron: Rayman/Parallax, http://bit.ly/parallax-smartpin)

In het 'Smart Pin overzicht' neem ik een uittreksel over uit de Propeller 2 datasheet. U kunt zien dat één pin vele functies kan hebben. Op dit moment zijn we alleen geïnteresseerd in 11110* = async serial transmit om data uit te zenden en die later te debuggen. De eerste stap is om uit te zoeken hoe we de juiste mode voor de pin kunnen instellen en of dat kan met puur SPIN2 of dat we er een beetje assembly in moeten mengen.

UART configuratie

Dit is het punt waarop je je achter je oren begint te krabben als je voor de eerste keer met SPIN2 en de Propeller 2 werkt. De huidige datasheet is, min of meer, volledig. Maar alles goed lezen en begrijpen kan langer duren dan verwacht. Ons doel is een eenvoudige functie, een tx(), die een te verzenden karakter naar een pin stuurt die werkt als een UART. We zullen werken met 115200 baud, 8 databits, geen pariteit en slechts één stopbit. Het eerste wat we moeten doen is de pin instellen:

Stel de pin in op async serial transmit
Stel baudrate en databits in
Schakel de Smart Pin in

Deze drie eenvoudige stappen zorgen ervoor dat een pin een zendende UART-pin wordt. Omdat we de code later zullen aanpassen en hergebruiken, stoppen we die in een functie. Een functie bevat eenvoudigweg code of codefragmenten die vaak binnen een programma worden gebruikt. Dit vermijdt steeds weer kopiëren en plakken en maakt het ook mogelijk om onderhoud op één plaats te doen. We zullen een functie gebruiken en deze serial_start noemen. Deze heeft geen argumenten en vereenvoudigt de drie hierboven gegeven stappen. De pin die we gebruiken is momenteel hard-coded op pin 57 (een van de LED-pinnen die ook bereikbaar is op een van de randconnectoren zoals te zien is in figuur 3).
 

Pinheader location
Figuur 3: Locatie van de pinheader die toegang geeft tot de LED-pinnen.

Hier begint de functie met een PUB gevolgd door zijn naam en aan het eind lege haken, omdat er geen argumenten zijn.

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

Op regel 1 zien we -zoals hiervoor al aangegeven- de functie, of preciezer gezegd het functie-hoofd. De volgende regel stelt pin 57 in als async serial transmit, herkenbaar aan de 11110. Het eerste bit is altijd nul, de twee meest significante bits, hier '01', geven aan dat de pin wordt bestuurd door de GPIO of Smart Pin functie. We gebruiken hier de SPIN2 WRPIN-functie om onze stap één te realiseren. De volgende functie WXPIN schrijft, voor een smart pin in async seriële mode, de gewenste klokdeler en de te gebruiken databits. Om het eenvoudig te houden slaan we het deel met de fractie baudrate-deler nu even over. De waarde voor de baudrate kan worden berekend door systemclock/baudrate -hier 25 MHz/115200 baud- wat resulteert in ongeveer 217. Dit resultaat moet met 16 naar rechts worden verschoven. Voor het aantal bits dat moet worden overgezet, gebruiken we de formule (gewenst aantal bits-1) met als resultaat (8-1) bits.

Meer is er niet nodig om de transmissiesnelheid en de databits in te stellen. De volgende drie regels zien er anders uit dan de vorige code. We zien een kleine assembly-sectie, die enige uitleg behoeft. Met org kunt u een sectie met assembly-instructies starten. Het assembly-commando dirh zal de smart pin functies inschakelen, zoals nodig voor onze stap drie. Wat nu anders is, is hoe we kunnen aangeven aan welk pinnummer we werken, want dit moet beginnen met een '#'.

Het laatste regeleinde zal het assembly-gedeelte beëindigen. In dit speciale geval vormt deze tevens het einde van de functie zelf. We vermijden assembly liever, maar momenteel is er geen SPIN2-equivalent voor de dirh assembly-instructie. De geformatteerde en gemarkeerde code is te zien in figuur 4.

serial_start() code
Figuur 4: Code van serial_start().

Nu de pin in de juiste mode staat, kunnen we verder gaan en een functie instellen die een karakter verzendt en wacht tot dat klaar is. De functie kan grotendeels uit de datasheet [4], pagina 91 worden gehaald. We hebben gezien hoe we functies kunnen bouwen die geen argumenten hebben, wat betekent dat er niets aan wordt doorgegeven. Om een karakter te verzenden, zou het gunstig zijn als we aan de functie kunnen doorgeven wat we willen verzenden. Aangezien we momenteel zoveel mogelijk assembly proberen te vermijden, zullen we, waar mogelijk, de SPIN2-functies gebruiken in plaats van inline assembly.

Verzenden

Voor het verzenden maken we een tx functie die er bijna identiek uitziet als onze serial_start() functie, zoals u kunt zien in figuur 5.

tx function
Figuur 5: Code voor de functie tx.

Het zichtbare verschil, naast de naam, is het argument 'val' binnen de haakjes. 'val' zal het karakter bevatten dat moet worden afgedrukt. Binnen de functie zullen we eerst de waarde in het transmissieregister van pin 57 schrijven met behulp van het WYPIN-commando. Het volgende gedeelte bestaat weer uit een paar regels assembly-code. We moeten wachten tot de busy-flag voor de zender niet meer '1' is en de transmissie is voltooid. Volgens de datasheet moeten we eerst drie CPU-cycli wachten om de vlag op een consistente manier te kunnen lezen. Dit wordt bereikt door de waitx instructie met de #1 parameter, aangezien de uitvoering ervan twee cycli duurt + de hoeveelheid die voor de functie is opgegeven (hier één cyclus). De volgende regel is een label genaamd wait, in assembly is dit een plaats waar we later naar toe kunnen springen. RDPIN in assembly, zoals hier geschreven, leest de pin-status met carry. Dit wordt aangeduid met de WC aan het eind van het statement. Het carry-bit, dat hier dienst doet als bezettoon, is belangrijk omdat het aangeeft of de transmissie voltooid is.

RDPIN val, #57 WC leest de status inclusief hetcarry-bit in onze val waarde. Aangezien de inhoud momenteel wordt verzonden, kunnen we het geheugen van val hergebruiken om er de status van de smart pin in te lezen. Het laatste commando IF_C◦JMP◦#wait is een voorwaardelijke sprong; in basic zou het het beruchte GOTO-equivalent zijn gecombineerd met een IF-statement. Kort vertaald betekent het: Heb je iets gelezen met het carry-bit (hier busy-flag) gezet? Ga terug naar het wait-label en begin van daaruit opnieuw, of ga anders verder. Onze overdracht is klaar als het carry-bit niet meer gezet is, dus de functie zal doorlopen tot het einde en terugkeren naar waar hij was aangeroepen.

Assembleer de onderdelen

We kunnen nu de code assembleren en na elke pinwrite() een "0" of "1" zenden door tx("0") of tx("1") te gebruiken in onze code, zoals te zien in figuur 6.

 

Complete code
Figuur 6: Compleet geassembleerde code.

Om de output te kunnen oppikken, moet een USB-serieel-converter worden aangesloten. Voor dit doel hebben we onze vertrouwde Logic 16 genomen en de output van de LED, de seriële transmissie opgenomen en het resultaat getoond in figuur 7 en figuur 8.

Logic analyzer trace
Figuur 7: Logic analyzer spoor toont elke 500 ms een uitgezonden karakter.
 Zoomed trace
Figuur 8: Het ingezoomde spoor laat het uitgezonden karakter zien.


Maar hoe zit het met strings? Zou het niet leuk zijn om gewoon een print("Hello World") te doen en dat serieel te laten verzenden, zoals we dat in de Arduino-wereld doen? Ja, dat kan en dat doen we dus ook. De volgende stap in onze serie over de Propeller 2 bevat een introductie hierop.

Propeller 2: Overzicht van smart-pin functies:

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

 


Meer over Propeller 2 en aanverwante onderwerpen 

Wilt u meer weten over onderwerpen als de Propeller 2 van Parallax? Neem vandaag nog een Elektor-lidmaatschap en mis nooit meer een artikel, project of tutorial.