raspberry pi pico, maszyna stanu przykład
Kluczowe punkty
• FSM upraszcza logikę sterowania i poprawia czytelność kodu.
• PIO daje deterministyczne timingi, niezależne od rdzeni CPU.
• Warto unikać funkcji blokujących i wykorzystywać przerwania lub flagi.
Finite State Machine (FSM) = {S, Σ, δ, s₀, F}
W RP2040 możemy:
• zaimplementować δ w kodzie wysokiego poziomu (MicroPython / C),
• opisać δ jako program PIO (do 32 instrukcji), który wykonuje się z precyzją pojedynczych cykli 133 MHz.
# RPi Pico – MicroPython 1.21 (2024-02)
from machine import Pin, Timer
import utime
from enum import IntEnum
class State(IntEnum):
GREEN = 0
YELLOW = 1
RED = 2
RED_YEL = 3
LED = { # słownik pinów
State.GREEN: Pin(13, Pin.OUT, value=0),
State.YELLOW: Pin(14, Pin.OUT, value=0),
State.RED: Pin(15, Pin.OUT, value=0)
}
BTN = Pin(16, Pin.IN, Pin.PULL_UP)
MIN_GREEN_MS = 5000
tim_ref = utime.ticks_ms()
request = False
state = State.GREEN
def all_off():
for led in LED.values(): led.value(0)
def btn_irq(pin):
global request
if not pin.value(): # zbocze opadające
request = True
BTN.irq(trigger=Pin.IRQ_FALLING, handler=btn_irq)
def change(st):
global state, tim_ref
state, tim_ref = st, utime.ticks_ms()
print("→", state.name)
def elapsed(ms): # pomocniczy licznik bez blokowania
return utime.ticks_diff(utime.ticks_ms(), tim_ref) >= ms
print("Start FSM")
while True:
if state == State.GREEN:
all_off(); LED[State.GREEN].value(1)
if request and elapsed(MIN_GREEN_MS):
request = False
change(State.YELLOW)
elif state == State.YELLOW:
all_off(); LED[State.YELLOW].value(1)
if elapsed(2000):
change(State.RED)
elif state == State.RED:
all_off(); LED[State.RED].value(1)
if elapsed(5000):
change(State.RED_YEL)
elif state == State.RED_YEL:
all_off(); LED[State.RED].value(1); LED[State.YELLOW].value(1)
if elapsed(1000):
change(State.GREEN)
utime.sleep_ms(10)
Cechy: brak sleep()
w stanie, debounce w przerwaniu, łatwa rozbudowa o kolejne stany (np. tryb nocny).
Cel: wygenerować podany z CPU ciąg N impulsów 50 % duty na GP16.
import rp2, time
from machine import Pin
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW) # 1 instr = 1 cykl PIO
def pulse_n():
pull() # pobierz N z FIFO
mov(x, osr) # x = N
label("loop")
set(pins, 1) [1] # wysoki stan 2 cykle
set(pins, 0) [1] # niski 2 cykle
jmp(x_dec, "loop") # dekrementuj x
irq(block, 0) # zgłoś CPU, że skończono
sm = rp2.StateMachine(0, pulse_n, freq=1_000_000, set_base=Pin(16))
sm.active(1)
sm.put(10) # 10 impulsów
sm.irq(lambda s: print("Impulsy zakończone"))
while sm.rx_fifo(): pass
time.sleep(0.1)
• Dokładny okres = 4 cykle / f_clk = 4 µs → 250 kHz.
• CPU zajmuje się tylko wpisaniem wartości i obsługą IRQ – czasu rzeczowego nie traci.
• MicroPython 1.21 (luty 2024) dodał natywny moduł machine.Timer
z callbackiem współdzielonym między rdzeniami.
• Coraz częściej łączy się FSM w kodzie wysokiego poziomu z modułem asyncio
, uzyskując współbieżną obsługę wielu zadań.
• PIO wykorzystywane jest do dekodowania protokołów (DALI, DMX512, CAN PHY-less), co zmniejsza konieczność stosowania koprocesorów.
• Trend: gotowe biblioteki „statechart” (np. micropy-hsm
) przenoszą wzorce UML StateChart na RP2040.
• Debounce sprzętowy (RC, układ Schmitta) nadal zalecany w krytycznych aplikacjach.
• utime.ticks_ms()
w RP2040 przepełnia się co ~49 dni – w aplikacjach 24/7 trzeba stosować ticks_diff
.
• W PIO każdy program zajmuje do 32 instrukcji; można współdzielić jeden program przez kilka maszyn poprzez różne offsety (wrap_target / wrap).
• Sygnalizacja świetlna używana w ruchu publicznym podlega normom PN-EN 12368 – kod hobbystyczny nie spełnia wymogów bezpieczeństwa; do celów edukacyjnych korzystamy wyłącznie w warunkach laboratoryjnych.
• Oprogramowanie wbudowane w urządzenia sterujące mechanizmami wykonawczymi powinno przechodzić analizę FMEA i testy zgodne z IEC 61508 (Safety Integrity Level).
• Projektując większy system, rozważ FSM hierarchiczną – główna maszyna „TRYB PRACY”, pod-FSM dla każdego trybu.
• Do testów integracyjnych użyj symulacji GPIO (np. pico-sim
) lub rejestrowania stanów w RQ Spy.
• Na etapie PCB warto wyprowadzić piny PIO jako test-points – ułatwia logic-probing sygnałów generowanych sprzętowo.
• MicroPython nie wspiera przerwań PIO; obsługę IRQ trzeba pisać w C lub korzystać z rp2.PIO.irq
.
• W dwurdzeniowym RP2040 kod MicroPython działa tylko na rdzeniu 0; rdzeń 1 można wykorzystać w C (pico-sdk) – wymaga to własnego firmware (UF2).
• PIO nie ma dostępu do DMA w MicroPythonie – dla szybkich strumieni (SPI, I²S) lepiej użyć C/SDK.
• Zapoznaj się z projektem „pico-can” – sprzętowy kontroler CAN zbudowany wyłącznie w PIO.
• Przeanalizuj bibliotekę tinyusb_device_fsm
– stanowa obsługa USB Device na RP2040.
• Zobacz publikację „Async State Machines on RP2040” (Raspberry Pi Forum, XI 2023) – integracja uasyncio
+ FSM.
Zaprezentowano dwa komplementarne podejścia do maszyn stanów na Raspberry Pi Pico: