Programovatelné IO na mikrořadičích RP2040 – praktické použití

13. 2. 2025
Doba čtení: 27 minut

Sdílet

Raspberry Pi Pico s řadičem RP2040
Autor: Raspberry Pi
Na úvodní článek o PIO na mikrořadičích Raspberry Pi Pico dnes navážeme. Pokusíme se propojit znalosti o PIO (interní architektura, způsob propojení s mikrořadičem i s GPIO, instrukční soubor) s vlastnostmi MicroPythonu.

Obsah

1. Programovatelné IO na mikrořadičích RP2040 – praktické použití

2. Získání MicroPythonu pro konkrétní verzi Rapberry Pi Pica

3. Instalace MicroPythonu

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ů

11. Realizace skoků v PIO

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

17. Shrnutí

18. Příloha: dekorátory v jazyce Python

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

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/do­cumentation/microcontroller­s/micropython.html, odkud si je můžete stáhnout. Nebo je možné použít tyto přímé odkazy:

Poznámka: proč se jednotlivé adresy od sebe tak odlišují, je mi záhadou.

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
Poznámka: kapacita Flash paměti u první verze Raspberry Pi Pica je 2MB, kterou tento soubor nepřesahuje.

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/do­cumentation/microcontroller­s/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.

Poznámka: režim bootloader si lze kdykoli později „vynutit“ stiskem tlačítka BOOTSEL (jedná se o jediné tlačítko, které je na Picu nainstalováno) při připojení Raspberry Pi Pica k počítači přes USB. Tímto způsobem je možné například nahrát novou verzi MicroPythonu, vyzkoušet si CircuitPython, nahrát vlastní program přeložený z céčka, Rustu atd.

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.

Poznámka: jedná se o jedinou aplikaci, kterou budeme muset nainstalovat. Pro programování Raspberry Pi Pica není (na straně počítače) nutné nic dalšího – žádný assembler ani překladač, žádné integrované vývojové prostředí a ani žádný debugger (pokud pochopitelně nebudeme tvořit komplikovanější programy).

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
Poznámka: pokud se ve vašem případě vytvořilo odlišné zařízení, samozřejmě bude nutné použít jeho název. Cesta však bude stejná, tj. /dev/název_tty_zařízení (tty je odvozeno od slova teletype neboli dálnopis).

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
Poznámka: při každém dalším připojení Raspberry Pi Pico postačuje se přihlásit ke skupině newgrp dialout a spustit minicom (je možná vhodné si oba příkazy vložit do aliasu).

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]
Poznámka: to může vypadat poněkud zvláštně a mohlo by se dokonce zdát, že se vlastně ani nejedná o platný Pythonovský zdrojový kód. Ovšem pokud bude výsledkem volání pseudofunkcí objekt, v jehož třídě je přetížen operátor indexování, bude vše z pohledu syntaxe Pythonu korektní.

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.or­g/en/latest/rp2/quickref.html#pro­grammable-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")
Poznámka: to, jaký pin či piny se budou měnit instrukcí set, může být nakonfigurováno v jiné části programu (definicí mapování GPIO).

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]
    ...
    ...
    ...
Poznámka: nyní si již můžete snadno spočítat, kolik takových instrukcí bude zapotřebí a jaká bude délka zpoždění poslední z nich: součet musí být roven hodnotě 999, protože jeden cyklus trvá instrukce set.

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).

Poznámka: na dekorátory narazíme v Pythonu prakticky v jakémkoli oboru (třeba i u tvorby testů, REST API atd.). Vždy je důležité si uvědomit, že se nejedná o žádnou magii, ale o jinak zapsané volání funkce vyššího řádu.

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:

# Demonstrační příklad Stručný popis příkladu Cesta
1 display_version.py tisk verze MicroPythonu https://github.com/tisnik/most-popular-python-libs/blob/master/pio/displa­y_version.py
2 display_frequency.py zjištění hodinové frekvence MCU https://github.com/tisnik/most-popular-python-libs/blob/master/pio/displa­y_frequency.py
3 display_mem_info1.py zobrazení základních informací o paměti MicroPythonu https://github.com/tisnik/most-popular-python-libs/blob/master/pio/displa­y_mem_info1.py
4 display_mem_info2.py zobrazení podrobnějších informací o paměti MicroPythonu https://github.com/tisnik/most-popular-python-libs/blob/master/pio/displa­y_mem_info2.py
5 display_pins.py zobrazení konfigurace GPIO pinů https://github.com/tisnik/most-popular-python-libs/blob/master/pio/display_pins.py
6 blink_gpio25.py blikání LED na GPIO #25 bez použití PIO https://github.com/tisnik/most-popular-python-libs/blob/master/pio/blink_gpio25.py
7 blink_gpio8.py blikání LED na GPIO #8 bez použití PIO https://github.com/tisnik/most-popular-python-libs/blob/master/pio/blink_gpio8.py
8 pio_blink_pin25.py blikání LED na GPIO #25 s použitím PIO https://github.com/tisnik/most-popular-python-libs/blob/master/pio/pio_blin­k_pin25.py
9 pio_blink_pin8.py blikání LED na GPIO #8 s použitím PIO https://github.com/tisnik/most-popular-python-libs/blob/master/pio/pio_blin­k_pin8.py

20. Odkazy na Internetu

  1. 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/
  2. 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/
  3. 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/
  4. Překlad funkcí přímo do nativního kódu MicroPythonem
    https://www.root.cz/clanky/preklad-funkci-primo-do-nativniho-kodu-micropythonem/
  5. 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/
  6. Programmable IO
    https://docs.micropython.or­g/en/latest/rp2/tutorial/pi­o.html
  7. Introduction to the PIO (Programmable Input Output) of the RP2040
    https://tutoduino.fr/en/pio-rp2040-en/
  8. MicroPython examples: PIO
    https://github.com/raspberrypi/pico-micropython-examples/tree/master/pio
  9. PIO: Wikipedia CZ (pozor: jedná se o něco jiného!)
    https://cs.wikipedia.org/wiki/PIO
  10. RP2040 (Wikipedia)
    https://en.wikipedia.org/wiki/RP2040
  11. Maximising MicroPython speed
    https://docs.micropython.or­g/en/latest/reference/spe­ed_python.html
  12. Online ARM converter
    https://armconverter.com/?disasm
  13. The 3 different code emitters
    https://www.kickstarter.com/pro­jects/214379695/micro-python-python-for-microcontrollers/posts/664832
  14. The 3 different code emitters, part 2
    https://www.kickstarter.com/pro­jects/214379695/micro-python-python-for-microcontrollers/posts/665145
  15. Fast Filters for the Pyboard
    https://github.com/peterhin­ch/micropython-filters
  16. How to load 32 bit constant from assembler with @micropython.asm_thumb
    https://forum.micropython­.org/viewtopic.php?f=21&t=12931&sid=25­de8871fa9cfcf8cafb6318f9d8ba3a
  17. Pi pico, micropython.asm_thumb: ADR Rd, <label> and LDR Rd, <label> not implemented?
    https://github.com/orgs/mi­cropython/discussions/12257
  18. MicroPython documentation
    https://docs.micropython.or­g/en/latest/index.html
  19. Inline assembler for Thumb2 architectures
    https://docs.micropython.or­g/en/latest/reference/asm_thum­b2_index.html
  20. Inline assembler in MicroPython
    https://docs.micropython.or­g/en/latest/pyboard/tutori­al/assembler.html#pyboard-tutorial-assembler
  21. MCU market turns to 32-bits and ARM
    http://www.eetimes.com/do­cument.asp?doc_id=1280803
  22. Cortex-M0 Processor (ARM Holdings)
    http://www.arm.com/produc­ts/processors/cortex-m/cortex-m0.php
  23. Cortex-M0+ Processor (ARM Holdings)
    http://www.arm.com/produc­ts/processors/cortex-m/cortex-m0plus.php
  24. ARM Processors in a Mixed Signal World
    http://www.eeweb.com/blog/arm/arm-processors-in-a-mixed-signal-world
  25. RISCové mikroprocesory s komprimovanými instrukčními sadami
    https://www.root.cz/clanky/riscove-mikroprocesory-s-komprimovanymi-instrukcnimi-sadami/
  26. RISCové mikroprocesory s komprimovanými instrukčními sadami (2)
    https://www.root.cz/clanky/riscove-mikroprocesory-s-komprimovanymi-instrukcnimi-sadami-2/
  27. ARM Architecture (Wikipedia)
    https://en.wikipedia.org/wi­ki/ARM_architecture
  28. Cortex-M0 (Wikipedia)
    https://en.wikipedia.org/wi­ki/ARM_Cortex-M0
  29. Cortex-M0+ (Wikipedia)
    https://en.wikipedia.org/wi­ki/ARM_Cortex-M#Cortex-M0.2B
  30. Improving ARM Code Density and Performance
    New Thumb Extensions to the ARM Architecture Richard Phelan
  31. The ARM Processor Architecture
    http://www.arm.com/produc­ts/processors/technologies/in­struction-set-architectures.php
  32. Thumb-2 instruction set
    http://infocenter.arm.com/hel­p/index.jsp?topic=/com.ar­m.doc.ddi0344c/Beiiegaf.html
  33. Introduction to ARM thumb
    http://www.eetimes.com/dis­cussion/other/4024632/Intro­duction-to-ARM-thumb
  34. ARM, Thumb, and ThumbEE instruction sets
    http://www.keil.com/suppor­t/man/docs/armasm/armasm_CEG­BEIJB.htm
  35. An Introduction to ARM Assembly Language
    http://dev.emcelettronica­.com/introduction-to-arm-assembly-language
  36. Processors – ARM
    http://www.arm.com/produc­ts/processors/index.php
  37. The ARM Instruction Set
    http://simplemachines.it/doc/ar­m_inst.pdf
  38. The Thumb instruction set
    http://apt.cs.manchester.ac­.uk/ftp/pub/apt/peve/PEVE05/Sli­des/05_Thumb.pdf
  39. Why Learn Assembly Language?
    http://www.codeproject.com/Ar­ticles/89460/Why-Learn-Assembly-Language
  40. Is Assembly still relevant?
    http://programmers.stackex­change.com/questions/95836/is-assembly-still-relevant
  41. Why Learning Assembly Language Is Still a Good Idea
    http://www.onlamp.com/pub/a/on­lamp/2004/05/06/writegreat­code.html
  42. Assembly language today
    http://beust.com/weblog/2004/06/23/as­sembly-language-today/
  43. Assembler: Význam assembleru dnes
    http://www.builder.cz/rubri­ky/assembler/vyznam-assembleru-dnes-155960cz
  44. Assembler pod Linuxem
    http://phoenix.inf.upol.cz/li­nux/prog/asm.html
  45. AT&T Syntax versus Intel Syntax
    https://www.sourceware.or­g/binutils/docs-2.12/as.info/i386-Syntax.html
  46. Linux Assembly website
    http://asm.sourceforge.net/
  47. Raspberry Pi Pico Variants – A Detailed Comparison
    https://circuitdigest.com/ar­ticle/raspberry-pi-pico-variants-comparison
  48. Raspberry Pi Pico 2 vs Original Pico: What’s New?
    https://www.digikey.cz/en/ma­ker/blogs/2024/raspberry-pi-pico-2-vs-original-pico-whats-new
  49. pio-uart
    https://github.com/Sympatron/pio-uart
  50. Datasheet čipu RP2040
    https://datasheets.raspbe­rrypi.com/rp2040/rp2040-datasheet.pdf
Neutrální ikona do widgetu na odběr článků ze seriálů

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.


Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.