PICO, maszyna stanów przykład w C++
sleep_ms()
w pętli update). // fsm_traffic_pico.cpp
#include "pico/stdlib.h"
#include "hardware/gpio.h"
constexpr uint LED_R = 2;
constexpr uint LED_Y = 3;
constexpr uint LED_G = 4;
constexpr uint BTN = 5;
// ---------- ENUM STANÓW ----------
enum class State { Red, Green, Yellow, Service, Off };
// ---------- ENUM ZDARZEŃ ----------
enum class Event { TimerElapsed, ButtonPressed };
// ---------- KONFIGURACJA TIMINGU ----------
struct Dur {
static constexpr uint32_t RED_MS = 5000;
static constexpr uint32_t GREEN_MS = 5000;
static constexpr uint32_t YELLOW_MS = 2000;
static constexpr uint32_t BLINK_MS = 500;
};
// ---------- KLASA FSM ----------
class TrafficFSM {
public:
void init();
void update(); // wywoływana cyklicznie w main()
private:
State current{State::Off};
uint32_t ts_state{}; // znacznik czasu wejścia w stan
bool blink_on{false};
void dispatch(Event ev);
void enter(State s);
// Akcje dla poszczególnych stanów
void actionRed();
void actionGreen();
void actionYellow();
void actionServiceBlink();
};
// -------------------------------------------------------
void TrafficFSM::init() {
gpio_init(LED_R); gpio_set_dir(LED_R, GPIO_OUT);
gpio_init(LED_Y); gpio_set_dir(LED_Y, GPIO_OUT);
gpio_init(LED_G); gpio_set_dir(LED_G, GPIO_OUT);
gpio_init(BTN); gpio_set_dir(BTN, GPIO_IN); gpio_pull_up(BTN);
enter(State::Red); // start w czerwonym
}
void TrafficFSM::enter(State s) {
current = s;
ts_state = to_ms_since_boot(get_absolute_time());
// Zgaszenie wszystkich LED – unikamy „ghostingu”
gpio_put_masked((1u<<LED_R)|(1u<<LED_Y)|(1u<<LED_G), 0);
}
void TrafficFSM::dispatch(Event ev) {
switch (current) {
case State::Red:
if (ev == Event::TimerElapsed) enter(State::Green);
else if (ev == Event::ButtonPressed) enter(State::Off);
break;
case State::Green:
if (ev == Event::TimerElapsed) enter(State::Yellow);
else if (ev == Event::ButtonPressed) enter(State::Off);
break;
case State::Yellow:
if (ev == Event::TimerElapsed) enter(State::Red);
else if (ev == Event::ButtonPressed) enter(State::Off);
break;
case State::Off:
if (ev == Event::ButtonPressed) enter(State::Service);
break;
case State::Service:
if (ev == Event::ButtonPressed) enter(State::Red);
break;
}
}
void TrafficFSM::actionRed() { gpio_put(LED_R, 1); }
void TrafficFSM::actionGreen() { gpio_put(LED_G, 1); }
void TrafficFSM::actionYellow() { gpio_put(LED_Y, 1); }
void TrafficFSM::actionServiceBlink() {
uint32_t now = to_ms_since_boot(get_absolute_time());
if (now - ts_state >= Dur::BLINK_MS) {
ts_state = now;
blink_on = !blink_on;
gpio_put(LED_Y, blink_on);
}
}
void TrafficFSM::update() {
// 1. Obsługa przycisku – debouncing 25 ms
static bool btn_last{false};
static uint32_t t_last{};
bool btn = !gpio_get(BTN);
uint32_t now = to_ms_since_boot(get_absolute_time());
if (btn != btn_last && (now - t_last) > 25) {
t_last = now;
btn_last = btn;
if (btn) dispatch(Event::ButtonPressed);
}
// 2. Akcje stanu i sprawdzenie timeoutu
switch (current) {
case State::Red:
actionRed();
if (now - ts_state >= Dur::RED_MS) dispatch(Event::TimerElapsed);
break;
case State::Green:
actionGreen();
if (now - ts_state >= Dur::GREEN_MS) dispatch(Event::TimerElapsed);
break;
case State::Yellow:
actionYellow();
if (now - ts_state >= Dur::YELLOW_MS) dispatch(Event::TimerElapsed);
break;
case State::Service:
actionServiceBlink(); // własny timer w funkcji
break;
case State::Off:
/* nic – LED-y wygaszone */
break;
}
}
int main() {
stdio_init_all();
TrafficFSM fsm;
fsm.init();
while (true) {
fsm.update();
tight_loop_contents(); // makro z SDK – oszczędza energię
}
}
cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(fsm_traffic_pico)
pico_sdk_init()
add_executable(fsm_traffic_pico fsm_traffic_pico.cpp)
target_link_libraries(fsm_traffic_pico pico_stdlib)
pico_add_extra_outputs(fsm_traffic_pico)
tight_loop_contents()
w wersji oszczędzającej jeszcze ~15 µA w trybie IDLE. update()
pozostaje jednak najlżejszym, „bare-metalowym” podejściem. sleep_ms()
na nieblokujące timery alarm_pool
/hardware_timer
– podnosi to responsywność i obniża pobór mocy. ts_state
, można użyć hardware_alarm
i obsługi przerwania; w przykładzie pozostajemy przy prostym „software timer”. gpio_put_masked()
jedną operacją zeruje wszystkie LED-y – minimalizuje zjawisko „ghost flash” przy szybkim przełączaniu stanów. enum class
gwarantuje kontrolę typów i unika kolizji nazw w globalnym zakresie. PICO_PRINTF_USB_ENABLE 1
w CMakeLists.txt
, a w pętli update()
wypisuj current
– ułatwia weryfikację przejść. pio_uart
+ pytest-rp2040
(najnowsza wtyczka 2024-04). dormant
/sleep
. Po dodaniu warto przenieść FSM do przerwań → pętla główna może spać. Gdybyś chciał przerzucić cykl świateł do sprzętowej state-machine, poniższy 8-liniowy program PIO generuje sekwencję R-G-Y (bez przycisku). Rdzeń główny zasila SM danymi czasu trwania stanów.
.program tl
.pull_block
out y, 32 ; Y = czas stanu [cykle]
label_loop:
set pins, 1 [0] ; LED R
mov x, y
r_wait: jmp x-- r_wait
set pins, 2 ; LED G
mov x, y
g_wait: jmp x-- g_wait
set pins, 4 ; LED Y
mov x, y
y_wait: jmp x-- y_wait
jmp label_loop
W C++ przygotowujesz tablicę czasów i wysyłasz do FIFO SM – rdzeń RP2040 pozostaje niemal bezczynny.
Przedstawiony przykład demonstruje kompletną, nieblokującą maszynę stanów w C++ dla Raspberry Pi Pico, wzbogaconą o dobre praktyki debouncingu i timingu. Zaprezentowaliśmy również alternatywę sprzętową w PIO, podkreślając różnicę między programową FSM a sprzętową „state machine” RP2040. Dzięki modularnej strukturze możesz łatwo rozszerzyć projekt o kolejne stany i logikę, jednocześnie zachowując wysoką czytelność i testowalność kodu.