Obsah
1. Programovatelné IO na mikrořadičích RP2040 – komunikace s PIO a ovládání PIO
2. Krátké zopakování z minula: ovládání LED připojené na GPIO přes PIO
3. Využití druhého registru Y pro řízení střídy
4. Demonstrační příklad: využití registru Y pro řízení střídy při blikání LED
5. Explicitní zápis nekonečné programové smyčky s řízením GPIO
6. Demonstrační příklad: explicitní zápis nekonečné programové smyčky
7. Využití direktiv wrap a wrap_target
8. Demonstrační příklad: direktivy wrap a wrap_target namísto explicitně zapsané smyčky
9. Nastavení střídy z hlavního programu přes FIFO
10. Konfigurace střídy ještě před spuštěním blikání LED
11. Demonstrační příklad: konfigurace střídy před spuštěním hlavní smyčky
12. Realizace neblokujícího čtení z FIFO
13. Neblokující čtení z fronty s využitím výchozí hodnoty
14. Demonstrační příklad: prohození významu registrů X a Y
15. Průběžná změna střídy kdykoli za běhu PIO programu
16. Demonstrační příklad: průběžná změna střídy za běhu PIO programu
17. Realizace PWM řízeného přes PIO
18. Příloha: varianty všech dnešních příkladů pro LED připojenou na GPIO 25
19. Repositář s demonstračními příklady
1. Programovatelné IO na mikrořadičích RP2040 – komunikace s PIO a ovládání PIO
V úvodních dvou článcích o programovatelném IO (PIO) implementovaném na mikrořadičích RP2040 [1] a [2] jsme si popsali jak celou architekturu PIO, tak i způsob přímého ovládání vstupně-výstupních pinů (GPIO) s využitím programů, které mohou být vykonávány jednotlivými stavovými stroji, které PIO tvoří. V těchto programech jsme využili některé instrukce z instrukčního souboru PIO. Konkrétně se jednalo o instrukce SET (nastavení pinů či nastavení vybraného interního registru) a JMP (nepodmíněné i podmíněné skoky). Známe i pseudoinstrukci NOP a navíc i způsob definice počtu cyklů, které mají být vykonány před dokončením instrukce. Naše PIO programy prozatím pracovaly velmi jednoduše: stavový stroj neustále opakoval tu samou sekvenci instrukcí s předem nastavenou frekvencí.
Ovšem v praxi je nutné s PIO nějakým způsobem komunikovat a ovládat ho z hlavního programu, který běží přímo na procesorových jádrech čipu RP2040 (ARM nebo RISC-V). Například budeme potřebovat realizovat softwarovou verzi sběrnice I2C, asynchronní sériový port UART nebo pulsně-šířkovou modulaci (PWM). V takových případech je nutné do PIO (resp. přesněji řečeno do jednotlivých stavových strojů) předávat data posílaná na sběrnici/port, číst přijatá data nebo měnit bitový vzorek v případě PWM. Teoreticky již víme, jak PIO ovládat – využít lze FIFO (fronty) a k nim připojené posuvné registry ISR (input shift register) a OSR (output shift register). Existuje však ještě jeden způsob ovládání – „vnucení“ nové instrukce stavovému stroji. V dnešním článku si ukážeme použití FIFO a registrů ISR a OSR. Může se zdát, že se jedná o složitou technologii, jak však uvidíme v praxi, je využití FIFO poměrně elegantní a jednoduché.
2. Krátké zopakování z minula: ovládání LED připojené na GPIO přes PIO
V této kapitole si připomeneme, jakým způsobem jsme vlastně přes PIO (resp. přes stavový stroj) ovládali LED připojenou na zvolený port. Realizovali jsme blikání této LED. Ve skutečnosti je to poměrně snadné – nastavíme příslušný GPIO na jedničku a po uplynutí předem známého počtu cyklů tento GPIO vynulujeme. Opět počkáme nějaký počet cyklů a můžeme celý program zopakovat. Nejjednodušší způsob (i když zdaleka nikoli nejelegantnější) je založen na instrukcích SET a na pseudoinstrukci NOP (což je vhodně zakódované „čekání“):
import rp2
from machine import Pin
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink_10hz():
set(pins, 1) [19]
nop() [19]
nop() [19]
nop() [19]
nop() [19]
set(pins, 0) [19]
nop() [19]
nop() [19]
nop() [19]
nop() [19]
sm = rp2.StateMachine(0, blink_1hz, freq=2000, set_base=Pin(8))
sm.active(1)
Sofistikovanější řešení spočívá ve využití programové smyčky realizované instrukcí JMP. Tato instrukce totiž (kromě dalších variant) dokáže snížit hodnotu interního registru X o jedničku a provést skok v případě, že se ještě nedosáhlo nuly. V programu je realizována dvojice smyček, které obsahují pouze „zpožděnou“ operaci NOP
import rp2
from machine import Pin
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink_1hz():
set(pins, 1)
set(x, 31) [6]
label("delay_high")
nop() [29]
jmp(x_dec, "delay_high")
set(pins, 0)
set(x, 31) [6]
label("delay_low")
nop() [29]
jmp(x_dec, "delay_low")
sm = rp2.StateMachine(0, blink_1hz, freq=2000, set_base=Pin(8))
sm.active(1)
V tomto případě může být mnohem čitelnější si uvést program tak, jak by se napsal přímo v assembleru a nikoli nepřímo v MicroPythonu:
set pins, 1
set X, 31 [6]
delay_high: nop [29]
jmp X--, delay_high
set pins, 0
set X 31 [6]
delay_low: nop [29]
jmp X--, delay_low
Resp. pro úplnost musíme dodat i direktivy .wrap a .wrap_target, které nám umožní realizovat nekonečnu smyčku (MicroPython tyto direktivy vkládá automaticky):
.wrap_target
set pins, 1
set X, 31 [6]
delay_high: nop [29]
jmp X--, delay_high
set pins, 0
set X 31 [6]
delay_low: nop [29]
jmp X--, delay_low
.wrap
3. Využití druhého registru Y pro řízení střídy
Výše uvedená dvojice programů sice skutečně dokázala blikat LED s využitím PIO, ovšem použité řešení je založeno na konstantách, které jsou do zdrojového kódu „zadrátovány“. To například znamená, že není možné (jednoduše) měnit střídu, tedy poměr časů, v nichž je LED rozsvícena či naopak zhasnuta. Jedno z možných řešení spočívá v náhradě dvou zpožďovacích smyček za smyčku jedinou. Uvnitř této smyčky budeme kontrolovat hodnotu počitadla (tedy registru X) a pokud překročí nastavenou mez, provede se zhasnutí LED. A změnou této meze, která bude uložena ve druhém pomocném registru Y, bude možné ovlivňovat střídu v poměrech od 0:32 až po 32:32:
.wrap_target
set X, 31 # počitadlo smyčky
set Y, 16 # hraniční hodnota pro přepnutí pinu s připojenou LED
set pins, 1 # nastavit pin (rozsvítit LED)
blink_loop: jmp X!=Y, skip # když X!=Y přeskočit další instrukci
set pins, 0 # vynulovat pin (zhasnout LED)
skip: nop [29] # zpožďovací mechanismus
jmp X--, blink_loop
.wrap
4. Demonstrační příklad: využití registru Y pro řízení střídy při blikání LED
Program pro PIO (stavový stroj), který jsme viděli v předchozí kapitole, je možné snadno přepsat o MicroPythonu, i když je nutné poznamenat, že výsledný kód již není tak čitelný, jako originál (a to zejména kvůli nutnosti definovat návěští formou volání funkce):
import rp2
from machine import Pin
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink1():
set(x, 31) # počitadlo smyčky
set(y, 16) # hraniční hodnota pro přepnutí pinu
set(pins, 1) # nastavit pin
label("blink_loop")
jmp(x_not_y, "skip") # když x!=y přeskočit další instrukci
set(pins, 0) # vynulovat pin
label("skip")
nop() [29] # zpožďovací mechanismus
jmp(x_dec, "blink_loop") # a opakujeme x-krát
sm = rp2.StateMachine(0, blink1, freq=2000, set_base=Pin(8))
sm.active(1) # spustit stavový stroj
Tento program do RPi Pico můžeme přenést přes Minicom, s využitím IDE Thonny atd. Následuje ukázka jeho přenosu a spuštění přes Minicom:
5. Explicitní zápis nekonečné programové smyčky s řízením GPIO
Prozatím jsme celý program pro PIO měli strukturovaný takovým způsobem, že se díky použití direktiv .wrap a .wrap_target celá série instrukcí neustále opakovala. Ovšem ve chvíli, kdy budeme potřebovat měnit střídu resp. přesněji řečeno ji konfigurovat z hlavního programu, je situace nepatrně složitější. Budeme totiž chtít, aby se instrukce set Y, střída neopakovala, ale zavolal se jen jednou. Tato instrukce tedy musí být uvedena mimo opakující se smyčku. Jedno z možných řešení spočívá v tom, že direktivy .wrap a .wrap_target vůbec nepoužijeme a namísto nich realizujeme vlastní nekonečnou programovou smyčku s využitím instrukce JMP bez podmínky. Celý program pro PIO se tak stane nepatrně složitějším, ale stále se jedná o poměrně dobře čitelný a pochopitelný kód:
set Y, 16 # hraniční hodnota pro přepnutí pinu s připojenou LED
endless_loop: # začátek nekonečné smyčky
set X, 31 # počitadlo smyčky
set pins, 1 # nastavit pin (rozsvítit LED)
blink_loop: jmp X!=Y, skip # když X!=Y přeskočit další instrukci
set pins, 0 # vynulovat pin (zhasnout LED)
skip: nop [29] # zpožďovací mechanismus
jmp X--, blink_loop
jmp endless_loop # opakovat vnější nekonečnou smyčku
6. Demonstrační příklad: explicitní zápis nekonečné programové smyčky
Opět si můžeme program napsaný přímo v assembleru PIO transformovat do MicroPythonu. Prozatím budeme registr Y nastavovat funkcí set(y, 16), která se přeloží na instrukci SET Y, 16, ovšem později provedeme takové úpravy, které zajistí nastavení registru Y přes FIFO a registr OSR (Output Shift Register):
import rp2
from machine import Pin
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink2():
set(y, 16) # hraniční hodnota pro přepnutí pinu
label("endless_loop") # cíl skoku
set(x, 31) # počitadlo smyčky
set(pins, 1) # nastavit pin
label("blink_loop")
jmp(x_not_y, "skip") # když x!=y přeskočit další instrukci
set(pins, 0) # vynulovat pin
label("skip")
nop() [29] # zpožďovací mechanismus
jmp(x_dec, "blink_loop") # a opakujeme x-krát
jmp("endless_loop")
sm = rp2.StateMachine(0, blink2, freq=2000, set_base=Pin(8))
sm.active(1) # spustit stavový stroj
7. Využití direktiv wrap a wrap_target
Ve skutečnosti je ve světě RP2040 idiomatičtější namísto explicitně zapsané programové smyčky realizované nepodmíněným skokem JMP stále používat direktivy .wrap a .wrap_target, ovšem s tím, že .wrap_target nebude umístěna na začátku programu, ale na to místo, od něhož se má celá sekvence instrukcí PIO znovu opakovat. Jedná se tak vlastně o poněkud odlišně deklarovanou nekonečnou programovou smyčku (lišit se bude i její časování, ale jen nepatrně, což nám prozatím nebude vadit). Nová struktura programu pro PIO tedy může vypadat následovně:
set Y, 16 # hraniční hodnota pro přepnutí pinu s připojenou LED
.wrap_target
set X, 31 # počitadlo smyčky
set pins, 1 # nastavit pin (rozsvítit LED)
blink_loop: jmp X!=Y, skip # když X!=Y přeskočit další instrukci
set pins, 0 # vynulovat pin (zhasnout LED)
skip: nop [29] # zpožďovací mechanismus
jmp X--, blink_loop
.wrap
8. Demonstrační příklad: direktivy wrap a wrap_target namísto explicitně zapsané smyčky
Víme již, že MicroPython automaticky vkládá direktivu .wrap_target na začátek celého PIO programu a naopak direktivu .wrap na jeho konec. Toto chování je většinou žádoucí, ovšem v případě potřeby můžeme direktivy specifikovat ručně, a to přesně na těch místech v programu, do nichž mají být umístěny. Pro tyto účely se používají funkce wrap_target() a wrap() zapisované do Pythonovské funkce s dekorátorem rp2.asm_pio:
import rp2
from machine import Pin
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink3():
set(y, 16) # hraniční hodnota pro přepnutí pinu
wrap_target() # cíl skoku
set(x, 31) # počitadlo smyčky
set(pins, 1) # nastavit pin
label("blink_loop")
jmp(x_not_y, "skip") # když x!=y přeskočit další instrukci
set(pins, 0) # vynulovat pin
label("skip")
nop() [29] # zpožďovací mechanismus
jmp(x_dec, "blink_loop") # a opakujeme x-krát
wrap()
sm = rp2.StateMachine(0, blink3, freq=2000, set_base=Pin(8))
sm.active(1) # spustit stavový stroj
9. Nastavení střídy z hlavního programu přes FIFO
Nyní se konečně dostáváme k ústřednímu tématu dnešního článku – jakým způsobem je možné změnit konfiguraci (resp. přesněji řečeno změnit chování) PIO programu, a to před jeho spuštěním nebo i při jeho běhu. Jedná se o velmi často řešenou problematiku, protože vlastně jakýkoli netriviální PIO program buď vyžaduje nějaká data pro zpracování (viz již zmíněnou I2C sběrnici nebo UART) nebo naopak nějaká data produkuje (sběrnice či port v režimu čtení). Pro tyto účely se používají FIFO, tj. jednosměrné nebo obousměrné fronty, které propojují jednotlivé stavové stroje s vlastním mikrořadičem. Připomeňme si, jak vlastně vypadá způsob propojení PIO s mikrořadičem:
Obrázek 1: Jeden z bloků PIO (druhý má totožnou architekturu)
Zdroj: datasheet k čipu RP2040, dostupné na https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf.
Jedna z FIFO je připojena k posuvnému registru OSR (Output Shift Register) a druhá fronta k posuvnému registru ISR (Input Shift Register). Fronty i posuvné registry jsou pojmenovány tak, jak jsou používány z pohledu mikrořadiče, tj. data jsou posílána z mikrořadiče do TX FIFO (TX=transfer) a ukládána do OSR (výstupního posuvného registru). A naopak mikrořadič čte data z RX FIFO (RX=receive), do které se data nasouvají přes ISR (vstupní posuvný registr). To nám bude při programování PIO působit „logické kolize“, protože vstupní data budeme číst z OSR a naopak výstupní data posílat do registru ISR:
Obrázek 2: Výstupní FIFO a registr OSR
Zdroj: datasheet k čipu RP2040, dostupné na https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf.
Obrázek 3: Vstupní FIFO a registr ISR
Zdroj: datasheet k čipu RP2040, dostupné na https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf.
10. Konfigurace střídy ještě před spuštěním blikání LED
Podívejme se nyní na způsob konfigurace střídy, kterou provedeme ještě před spuštěním blikání LED. Co to znamená? Ještě před vstupem do hlavní (nekonečné) programové smyčky přečteme hodnotu z výstupní FIFO (výstupní je ovšem z pohledu mikrořadiče, zatímco z pohledu PIO je tato fronta vstupní) a necháme si tuto hodnotu přenést do registru OSR (Output Shift Register). V případě, že je FIFO prázdná, budeme čekat na zápis hodnoty; bude se tedy jednat o blokující operaci čtení:
pull block
Ve druhém kroku přeneseme celý obsah OSR (tedy všech 32 bitů) do interního registru Y:
mov Y, OSR
Zbytek programu již zůstane stejný:
.wrap_target
set X, 31 # počitadlo smyčky
set pins, 1 # nastavit pin (rozsvítit LED)
blink_loop: jmp X!=Y, skip # když X!=Y přeskočit další instrukci
set pins, 0 # vynulovat pin (zhasnout LED)
skip: nop [29] # zpožďovací mechanismus
jmp X--, blink_loop
.wrap
Celý program tedy bude vypadat takto:
pull block # blokující přečtení 32 bitů z FIFO s jejich zápisem do OSR
mov Y, OSR # přenos všech 32 bitů z OSR do interního registru Y
.wrap_target
set X, 31 # počitadlo smyčky
set pins, 1 # nastavit pin (rozsvítit LED)
blink_loop: jmp X!=Y, skip # když X!=Y přeskočit další instrukci
set pins, 0 # vynulovat pin (zhasnout LED)
skip: nop [29] # zpožďovací mechanismus
jmp X--, blink_loop
.wrap
Pokud nyní příslušný PIO program spustíme, bude čekat na zápis 32bitové hodnoty do výstupní FIFO. Jak je to zařízeno v MicroPythonu si ukážeme v navazující kapitole.
11. Demonstrační příklad: konfigurace střídy před spuštěním hlavní smyčky
Program napsaný v assembleru PIO si nyní přepíšeme do MicroPythonu:
import rp2
from machine import Pin
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink4():
pull(block) # přečtení hodnoty z FIFO s čekáním, uložení do OSR
mov(y, osr) # přesun hodnoty z OSR do registru Y
wrap_target() # cíl skoku
set(x, 31) # počitadlo smyčky
set(pins, 1) # nastavit pin
label("blink_loop")
jmp(x_not_y, "skip") # když x!=y přeskočit další instrukci
set(pins, 0) # vynulovat pin
label("skip")
nop() [29] # zpožďovací mechanismus
jmp(x_dec, "blink_loop") # a opakujeme X-krát
wrap()
sm = rp2.StateMachine(0, blink4, freq=2000, set_base=Pin(8))
sm.active(1) # spustit stavový stroj
Po spuštění – inicializaci – stavového stroje se zdánlivě nebude nic dít (LED nebude blikat). PIO program totiž čeká na hodnotu zapsanou do výstupní FIFO (původně je totiž FIFO prázdná). Tento zápis se provede velmi jednoduše:
sm.put(10) # prahová hodnota, která se pošle do FIFO
12. Realizace neblokujícího čtení z FIFO
Víme již, jak nastavit parametr či parametry před spuštěním hlavní smyčky, která bude ovládat GPIO (vstupně-výstupní piny). Ovšem v praxi je taktéž nutné měnit parametry za běhu této smyčky. V našem konkrétním případě to znamená požadavek na to, aby se mohla měnit střída blikání LED v průběhu tohoto blikání. Teoreticky tedy potřebujeme z FIFO vyčíst novou hodnotu, ovšem bez čekání na tuto hodnotu v případě, že žádná nová hodnota nebyla poslána. Jinými slovy – pokud hlavní procesor (jádro) nepošle nový parametr, budeme chtít, aby LED stále blikala na základě dříve natavené střídy. Samotné čtení z FIFO bez čekání je teoreticky dobře realizovatelné, protože můžeme použít modifikátor noblock:
pull block # blokující přečtení 32 bitů z FIFO s jejich zápisem do OSR
mov Y, OSR # přenos všech 32 bitů z OSR do interního registru Y
.wrap_target
pull noblock # neblokující pokus o přečtení 32 bitů z FIFO, ovšem bez čekání na hodnotu
POZOR: nefunkční řešení!!!
mov Y, OSR # přenos všech 32 bitů z OSR do interního registru Y
set X, 31 # počitadlo smyčky
set pins, 1 # nastavit pin (rozsvítit LED)
blink_loop: jmp X!=Y, skip # když X!=Y přeskočit další instrukci
set pins, 0 # vynulovat pin (zhasnout LED)
skip: nop [29] # zpožďovací mechanismus
jmp X--, blink_loop
.wrap
Ovšem takto naznačené řešení ve skutečnosti nebude plně funkční, protože i když je pull noblock neblokující operace (tedy nečeká na data poslaná do FIFO), stále tato operace musí „něco“ zapsat do registru OSR, z něhož následně tuto hodnotu vyčteme. Je tomu tak z toho důvodu, že čtení z OSR je destruktivní operací – ve skutečnosti se totiž nejedná o pouhé čtení, ale o vysunutí bitů z registru a doplnění nulami (zleva či zprava, podle nastavení). Toto řešení tedy budeme muset nepatrně opravit.
13. Neblokující čtení z fronty s využitím výchozí hodnoty
Ve skutečnosti provádí instrukce pull noblock následující operaci:
- V případě, že se ve frontě (FIFO) nachází alespoň jedna hodnota, je tato hodnota z fronty přečtena a předána do posuvného registruOSR.
- V případě, že je fronta prázdná, je do posuvného registruOSR uložena hodnota z interního registru X. To znamená, že se vlastně jedná o stejnou operaci, jakou provede mov OSR, X.
Pro nás je užitečný především druhý bod, protože ten nám zaručí, že pokud bude v registru X uložena střída (tedy prakticky hodnota 0 až 31), budeme moci provést instrukci pull noblock, která automaticky naplní OSR buď novou hodnotou zapsanou „zvenku“, nebo původní hodnotou, která se již používá. A díky tomu, že OSR bude vždy naplněn správnou hodnotou, nebude nám vadit, že jeho čtení je vlastně destruktivní operací.
14. Demonstrační příklad: prohození významu registrů X a Y
Aby bylo možné v plné míře využít neblokující operaci pull noblock, musíme v našem PIO programu prohodit význam interních registrů X a Y. Je tomu tak z toho důvodu, že vlastnosti těchto registrů nejsou plně symetrické a jejich rozdíly se projeví právě u operace pull v případě, že ve FIFO nejsou uložena žádná data.
V původní verzi PIO programu sloužil registr Y pro uložení střídy a registr X byl použit jako počitadlo programové smyčky:
pull block # blokující přečtení 32 bitů z FIFO s jejich zápisem do OSR
mov Y, OSR # přenos všech 32 bitů z OSR do interního registru Y
.wrap_target
set X, 31 # počitadlo smyčky
set pins, 1 # nastavit pin (rozsvítit LED)
blink_loop: jmp X!=Y, skip # když X!=Y přeskočit další instrukci
set pins, 0 # vynulovat pin (zhasnout LED)
skip: nop [29] # zpožďovací mechanismus
jmp X--, blink_loop
.wrap
Nová verze PIO programu použití těchto registrů prohodí. Všechny modifikované instrukce (resp. přesněji řečeno jejich operandy) jsou podtrženy:
pull block # blokující přečtení 32 bitů z FIFO s jejich zápisem do OSR
mov X, OSR # přenos všech 32 bitů z OSR do interního registru X
.wrap_target
set Y, 31 # počitadlo smyčky
set pins, 1 # nastavit pin (rozsvítit LED)
blink_loop: jmp X!=Y, skip # když X!=Y přeskočit další instrukci
set pins, 0 # vynulovat pin (zhasnout LED)
skip: nop [29] # zpožďovací mechanismus
jmp Y--, blink_loop
.wrap
Zdrojový kód upraveného demonstračního příkladu vypadá následovně:
import rp2
from machine import Pin
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink5():
pull(block) # přečtení hodnoty z FIFO s čekáním, uložení do OSR
mov(x, osr) # přesun hodnoty z OSR do registru X
wrap_target() # cíl skoku
set(y, 31) # počitadlo smyčky
set(pins, 1) # nastavit pin
label("blink_loop")
jmp(x_not_y, "skip") # když X!=Y přeskočit další instrukci
set(pins, 0) # vynulovat pin
label("skip")
nop() [29] # zpožďovací mechanismus
jmp(y_dec, "blink_loop") # a opakujeme Y-krát
wrap()
sm = rp2.StateMachine(0, blink5, freq=2000, set_base=Pin(8))
sm.active(1) # spustit stavový stroj
sm.put(10) # prahová hodnota, která se pošle do FIFO
15. Průběžná změna střídy kdykoli za běhu PIO programu
Nyní nám zbývá poslední krok – pokus o průběžné čtení z fronty (FIFO). V případě, že fronta obsahuje nové hodnoty, je první z těchto hodnot přesunuta do registru OSR a následně do X, kde bude použita jako nová hodnota střídy. Pokud bude fronta prázdná, přesune se do registru OSR aktuální obsah registru X, což povede k tomu, že bude použita nová hodnota střídy:
pull block # blokující přečtení 32 bitů z FIFO s jejich zápisem do OSR
mov X, OSR # přenos všech 32 bitů z OSR do interního registru X
.wrap_target
pull noblock # zkusíme přečíst novou poslanou hodnotu, ale nečekáme na ni
# pokud hodnota nebyla poslána, doplní se hodnota registru X
mov X, OSR # přenos všech 32 bitů z OSR do interního registru X
set Y, 31 # počitadlo smyčky
set pins, 1 # nastavit pin (rozsvítit LED)
blink_loop: jmp X!=Y, skip # když X!=Y přeskočit další instrukci
set pins, 0 # vynulovat pin (zhasnout LED)
skip: nop [29] # zpožďovací mechanismus
jmp Y--, blink_loop
.wrap
16. Demonstrační příklad: průběžná změna střídy za běhu PIO programu
Podívejme se nyní na variantu demonstračního příkladu, která nám umožňuje měnit střídu blikání LED kdykoli za běhu PIO programu, tedy i po spuštění příslušné programové smyčky:
import rp2
from machine import Pin
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink6():
pull(block) # přečtení hodnoty z FIFO s čekáním, uložení do OSR
mov(x, osr) # přesun hodnoty z OSR do registru X
wrap_target() # cíl skoku
pull(noblock) # zkusíme přečíst novou poslanou hodnotu, ale nečekáme na ni
# pokud hodnota nebyla poslána, doplní se hodnota registru X
mov(x, osr) # přesun hodnoty z OSR do registru X
set(y, 31) # počitadlo smyčky
set(pins, 1) # nastavit pin
label("blink_loop")
jmp(x_not_y, "skip") # když X!=Y přeskočit další instrukci
set(pins, 0) # vynulovat pin
label("skip")
nop() [29] # zpožďovací mechanismus
jmp(y_dec, "blink_loop") # a opakujeme Y-krát
wrap()
sm = rp2.StateMachine(0, blink6, freq=2000, set_base=Pin(8))
sm.active(1) # spustit stavový stroj
sm.put(10) # prahová hodnota, která se pošle do FIFO
Nyní je možné kdykoli spustit příkaz, který do FIFO pošle novou hodnotu v rozsahu 0 až 31:
sm.put(1) time.sleep(1) sm.put(10) time.sleep(1) sm.put(20) ... ... ...
17. Realizace PWM řízeného přes PIO
Zvýšením frekvence hodinového signálu, jímž bude řízen stavový stroj, můžeme realizovat PWM neboli pulzně šířkovou modulaci. Frekvence musí být tak vysoká, aby ji nebylo možné postřehnout okem. Samotný PIO program se nebude nijak měnit, protože v jeho zdrojovém kódu se frekvence nenastavuje:
import rp2
from machine import Pin
import time
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink7():
pull(block) # přečtení hodnoty z FIFO s čekáním, uložení do OSR
mov(x, osr) # přesun hodnoty z OSR do registru X
wrap_target() # cíl skoku
pull(noblock) # zkusíme přečíst novou poslanou hodnotu, ale nečekáme na ni
# pokud hodnota nebyla poslána, doplní se hodnota registru X
mov(x, osr) # přesun hodnoty z OSR do registru X
set(y, 31) # počitadlo smyčky
set(pins, 1) # nastavit pin
label("blink_loop")
jmp(x_not_y, "skip") # když X!=Y přeskočit další instrukci
set(pins, 0) # vynulovat pin
label("skip")
nop() [29] # zpožďovací mechanismus
jmp(y_dec, "blink_loop") # a opakujeme Y-krát
wrap()
Frekvenci nastavíme při konstrukci objektu, který představuje (z pohledu MicroPythonu) stavový stroj. Pokusme se například nastavit 200 kHz:
sm = rp2.StateMachine(0, blink6, freq=200000, set_base=Pin(8)) sm.active(1) # spustit stavový stroj
Nyní je již možné postupně měnit intenzitu svitu LED, například následujícím způsobem:
for duty_cycle in range(0, 32):
time.sleep(0.3)
sm.put(duty_cycle) # prahová hodnota, která se pošle do FIFO
for duty_cycle in range(31, 0, -1):
time.sleep(0.3)
sm.put(duty_cycle) # prahová hodnota, která se pošle do FIFO
18. Příloha: varianty všech dnešních příkladů pro LED připojenou na GPIO 25
Všechny demonstrační příklady popsané v předchozích kapitolách byly navrženy takovým způsobem, aby ovládaly LED připojenou na vstupně-výstupní pin číslo 8 (což není standardní způsob připojení). V této příloze jsou uvedeny všechny příklady v nepatrně odlišné konfiguraci, v níž je LED připojena ke GPIO číslo 25, což je standardní pin používaný v RPi Pico bez Wifi modulu:
import rp2
from machine import Pin
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink1():
set(x, 31) # počitadlo smyčky
set(y, 16) # hraniční hodnota pro přepnutí pinu
set(pins, 1) # nastavit pin
label("blink_loop")
jmp(x_not_y, "skip") # když x!=y přeskočit další instrukci
set(pins, 0) # vynulovat pin
label("skip")
nop() [29] # zpožďovací mechanismus
jmp(x_dec, "blink_loop") # a opakujeme x-krát
sm = rp2.StateMachine(0, blink1, freq=2000, set_base=Pin(25))
sm.active(1) # spustit stavový stroj
import rp2
from machine import Pin
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink2():
set(y, 16) # hraniční hodnota pro přepnutí pinu
label("endless_loop") # cíl skoku
set(x, 31) # počitadlo smyčky
set(pins, 1) # nastavit pin
label("blink_loop")
jmp(x_not_y, "skip") # když x!=y přeskočit další instrukci
set(pins, 0) # vynulovat pin
label("skip")
nop() [29] # zpožďovací mechanismus
jmp(x_dec, "blink_loop") # a opakujeme x-krát
jmp("endless_loop")
sm = rp2.StateMachine(0, blink2, freq=2000, set_base=Pin(25))
sm.active(1) # spustit stavový stroj
import rp2
from machine import Pin
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink3():
set(y, 16) # hraniční hodnota pro přepnutí pinu
wrap_target() # cíl skoku
set(x, 31) # počitadlo smyčky
set(pins, 1) # nastavit pin
label("blink_loop")
jmp(x_not_y, "skip") # když x!=y přeskočit další instrukci
set(pins, 0) # vynulovat pin
label("skip")
nop() [29] # zpožďovací mechanismus
jmp(x_dec, "blink_loop") # a opakujeme x-krát
wrap()
sm = rp2.StateMachine(0, blink3, freq=2000, set_base=Pin(25))
sm.active(1) # spustit stavový stroj
import rp2
from machine import Pin
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink4():
pull(block) # přečtení hodnoty z FIFO s čekáním, uložení do OSR
mov(y, osr) # přesun hodnoty z OSR do registru Y
wrap_target() # cíl skoku
set(x, 31) # počitadlo smyčky
set(pins, 1) # nastavit pin
label("blink_loop")
jmp(x_not_y, "skip") # když x!=y přeskočit další instrukci
set(pins, 0) # vynulovat pin
label("skip")
nop() [29] # zpožďovací mechanismus
jmp(x_dec, "blink_loop") # a opakujeme X-krát
wrap()
sm = rp2.StateMachine(0, blink4, freq=2000, set_base=Pin(25))
sm.active(1) # spustit stavový stroj
sm.put(10) # prahová hodnota, která se pošle do FIFO
import rp2
from machine import Pin
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink5():
pull(block) # přečtení hodnoty z FIFO s čekáním, uložení do OSR
mov(x, osr) # přesun hodnoty z OSR do registru X
wrap_target() # cíl skoku
set(y, 31) # počitadlo smyčky
set(pins, 1) # nastavit pin
label("blink_loop")
jmp(x_not_y, "skip") # když X!=Y přeskočit další instrukci
set(pins, 0) # vynulovat pin
label("skip")
nop() [29] # zpožďovací mechanismus
jmp(y_dec, "blink_loop") # a opakujeme Y-krát
wrap()
sm = rp2.StateMachine(0, blink5, freq=2000, set_base=Pin(25))
sm.active(1) # spustit stavový stroj
sm.put(10) # prahová hodnota, která se pošle do FIFO
import rp2
from machine import Pin
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink6():
pull(block) # přečtení hodnoty z FIFO s čekáním, uložení do OSR
mov(x, osr) # přesun hodnoty z OSR do registru X
wrap_target() # cíl skoku
pull(noblock) # zkusíme přečíst novou poslanou hodnotu, ale nečekáme na ni
# pokud hodnota nebyla poslána, doplní se hodnota registru X
mov(x, osr) # přesun hodnoty z OSR do registru X
set(y, 31) # počitadlo smyčky
set(pins, 1) # nastavit pin
label("blink_loop")
jmp(x_not_y, "skip") # když X!=Y přeskočit další instrukci
set(pins, 0) # vynulovat pin
label("skip")
nop() [29] # zpožďovací mechanismus
jmp(y_dec, "blink_loop") # a opakujeme Y-krát
wrap()
sm = rp2.StateMachine(0, blink6, freq=2000, set_base=Pin(25))
sm.active(1) # spustit stavový stroj
sm.put(10) # prahová hodnota, která se pošle do FIFO
import rp2
from machine import Pin
import time
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink6():
pull(block) # přečtení hodnoty z FIFO s čekáním, uložení do OSR
mov(x, osr) # přesun hodnoty z OSR do registru X
wrap_target() # cíl skoku
pull(noblock) # zkusíme přečíst novou poslanou hodnotu, ale nečekáme na ni
# pokud hodnota nebyla poslána, doplní se hodnota registru X
mov(x, osr) # přesun hodnoty z OSR do registru X
set(y, 31) # počitadlo smyčky
set(pins, 1) # nastavit pin
label("blink_loop")
jmp(x_not_y, "skip") # když X!=Y přeskočit další instrukci
set(pins, 0) # vynulovat pin
label("skip")
nop() [29] # zpožďovací mechanismus
jmp(y_dec, "blink_loop") # a opakujeme Y-krát
wrap()
sm = rp2.StateMachine(0, blink6, freq=200000, set_base=Pin(25))
sm.active(1) # spustit stavový stroj
for duty_cycle in range(0, 32):
time.sleep(0.3)
sm.put(duty_cycle) # prahová hodnota, která se pošle do FIFO
for duty_cycle in range(31, 0, -1):
time.sleep(0.3)
sm.put(duty_cycle) # prahová hodnota, která se pošle do FIFO
19. Repositář s demonstračními příklady
Zdrojové kódy všech prozatím popsaných demonstračních příkladů určených pro MicroPython běžící na čipech RP2040 s architekturou Cortex-M0 byly uloženy do repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs. V tabulce zobrazené níže jsou odkazy na jednotlivé příklady:
20. Odkazy na Internetu
- Zápis funkcí obsahujících instrukce Thumb a Thumb-2 v MicroPythonu
https://www.root.cz/clanky/zapis-funkci-obsahujicich-instrukce-thumb-a-thumb-2-v-micropythonu/ - Zápis funkcí obsahujících instrukce Thumb a Thumb-2 v MicroPythonu (2)
https://www.root.cz/clanky/zapis-funkci-obsahujicich-instrukce-thumb-a-thumb-2-v-micropythonu-2/ - Zápis funkcí obsahujících instrukce Thumb a Thumb-2 v MicroPythonu (dokončení)
https://www.root.cz/clanky/zapis-funkci-obsahujicich-instrukce-thumb-a-thumb-2-v-micropythonu-dokonceni/ - Překlad funkcí přímo do nativního kódu MicroPythonem
https://www.root.cz/clanky/preklad-funkci-primo-do-nativniho-kodu-micropythonem/ - MicroPython ve webovém prohlížeči: lehkotonážní varianta k Pyodide
https://www.root.cz/clanky/micropython-ve-webovem-prohlizeci-lehkotonazni-varianta-k-pyodide/ - Programmable IO
https://docs.micropython.org/en/latest/rp2/tutorial/pio.html - Introduction to the PIO (Programmable Input Output) of the RP2040
https://tutoduino.fr/en/pio-rp2040-en/ - MicroPython examples: PIO
https://github.com/raspberrypi/pico-micropython-examples/tree/master/pio - PIO: Wikipedia CZ (pozor: jedná se o něco jiného!)
https://cs.wikipedia.org/wiki/PIO - RP2040 (Wikipedia)
https://en.wikipedia.org/wiki/RP2040 - Maximising MicroPython speed
https://docs.micropython.org/en/latest/reference/speed_python.html - Online ARM converter
https://armconverter.com/?disasm - The 3 different code emitters
https://www.kickstarter.com/projects/214379695/micro-python-python-for-microcontrollers/posts/664832 - The 3 different code emitters, part 2
https://www.kickstarter.com/projects/214379695/micro-python-python-for-microcontrollers/posts/665145 - Fast Filters for the Pyboard
https://github.com/peterhinch/micropython-filters - How to load 32 bit constant from assembler with @micropython.asm_thumb
https://forum.micropython.org/viewtopic.php?f=21&t=12931&sid=25de8871fa9cfcf8cafb6318f9d8ba3a - Pi pico, micropython.asm_thumb: ADR Rd, <label> and LDR Rd, <label> not implemented?
https://github.com/orgs/micropython/discussions/12257 - MicroPython documentation
https://docs.micropython.org/en/latest/index.html - Inline assembler for Thumb2 architectures
https://docs.micropython.org/en/latest/reference/asm_thumb2_index.html - Inline assembler in MicroPython
https://docs.micropython.org/en/latest/pyboard/tutorial/assembler.html#pyboard-tutorial-assembler - MCU market turns to 32-bits and ARM
http://www.eetimes.com/document.asp?doc_id=1280803 - Cortex-M0 Processor (ARM Holdings)
http://www.arm.com/products/processors/cortex-m/cortex-m0.php - Cortex-M0+ Processor (ARM Holdings)
http://www.arm.com/products/processors/cortex-m/cortex-m0plus.php - ARM Processors in a Mixed Signal World
http://www.eeweb.com/blog/arm/arm-processors-in-a-mixed-signal-world - RISCové mikroprocesory s komprimovanými instrukčními sadami
https://www.root.cz/clanky/riscove-mikroprocesory-s-komprimovanymi-instrukcnimi-sadami/ - RISCové mikroprocesory s komprimovanými instrukčními sadami (2)
https://www.root.cz/clanky/riscove-mikroprocesory-s-komprimovanymi-instrukcnimi-sadami-2/ - ARM Architecture (Wikipedia)
https://en.wikipedia.org/wiki/ARM_architecture - Cortex-M0 (Wikipedia)
https://en.wikipedia.org/wiki/ARM_Cortex-M0 - Cortex-M0+ (Wikipedia)
https://en.wikipedia.org/wiki/ARM_Cortex-M#Cortex-M0.2B - Improving ARM Code Density and Performance
New Thumb Extensions to the ARM Architecture Richard Phelan - The ARM Processor Architecture
http://www.arm.com/products/processors/technologies/instruction-set-architectures.php - Thumb-2 instruction set
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0344c/Beiiegaf.html - Introduction to ARM thumb
http://www.eetimes.com/discussion/other/4024632/Introduction-to-ARM-thumb - ARM, Thumb, and ThumbEE instruction sets
http://www.keil.com/support/man/docs/armasm/armasm_CEGBEIJB.htm - An Introduction to ARM Assembly Language
http://dev.emcelettronica.com/introduction-to-arm-assembly-language - Processors – ARM
http://www.arm.com/products/processors/index.php - The ARM Instruction Set
http://simplemachines.it/doc/arm_inst.pdf - The Thumb instruction set
http://apt.cs.manchester.ac.uk/ftp/pub/apt/peve/PEVE05/Slides/05_Thumb.pdf - Why Learn Assembly Language?
http://www.codeproject.com/Articles/89460/Why-Learn-Assembly-Language - Is Assembly still relevant?
http://programmers.stackexchange.com/questions/95836/is-assembly-still-relevant - Why Learning Assembly Language Is Still a Good Idea
http://www.onlamp.com/pub/a/onlamp/2004/05/06/writegreatcode.html - Assembly language today
http://beust.com/weblog/2004/06/23/assembly-language-today/ - Assembler: Význam assembleru dnes
http://www.builder.cz/rubriky/assembler/vyznam-assembleru-dnes-155960cz - Assembler pod Linuxem
http://phoenix.inf.upol.cz/linux/prog/asm.html - AT&T Syntax versus Intel Syntax
https://www.sourceware.org/binutils/docs-2.12/as.info/i386-Syntax.html - Linux Assembly website
http://asm.sourceforge.net/ - Raspberry Pi Pico Variants – A Detailed Comparison
https://circuitdigest.com/article/raspberry-pi-pico-variants-comparison - Raspberry Pi Pico 2 vs Original Pico: What’s New?
https://www.digikey.cz/en/maker/blogs/2024/raspberry-pi-pico-2-vs-original-pico-whats-new - pio-uart
https://github.com/Sympatron/pio-uart - Datasheet čipu RP2040
https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf