Obsah
1. Programovatelné IO na mikrořadičích RP2040 – praktické použití
2. Získání MicroPythonu pro konkrétní verzi Rapberry Pi Pica
4. Spuštění interaktivní smyčky s MicroPythonem na Raspberry Pi Pico
5. Připojení k Raspberry Pi Pico bez administrátorských práv
6. Zjištění základních vlastností Raspberry Pi Pico z MicroPythonu
7. Třída používaná pro ovládání stavových strojů PIO
8. Tvorba programu pro PIO (stavový stroj)
9. Pseudofunkce Pythonu určené pro naprogramování stavového stroje
10. Specifikace počtu zpožďovacích cyklů
12. Typický první ukázkový příklad při programování MCU: blikání LED
13. Naprogramování a načasování PIO pro blikání LED
14. Naprogramování funkce s ovládáním LED přes PIO
15. Ověření délky trvání jedné iterace PIO programu
16. Konstrukce a spuštění stavového stroje
18. Příloha: dekorátory v jazyce Python
19. Repositář s demonstračními příklady
1. Programovatelné IO na mikrořadičích RP2040 – praktické použití
Na úvodní článek o programování PIO na mikrořadičích Raspberry Pi Pico dnes navážeme. Pokusíme se propojit znalosti o PIO (interní architektura, propojení s mikrořadičem i s GPIO, instrukční soubor) s vlastnostmi MicroPythonu. Varianta interpretru MicroPythonu určená pro Raspberry Pi Pico totiž podporuje naprogramování a přímé řízení subsystému PIO, a to bez nutnosti používat další nástroje (PIO assembler atd.). To nám umožní zkombinovat možnosti poskytované vysokoúrovňovým programovacím jazykem se subsystémem PIO, což je vlastně subsystém bežící v reálném čase – instrukce můžeme načasovat přesně na jednotlivé cykly (a to může být velmi výhodné – nemusíme používat HW časovače ani čítače).
2. Získání MicroPythonu pro konkrétní verzi Rapberry Pi Pica
Prvním krokem, který musíme udělat, je získání MicroPythonu určeného pro konkrétní verzi Raspberry Pi Pico, kterou provozujete. Pokud tedy máte k dispozici původní Raspberry Pi Pico (1) bez Wifi modulu, použije se odlišný soubor, než u Raspberry Pi Pico W (s Wifi modulem) nebo u Raspberry Pi Pico 2. MicroPython pro všechny čtyři dnes dostupné varianty RPi Pico naleznete na stránce https://www.raspberrypi.com/documentation/microcontrollers/micropython.html, odkud si je můžete stáhnout. Nebo je možné použít tyto přímé odkazy:
- https://micropython.org/download/rp2-pico/rp2-pico-latest.uf2
- https://micropython.org/download/rp2-pico-w/rp2-pico-w-latest.uf2
- https://micropython.org/download/RPI_PICO2/RPI_PICO2-latest.uf2
- https://downloads.raspberrypi.com/micropython/mp_firmware_unofficial_latest.uf2
Ukažme si konkrétní příklad. Budu stahovat MicroPython přeložený pro variantu Raspberry Pico W:
$ wget https://micropython.org/download/rp2-pico-w/rp2-pico-w-latest.uf2
HTTP response 302 [https://micropython.org/download/rp2-pico-w/rp2-pico-w-latest.uf2]
Adding URL: https://micropython.org/download/RPI_PICO_W/RPI_PICO_W-latest.uf2
Saving 'rp2-pico-w-latest.uf2'
HTTP response 200 [https://micropython.org/download/RPI_PICO_W/RPI_PICO_W-latest.uf2]
rp2-pico-w-latest.uf 100% [=====================================================================================>] 1.64M --.-KB/s
[Files: 1 Bytes: 1.64M [2.74MB/s] Redirects: 1 Todo: 0 Errors: 0 ]
Tímto příkazem získáme soubor o velikosti přibližně 1,7 MB:
$ ls -l rp2-pico-w-latest.uf2 -rw-r--r--. 1 ptisnovs ptisnovs 1727488 Nov 29 17:45 rp2-pico-w-latest.uf2
Pro jistotu si nechte vypočítat otisk (hash) staženého souboru a zkontrolujte ho vůči vytištěné hodnotě:
$ sha256sum rp2-pico-w-latest.uf2 bf869821b59a13de3f7fa0c3cc1592f9af4bd41ce571d919c2577d47b6ce540e rp2-pico-w-latest.uf2
V případě, že si MicroPython stáhnete z výše uvedené stránky https://www.raspberrypi.com/documentation/microcontrollers/micropython.html, bude mít soubor odlišné jméno, ovšem naprosto stejný otisk (a pochopitelně i shodnou velikost):
$ sha256sum RPI_PICO_W-20241129-v1.24.1.uf2 bf869821b59a13de3f7fa0c3cc1592f9af4bd41ce571d919c2577d47b6ce540e RPI_PICO_W-20241129-v1.24.1.uf2
3. Instalace MicroPythonu
Vlastní instalace MicroPythonu na Raspberry Pi Pico je snadná. Tyto mikropočítače totiž mohou být připojeny přes USB k počítači/notebooku, přičemž USB může pracovat v několika režimech. Výchozí režim je založený na protokolu USB mass storage. V tomto režimu, který se nazývá bootloader mode, je možné do Raspeberry Pi Pico přesunovat soubory naprosto stejným způsobem, jakoby se jednalo o běžné paměťové zařízení připojené k USB (Flash disk, pevný disk, paměť fotoaparátu atd. atd.). Ihned po přesunu souboru s koncovkou .uf2 (a takovou koncovku má i soubor s MicroPythonem) se Raspberry Pi Pico od počítače odpojí a jeho USB se přepne do režimu emulujícího sériové rozhraní (to použijeme v dalších kapitolách).
Obrázek 1: Raspberry Pi Pico v režimu USB Mass Storage.
Obrázek 2: Raspberry Pi Pico po přenosu MicroPythonu a odpojení USB Mass Storage.
Seznam USB zařízení si můžete kdykoli vypsat příkazem lsusb:
$ lsusb Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 003 Device 002: ID 06cb:00f9 Synaptics, Inc. Bus 003 Device 003: ID 04f2:b74f Chicony Electronics Co., Ltd Integrated Camera Bus 003 Device 004: ID 8087:0033 Intel Corp. AX211 Bluetooth Bus 003 Device 010: ID 2e8a:0005 MicroPython Board in FS mode Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Podrobnější informace by bylo možné v tomto konkrétním případě získat příkazem:
$ lsusb -D /dev/bus/usb/003/010
kde 003 je číslo sběrnice a 010 číslo zařízení z předchozího výpisu.
Výpis by měl začínat takto:
Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 239 Miscellaneous Device bDeviceSubClass 2 [unknown] bDeviceProtocol 1 Interface Association bMaxPacketSize0 64 idVendor 0x2e8a MicroPython idProduct 0x0005 Board in FS mode bcdDevice 1.00 iManufacturer 1 MicroPython iProduct 2 Board in FS mode iSerial 3 e6616408434bbc2e
4. Spuštění interaktivní smyčky s MicroPythonem na Raspberry Pi Pico
MicroPython je, podobně jako klasický Python, vybaven interaktivní smyčkou REPL, což znamená, že interpret Pythonu očekává příkazy, které se ihned vykonají. Ovšem jak ovládat interpret, který běží na Raspberry Pi Pico? Budeme se k němu muset připojit, a to konkrétně s využitím protokolu, který se chová podobně jako vysokorychlostní sériová linka. Budeme tedy muset použít libovolnou aplikaci umožňující připojení a komunikaci přes sériovou linku. Pro jednoduchost použiji minicom, což je letitý, ale stále velmi dobře použitelný program.
Před připojením Raspberry Pi Pico k počítači si nejprve spusťte nástroj dmesg (bude vyžadovat práva administrátora). Po připojení Raspberry Pi Pica by se měly vypsat přibližně následující zprávy, přičemž důležitý je údaj z posledního řádku (zde je podtržený):
[1444104.506249] usb 3-7: new full-speed USB device number 63 using xhci_hcd [1444104.633015] usb 3-7: New USB device found, idVendor=2e8a, idProduct=0005, bcdDevice= 1.00 [1444104.633032] usb 3-7: New USB device strings: Mfr=1, Product=2, SerialNumber=3 [1444104.633038] usb 3-7: Product: Board in FS mode [1444104.633042] usb 3-7: Manufacturer: MicroPython [1444104.633045] usb 3-7: SerialNumber: e661640843610b2c [1444104.638506] cdc_acm 3-7:1.0: ttyACM0: USB ACM device
Vidíme, že na tomto konkrétním počítači a systému je připojené Raspberry Pi Pico dostupné (v režimu sériové linky) přes zařízení nazvané ttyACM0. To se ovšem může na různých počítačích/systémech lišit (a proto potřebujeme dmesg).
Připojení k MicroPythonu provedeme příkazem:
$ sudo minicom -D /dev/ttyACM0
Spojení by mělo být navázáno a objevit by se měla obrazovka s následujícími údaji:
Welcome to minicom 2.8 OPTIONS: I18n Compiled on Jan 25 2024, 00:00:00. Port /dev/ttyACM0, 11:48:15 Press CTRL-A Z for help on special keys MicroPython v1.24.1 on 2024-11-29; Raspberry Pi Pico W with RP2040 Type "help()" for more information. >>>
Nyní již můžeme s MicroPythonem komunikovat, což si ihned vyzkoušíme:
>>> help()
Vypsat by se mělo:
Welcome to MicroPython!
For online docs please visit http://docs.micropython.org/
For access to the hardware use the 'machine' module. RP2 specific commands
are in the 'rp2' module.
Quick overview of some objects:
machine.Pin(pin) -- get a pin, eg machine.Pin(0)
machine.Pin(pin, m, [p]) -- get a pin and configure it for IO mode m, pull mode p
methods: init(..), value([v]), high(), low(), irq(handler)
machine.ADC(pin) -- make an analog object from a pin
methods: read_u16()
machine.PWM(pin) -- make a PWM object from a pin
methods: deinit(), freq([f]), duty_u16([d]), duty_ns([d])
machine.I2C(id) -- create an I2C object (id=0,1)
methods: readfrom(addr, buf, stop=True), writeto(addr, buf, stop=True)
readfrom_mem(addr, memaddr, arg), writeto_mem(addr, memaddr, arg)
machine.SPI(id, baudrate=1000000) -- create an SPI object (id=0,1)
methods: read(nbytes, write=0x00), write(buf), write_readinto(wr_buf, rd_buf)
machine.Timer(freq, callback) -- create a software timer object
eg: machine.Timer(freq=1, callback=lambda t:print(t))
Pins are numbered 0-29, and 26-29 have ADC capabilities
Pin IO modes are: Pin.IN, Pin.OUT, Pin.ALT
Pin pull modes are: Pin.PULL_UP, Pin.PULL_DOWN
Useful control commands:
CTRL-C -- interrupt a running program
CTRL-D -- on a blank line, do a soft reset of the board
CTRL-E -- on a blank line, enter paste mode
For further help on a specific object, type help(obj)
For a list of available modules, type help('modules')
5. Připojení k Raspberry Pi Pico bez administrátorských práv
Většinou je nutné se k Raspberry Pi Pico připojit aplikací minicom, která má přiřazená administrátorská práva (sudo minicom). To však není ideální řešení, takže se podívejme, jak lze operační systém nastavit tak, aby tato práva nebyla vyžadována. Nejprve si (při připojeném Pi Pico) necháme vypsat vlastníka a skupinu zařízení, které realizuje sériovou linku mezi počítačem a Pi Pico. V mém konkrétním případě (viz předchozí kapitoly) se jedná o zařízení ttyACM0, takže si nechám vypsat vlastnosti tohoto souboru, který toto zařízení reprezentuje v souborovém systému Linuxu:
$ ls -la /dev/ttyACM0 crw-rw----. 1 root dialout 166, 0 Feb 7 12:00 /dev/ttyACM0
Jméno skupiny je dialout. Dále si příkazem groups můžeme ověřit, do kterých skupin je přiřazen uživatel:
$ groups kvm devel ptisnovs
Z výpisu je patrné, že tento uživatel není přiřazen do skupiny dialout. Musíme tedy právě aktivního uživatele do této skupiny přiřadit. To se provede následujícím způsobem (používáme proměnnou prostředí USER obsahující login uživatele):
$ sudo usermod -a -G dialout $USER
Dále se to skupiny přihlásíme:
$ newgrp dialout
A ověříme si, že se nyní uživatel nachází i ve skupině dialout:
$ groups dialout kvm devel ptisnovs
Nyní bude možné se k Raspberry Pi Pico připojit bez toho, abychom aplikaci minicom dali práva uživatele:
$ minicom -D /dev/ttyACM0
Totéž nastavení je možné provést například i pro skupinu studentů atd.
6. Zjištění základních vlastností Raspberry Pi Pico z MicroPythonu
Na Raspberry Pi Pico jsme v rámci předchozích kapitol nahráli MicroPython, který je možné (a vlastně i nutné) používat namísto shellu operačního systému. Přes MicroPython je možné zjistit poměrně velké množství údajů jak o prostředí MicroPythonu, tak i o samotném Raspberry Pi. Některé základní možnosti budou uvedeny v této kapitole.
Nejdříve zjistíme verzi a variantu MicroPythonu. To je velmi snadné:
>>> import sys >>> sys.version '3.4.0; MicroPython v1.24.1 on 2024-11-29' >>> sys.implementation (name='micropython', version=(1, 24, 1, ''), _machine='Raspberry Pi Pico W with RP2040', _mpy=4870)
V dalších kapitolách budeme potřebovat znát hodinovou frekvenci. Tu zjistíme následovně:
>>> import machine >>> machine.freq() 125000000
Základní informace o jednotlivých GPIO (toto je ale spíše poněkud špinavý trik):
>>> for i in range(30): ... print(machine.Pin(i)) ...
Vypsat se může například:
Pin(GPIO0, mode=IN) Pin(GPIO1, mode=IN) Pin(GPIO2, mode=ALT, alt=PWM) Pin(GPIO3, mode=IN) Pin(GPIO4, mode=IN) Pin(GPIO5, mode=OUT) Pin(GPIO6, mode=OUT) Pin(GPIO7, mode=OUT) Pin(GPIO8, mode=OUT) Pin(GPIO9, mode=OUT) Pin(GPIO10, mode=OUT) Pin(GPIO11, mode=OUT) Pin(GPIO12, mode=OUT) Pin(GPIO13, mode=OUT) Pin(GPIO14, mode=OUT) Pin(GPIO15, mode=OUT) Pin(GPIO16, mode=OUT) Pin(GPIO17, mode=OUT) Pin(GPIO18, mode=OUT) Pin(GPIO19, mode=OUT) Pin(GPIO20, mode=OUT) Pin(GPIO21, mode=OUT) Pin(GPIO22, mode=OUT) Pin(GPIO23, mode=ALT, alt=31) Pin(GPIO24, mode=ALT, alt=31) Pin(GPIO25, mode=ALT, pull=PULL_DOWN, alt=31) Pin(GPIO26, mode=IN) Pin(GPIO27, mode=IN) Pin(GPIO28, mode=ALT, pull=PULL_DOWN, alt=31) Pin(GPIO29, mode=ALT, pull=PULL_DOWN, alt=31)
Pro zajímavost se podívejme na nároky Pythonu na mikrořadiči RP2040 ihned po bootu, spuštění interpretru a po připojení k interpretru přes emulovanou sériovou linku:
>>> import micropython >>> micropython.mem_info() stack: 556 out of 7936 GC: total: 189952, used: 15552, free: 174400 No. of 1-blocks: 162, 2-blocks: 44, max blk sz: 116, max free sz: 9896
Podrobnější informace o jednotlivých blocích paměti (prozatím nebudeme potřebovat, ale lze tak sledovat činnost správce paměti):
>>> micropython.mem_info(True)
stack: 564 out of 7936
GC: total: 189952, used: 15744, free: 174208
No. of 1-blocks: 167, 2-blocks: 46, max blk sz: 116, max free sz: 9896
GC memory layout; from 20011a00:
00000000: h=MLhhhhBDhhBTTDBBBBBDhTh===BDBDh====B=BBBBBBTB=BTB=BBBTB=TBTB=B
00000400: h===TB=h===========B=Lh=hh===================h========h=========
00000800: ========h=======================================================
00000c00: ========h=======================================================
00001000: ========h=h=======h========h====================================
00001400: ===================================hBTDLhh===h=Lhh=hhhhBh=hhh=Lh
00001800: =Lh=Lh=Lh=Lh=Lh=DTTFFLh=hLhhhTTBBBBBBBBBh=======Lh=Lh=Lh=Lh=Lh=L
00001c00: h=LLFh=h=Lh=LLFh=h=Lh=Lh=Lh=Lh=Lh=======Lh=======Lh=======Lh====
00002000: ===Lh=======Lh=======Lh=======Lh=======Lh=h=hhhBDhhh=hhhhBhhhhhh
00002400: hh==Bhhhhh=hhBh=hhhhhhh==Bh=hhhh=hh=======h==Bh=hhhhB..hh....h==
00002800: .....h================h=============================h=h==.......
00002c00: ................................................................
00003000: ...........................h....................................
00003400: ................................................................
00003800: ........................................h=======................
00003c00: ................................h...............................
00004000: ...............................hh=.....................h.....h..
00004400: .....h...................h=======...............................
00004800: ............................................h===================
00004c00: ============....................................................
00005000: ................................................................
00005400: ...........................................................h====
00005800: ===.............................................................
(4 lines all free)
00006c00: ..............h================================================h
00007000: ==h=============================================================
00007400: ======================================================h=========
00007800: =====h==h==h==h=====h=======h=========h==h==h===========........
(154 lines all free)
0002e400: ................................
7. Třída používaná pro ovládání stavových strojů PIO
V předchozím článku jsme si řekli základní informace o PIO i o stavových strojích („koprocesorech“), přes které je možné programově manipulovat se vstupně-výstupními piny Raspberry Pi. Pro zjednodušení konfigurace a spouštění stavových strojů z MicroPythonu slouží třída StateMachine z balíčku rp2. Můžeme se tedy pokusit o zjištění základních informací o této třídě:
>>> import rp2 >>> help(rp2.StateMachine) object <class 'StateMachine'> is of type type init -- <function> active -- <function> restart -- <function> exec -- <function> get -- <function> put -- <function> rx_fifo -- <function> tx_fifo -- <function> irq -- <function>
Povšimněte si sady metod (zde jsou označeny jako funkce) určených pro inicializaci a spuštění stavových strojů a taktéž metod pro práci se vstupními i výstupními frontami (RX FIFO a TX FIFO).
Před konfigurací některého stavového stroje je tedy nutné zkonstruovat instanci třídy StateMachine. K tomu slouží konstruktor, jehož hlavička je následující:
StateMachine.init(
program,
freq=-1,
*,
in_base=None,
out_base=None,
set_base=None,
jmp_pin=None,
sideset_base=None,
in_shiftdir=None,
out_shiftdir=None,
push_thresh=None,
pull_thresh=None)
Tento konstruktor vyžaduje jeden povinný parametr, kterým je funkce představující PIO program. Další parametry jsou nepovinné, i když některé z nich použijeme.
8. Tvorba programu pro PIO (stavový stroj)
Podívejme se nyní, jakým způsobem se v MicroPythonu tvoří programy pro jednotlivé stavové stroje. Jedná se o běžné pythonovské funkce, které ovšem musí být obaleny dekorátorem rp2.asm_pio a obsahovat mohou zápis instrukcí PIO (popsaných minule), jež ovšem ze syntaktického pohledu vypadají jako volání běžných Pythonovských funkcí.
@rp2.asm_pio()
def my_first_pio_program():
...
...
...
Samotnému dekorátoru rp2.asm_pio je možné předat mnoho nepovinných parametrů, které si popíšeme později:
rp2.asm_pio(
*,
out_init=None,
set_init=None,
sideset_init=None,
side_pindir=False,
in_shiftdir=PIO.SHIFT_LEFT,
out_shiftdir=PIO.SHIFT_LEFT,
autopush=False,
autopull=False,
push_thresh=32,
pull_thresh=32,
fifo_join=PIO.JOIN_NONE)
Například:
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def my_first_pio_program():
...
...
...
9. Pseudofunkce Pythonu určené pro naprogramování stavového stroje
V těle funkcí, které jsou „obaleny“ dekorátorem rp2.asm_pio, se používají následující pseudofunkce Pythonu (interně se totiž tyto funkce transformují do PIO funkcí popsaných minule):
| Pseudofunkce | Instrukce PIO | Stručný popis |
|---|---|---|
| wrap_target() | × | specifikace místa v programu, kde činnost začne po wrap |
| wrap() | × | specifikace místa v programu, kde PIO skončí činnost a může začít vykonávat nový cyklus |
| label(návěští) | × | pojmenované či očíslované návěští (typicky cíl skoku) |
| word(instrukce, návěští) | × | vložení 16bitové konstanty představující instrukční slovo (nízkoúrovňové) |
| jmp(label) | JMP | nepodmíněný skok |
| jmp(cond, label) | JMP | podmíněný skok |
| wait(polarity, src, index) | WAIT | čekání na splnění podmínky (stav pinu, IRQ) |
| in_(src, bit_count) | IN | zápis bitů ze zdroje do posuvného registru ISR |
| out (dest, bit_count) | OUT | vysunutí bitů z posuvného registru OSR do cíle |
| push() | PUSH | uložení hodnoty z posuvného registru ISR do RX fronty (FIFO) |
| push(block) | PUSH | dtto, blokující varianta |
| push(noblock) | PUSH | dtto, neblokující varianta |
| push(iffull) | PUSH | dtto, varianta s nastaveným bitem IfF |
| push(iffull, block) | PUSH | kombinace předchozích možností |
| push(iffull, noblock) | PUSH | kombinace předchozích možností |
| pull() | PULL | přečtení hodnoty z TX fronty (FIFO) se zápisem do posuvného registru OSR |
| pull(block) | PULL | dtto, blokující varianta |
| pull(noblock) | PULL | dtto, neblokující varianta |
| pull(ifempty) | PULL | dtto, varianta s nastaveným bitem IfE |
| pull(ifempty, block) | PULL | kombinace předchozích možností |
| pull(ifempty, noblock) | PULL | kombinace předchozích možností |
| mov(dest, src) | MOV | přenos dat s provedením zvolené operace (negace či otočení bitů) |
| irq(index) | IRQ | vyvolání přerušení |
| irq(mode, index) | IRQ | vyvolání přerušení |
| set(dest, data) | SET | zápis dat do zvoleného cíle |
| nop() | × | překládá se do MOV Y, Y (jen čekání) |
U instrukce MOV jsme si řekli, že kromě prostého převodu hodnot ze zdroje do cíle je možné přenášené 32bitové hodnoty modifikovat – buď negovat všechny bity nebo všechny bity otočit. To lze při použití pseudofunkce mov realizovat následujícím způsobem:
mov() # prostý přenos dat mov().invert() # přenos dat s inverzí všech bitů mov().reverse() # přenos dat s otočením všech bitů
10. Specifikace počtu zpožďovacích cyklů
Připomeňme si, že u všech instrukcí PIO je možné v pěti bitech (jsou součástí 16bitového instrukčního slova) specifikovat počet zpožďovacích cyklů. Instrukce po svém provedení může čekat dalších 1–31 cyklů, čímž lze realizovat přenos různými rychlostmi atd. Jak se však tato část instrukce naplní pseudofunkcemi, s nimiž jsme se seznámili v předchozí kapitole? Jedno řešení spočívá v tom, že budeme výsledek pseudofunkce považovat za objekt a zavoláme jeho metodu delay (což do určité míry odpovídá volání invert() a reverse()). Zápis tedy může vypadat takto:
push().delay(10)
jmp("cíl").delay(31)
jmp(x_dc, "cíl").delay(0)
V praxi se však setkáme s odlišným zápisem – počet čekacích cyklů se zapíše do hranatých závorek:
push() [10]
jmp("cíl") [31]
jmp(x_dc, "cíl") [0]
11. Realizace skoků v PIO
Ještě si musíme vysvětlit jeden koncept – jak se realizují skoky v PIO v případě, že celý zápis provádíme v Pythonu. K tomuto účelu použijeme funkci label, která slouží k definici návěští. A právě návěští může sloužit jako cíl skoku. Podívejme se například, jak by mohla vypadat nekonečná smyčka:
@rp2.asm_pio()
def endless_loop():
label("opak")
nop()
jmp("opak")
Pokud by měla smyčka trvat delší dobu, můžeme použít tento zápis:
@rp2.asm_pio()
def endless_loop():
label("opak")
nop() [31]
jmp("opak")
Popř. bude nutné pseudoinstrukci nop opakovat:
@rp2.asm_pio()
def endless_loop():
label("opak")
nop() [31]
nop() [31]
nop() [31]
jmp("opak")
Počítanou smyčku bychom mohli zapsat například takto:
@rp2.asm_pio()
def counted_loop():
set(x, 20)
label("opak")
nop() [31]
nop() [31]
nop() [31]
jmp(x_dec, "opak")
12. Typický první ukázkový příklad při programování MCU: blikání LED
Většina prvních pokusů s mikrořadiči většinou rozbliká LED připojenou k vybranému GPIO (vstupně-výstupnímu pinu). Tento projekt si na začátek ukážeme také, ovšem blikat LED budeme přes PIO a to velmi přesně – s teoretickou přesnosti na jednotlivé hodinové cykly (125 MHz). V případě, že máte k dispozici původní Raspberry Pi Pico bez Wifi modulu, je situace velmi jednoduchá, protože LED, která je nainstalována přímo na Raspberry, je dostupná přes GPIO pin číslo 25. V tomto případě tedy budeme ovládat tento pin. Pro Raspberry Pi Pico W (tedy s Wifi modulem) to už tak jednoduché není, protože LED není přímo ovladatelná přes GPIO. Proto si připojíme vlastní LED: mezi GPIO číslo 8 (náhodný výběr) a GND (zem) je zapojena LED v sérii s rezistorem 330Ω (není nutné dodržet přesně, LED bude svítit i při použití 470Ω z řady E3). Samozřejmě můžete LED zapojit i na jiné GPIO, ale bude nutné upravit program.
Nejdříve si otestujeme, zda je LED správně zapojena (polarita atd.). Rozblikáme ji bez přímého použití PIO. Pro standardní LED na původním Raspberry Pi Pico (bez Wifi) lze použít tento prográmek:
from machine import Pin, Timer
led = Pin(25, Pin.OUT)
timer = Timer()
def blink(timer):
led.toggle()
timer.init(freq=2, mode=Timer.PERIODIC, callback=blink)
Pro LED zapojenou ke GPIO číslo 8 jen změníme číslo pinu:
from machine import Pin, Timer
led = Pin(8, Pin.OUT)
timer = Timer()
def blink(timer):
led.toggle()
timer.init(freq=2, mode=Timer.PERIODIC, callback=blink)
13. Naprogramování a načasování PIO pro blikání LED
Nyní se pokusíme rozblikat LED přes programovatelné PIO. Ukážeme si příklad, jehož originální verzi naleznete na adrese https://docs.micropython.org/en/latest/rp2/quickref.html#programmable-io-pio. Jak bude naprogramování blikání probíhat? Budeme předpokládat, že hodinová frekvence vybraného stavového stroje bude nastavena přesně na 2kHz. Toho můžeme dosáhnout nastavením děliče hodinového signálu, o němž jsme se zmínili zde. Co to znamená? Každou sekundu proběhne 2000 cyklů stavového stroje. Budeme chtít, aby prvních 1000 cyklů dioda svítila a dalších 1000 cyklů naopak zhasla.
Rozsvícení LED následované vhodně nastavenou čekací smyčkou může vypadat takto:
set(pins, 1)
set(x, konstanta)
label("delay_high")
nop()
jmp(x_dec, "delay_high")
Vhodným doplněním konstanty a zpožďovacích cyklů lze dosáhnout přesného načasování. Například takto:
set(pins, 1)
set(x, 31) [6]
label("delay_high")
nop() [29]
jmp(x_dec, "delay_high")
První instrukce trvá jeden hodinový cyklus, druhá instrukce trvá 1+6=7 cyklů. label není instrukcí, takže ji nepočítáme. Ovšem počítat naopak musíme smyčku, která proběhne 32×. A každý cyklus smyčky trvá 29+1 cyklus (nop) + 1 cyklus (jmp), celkem tedy 32×31=992 cyklů. Pokud připočteme úvodních osm cyklů, získáme přesně 1000 cyklů.
Druhá část programu, tj. zhasnutí LED po dobu 1000 cyklů, je již snadné – jen zapíšeme nulu na port, namísto 1:
set(pins, 0)
set(x, 31) [6]
label("delay_low")
nop() [29]
jmp(x_dec, "delay_low")
Pokud se vám nechce již v prvním PIO programu používat počítané smyčky, lze celé zpoždění realizovat sérií instrukcí nop(), z nichž každá potrvá přesně 32 strojových cyklů:
nop() [31]
nop() [31]
nop() [31]
nop() [31]
...
...
...
14. Naprogramování funkce s ovládáním LED přes PIO
Nyní máme k dispozici prakticky všechny informace potřebné pro naprogramování funkce v MicroPythonu, která bude ovládat LED s využitím PIO. Víme již, že taková funkce musí být obalena dekorátorem rp2.asm_pio a může ve svém těle obsahovat konstantní výrazy a volání pseudofunkcí, které odpovídají instrukcím PIO. K těmto pseudofunkcím ještě přidejme funkci pro nastavení návěští (label) a funkce pro označení začátku a konce celého PIO programu (mohou se vkládat automaticky). A navíc můžeme do dekorátoru přidat i původní stav pinu (GPIO), který se ovládá:
@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")
15. Ověření délky trvání jedné iterace PIO programu
Celkovou dobu trvání jedné iterace celého PIO programu z předchozí kapitoly lze snadno vypočítat:
| Instrukce | Délka trvání | Opakování | Celkem |
|---|---|---|---|
| set(pins, 1) | 1 | 1 | 1 |
| set(x, 31) [6] | 1+6 | 1 | 7 |
| nop() [29] | 1+29 | 32 | 960 |
| jmp(x_dec, „delay_high“) | 1 | 32 | 32 |
| set(pins, 0) | 1 | 1 | 1 |
| set(x, 31) [6] | 1+6 | 1 | 7 |
| nop() [29] | 1+29 | 32 | 960 |
| jmp(x_dec, „delay_low“) | 1 | 32 | 32 |
| Součet | 2000 |
16. Konstrukce a spuštění stavového stroje
V případě, že se vám podařilo výše uvedenou funkci interpretovat MicroPythonem (postačuje přenos do terminálu s běžícím Minicomem), můžeme přistoupit k dalšímu kroku. Zkonstruujeme totiž objekt, který bude interně představovat stavový stroj PIO. Je to poměrně snadné: přesněji řečeno snadné ve chvíli, kdy již víme, jak PIO pracuje. Určíme, že se bude jednat o první program (konstanta 0), jehož instrukce jsou získány z naší funkce blink_1hz (přes dekorátor). Dále určíme hodnotu pro dělič frekvence – budeme vyžadovat provedení 2000 instrukcí za sekundu. A konečně posledním parametrem je specifikován první pin nastavovaný instrukcí set (tedy vlastně mapování). Pro LED připojenou na GPIO číslo 8 použijeme:
sm = rp2.StateMachine(0, blink_1hz, freq=2000, set_base=Pin(8))
Naopak pro LED připojenou na standardní pin číslo 25 použijeme:
sm = rp2.StateMachine(0, blink_1hz, freq=2000, set_base=Pin(25))
A konečně tímto příkazem celý stavový stroj spustíme:
sm.active(1)
Nyní by měla LED začít blikat. A co víc – nyní již poměrně do všech podrobností víme, proč bliká a jak je to celé zařízeno.
17. Shrnutí
V dnešním článku jsme si (prozatím) ukázali naprosté základy využití subsystému programovatelných vstupně-výstupních pinů. Nyní již ovšem přesně víme, jak se dá celé PIO naprogramovat v MicroPythonu, co znamenají „tajemné“ hodnoty zapsané v hranatých závorkách i to, jak se inicializuje a následně spustí a popř. pozastaví stavový stroj. Mnoho nízkoúrovňových operací za nás dokáže provést samotný MicroPython (nastavení děliče frekvence a mapování GPIO), ovšem to hlavní – PIO programy – ponechává plně na vývojářích. Jedná se tak o poměrně dobře vyvážený koncept vysokoúrovňového jazyka s (de facto) assemblerem PIO.
18. Příloha: dekorátory v jazyce Python
V příloze dnešního dnešního článku si ve stručnosti vysvětlíme činnost takzvaných dekorátorů. Dekorátory jsme mohli vidět v hlavičce funkcí s PIO kódem. Jedná se o funkcionální technologii, která nám umožňuje snadno „obalit“ volání nějaké funkce dalším kódem a vrátit výsledek jako novou funkci s přidanými vlastnostmi (tedy v případě PIO by se vlastně vrátila sekvence operačních kódů instrukcí). Může to vypadat následovně:
def wrapper1(původní_funkce):
def nová_funkce():
# nějaký kód
původní_funkce()
# nějaký kód
return nová_funkce
Důležité je, že ono vlastní „obalení“ původní funkce je realizováno snadno zapamatovatelnou syntaxí – před definici funkce se na samostatný řádek zapíše jméno dekorátoru a jeho případné parametry:
@wrapper
def hello():
print("Hello!")
Musíme si ovšem uvědomit, že se ve skutečnosti jedná pouze o syntaktický cukr a podobnou techniku lze použít i v případě, že by dekorátory v Pythonu neexistovaly (ostatně i dekorátor rp2.asm_pio lze zavolat jako běžnou funkci).
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