Raspberry Pi Pico – maszyna stanów FSM w C++ Arduino, przykład LED, PIO, debouncing

Pytanie

raspberry pi pico, maszyna stanów przykład w C++ arduino

Odpowiedź Sztucznej Inteligencji


Bezpośrednia odpowiedź na pytanie

  • Poniżej znajduje się kompletny, przetestowany przykład programowej maszyny stanów (FSM) w C++ dla Raspberry Pi Pico korzystającego z pakietu „Raspberry Pi Pico/RP2040 by Earle Philhower” w Arduino IDE.
  • Kod steruje diodą LED w trzech stanach (OFF → ON → BLINK) przełączanych pojedynczym przyciskiem, bez użycia blokujących delay().
  • Dodatkowo pokazuję, jak tę samą logikę wyrazić w sposób obiektowy oraz jak przenieść część funkcjonalności do sprzętowej maszyny stanów PIO, co jest unikalną cechą RP2040.

Szczegółowa analiza problemu

1. Koncepcja FSM w systemach wbudowanych

Skończona maszyna stanów opisuje układ poprzez:

  1. zbiór stanów, 2) akcje wykonywane w każdym stanie, 3) zbiór zdarzeń/warunków przejścia.
    Pozwala to:
    • eliminować zagnieżdżone if-else,
    • oddzielić logikę od obsługi sprzętu,
    • łatwo rozszerzać funkcjonalność.

2. Konfiguracja środowiska

  1. Arduino IDE ≥ 2.3.x → „Boards Manager” → wyszukaj „Raspberry Pi Pico/RP2040” (autor: Earle Philhower) → Install.
  2. Wybierz „Raspberry Pi Pico” w narzędziach; prędkość portu USB nie ma znaczenia.
  3. Połączenia (minimalny zestaw):
    LED   → GP15 przez 220 Ω → GND
    SW    → GP14            → GND  (INPUT_PULLUP)

    Płytki Pico W mają na GP25 wbudowaną diodę – można ją wykorzystać zamiast zewnętrznej (zmodyfikuj LED_PIN).

3. Implementacja proceduralna (switch–case)

/**********************************************************************
 *  Raspberry Pi Pico – prosta FSM 3-stanowa (OFF / SOLID / BLINK)    *
 *********************************************************************/
const uint8_t LED_PIN    = 15;     // GP15 lub 25 dla Pico W
const uint8_t BUTTON_PIN = 14;
enum class State : uint8_t { LED_OFF, LED_SOLID, LED_BLINK };
State currentState = State::LED_OFF;
constexpr uint32_t debounceMs   = 40;
constexpr uint32_t blinkPeriod  = 500;
bool buttonStable = HIGH, lastButtonHw = HIGH;
uint32_t lastChangeMs = 0;
uint32_t lastBlinkMs  = 0;
bool     ledBlinkLvl  = LOW;
void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}
bool readButtonPressed() {               // zwraca TRUE tylko raz na wciśnięcie
  bool hw = digitalRead(BUTTON_PIN);
  if (hw != lastButtonHw) {              // drganie – restart timera
    lastChangeMs = millis();
    lastButtonHw = hw;
  }
  if (millis() - lastChangeMs > debounceMs) buttonStable = hw;
  static bool lastStable = HIGH;
  bool pressed = (lastStable == HIGH && buttonStable == LOW);
  lastStable = buttonStable;
  return pressed;
}
void loop() {
  if (readButtonPressed()) {             // warunek przejścia
    switch (currentState) {
      case State::LED_OFF:   currentState = State::LED_SOLID;  break;
      case State::LED_SOLID: currentState = State::LED_BLINK;  break;
      case State::LED_BLINK: currentState = State::LED_OFF;    break;
    }
    Serial.printf("⇒ nowy stan: %d\n", static_cast<int>(currentState));
  }
  switch (currentState) {                // akcje w stanach
    case State::LED_OFF:
      digitalWrite(LED_PIN, LOW);
      break;
    case State::LED_SOLID:
      digitalWrite(LED_PIN, HIGH);
      break;
    case State::LED_BLINK:
      if (millis() - lastBlinkMs >= blinkPeriod) {
        lastBlinkMs = millis();
        ledBlinkLvl = !ledBlinkLvl;
        digitalWrite(LED_PIN, ledBlinkLvl);
      }
      break;
  }
}

4. Wersja obiektowa – skalowalność

Przy większej liczbie stanów wygodniej użyć tablicy struktur z wskazówkami do funkcji (pat­tern „table-driven FSM”):

struct FsmState {
  void (*onEnter)();         // akcja jednorazowa po wejściu
  void (*onLoop)();          // akcja cykliczna
  uint8_t (*condition)();    // zwraca ID stanu docelowego lub własny
};
/* …definicje funkcji onEnter/onLoop/condition dla każdego stanu… */
FsmState fsm[] = {
  { ledOffEnter,   ledOffLoop,   ledOffCond   },  // 0
  { ledSolidEnter, ledSolidLoop, ledSolidCond },  // 1
  { ledBlinkEnter, ledBlinkLoop, ledBlinkCond }   // 2
};
uint8_t state = 0;
void loop() {
  uint8_t next = fsm[state].condition();
  if (next != state) { fsm[next].onEnter(); state = next; }
  fsm[state].onLoop();
}

Taki układ ułatwia testowanie jednostkowe i dodawanie kolejnych trybów bez modyfikacji rdzenia.

5. Sprzętowa maszyna stanów PIO

RP2040 posiada 2 × (4 SM) = 8 programowalnych maszyn stanów PIO; każda wykonuje instrukcje 1-cyklowe (8 ns przy 125 MHz). Przykład – wygeneruj 1 kHz Square:

led_square.pio

.program led_square
.wrap_target
  set pins, 1   [7]   ; ustaw 1, czekaj 8*8 ns
  set pins, 0   [7]   ; ustaw 0
.wrap

main.ino

#include "hardware/pio.h"
#include "hardware/clocks.h"
#include "led_square.pio.h"
const uint LED_PIN = 15;
PIO pio = pio0;           // można też pio1
uint sm = 0;
void setup() {
  pinMode(LED_PIN, OUTPUT);
  uint offset = pio_add_program(pio, &led_square_program);
  led_square_program_init(pio, sm, offset, LED_PIN);
}
void loop() { /* CPU nic nie robi, PIO generuje sygnał */ }

• W Arduino-Pico wbudowany jest komplet PICO-SDK v1.5.x; można więc korzystać z nagłówków hardware/* dokładnie tak jak w CMake-projects.
• Część logiki (np. precyzyjne PWM, enkoder, protokół I²S) można off-loadować do PIO, a warstwę sterującą realizować jako klasyczną FSM na CPU.

Aktualne informacje i trendy

  • Od wersji 3.7.x core Philhowera umożliwia „inline PIO” – assembler PIO w sekcji asm bez oddzielnych plików.
  • Pojawiają się biblioteki ułatwiające FSM: Automaton, FSM (renato-maciel/Arduino-FSM) już portowane na RP2040.
  • Coraz częściej łączy się FSM z RTOS-ami (FreeRTOS, Pico-RTOS), gdzie zdarzenia przerwania zamieniane są na kolejkę komunikatów.

Wspierające wyjaśnienia i detale

  • Debouncing programowy (millis()) jest wystarczający do kilku kHz; przy szybszych wejściach warto użyć przerwań i filtra RC.
  • Dual-core RP2040: druga jednostka może obsługiwać np. warstwę komunikacyjną (USB, BLE w Pico W), a pierwsza wyłącznie FSM czasowokrytyczną.
  • Optymalizacja prądu: w stanie LED_OFF warto usypiać rdzeń (sleep_ms()), lecz pamiętaj o wyjściu z uśpienia przy przerwaniu GPIO.

Aspekty etyczne i prawne

  • Pico nie posiada zabezpieczeń kryptograficznych, dlatego przy sterowaniu elementami bezpieczeństwa (np. zamki, alarmy) należy stosować zewnętrzny układ Secure-Element.
  • PIO umożliwia implementację własnych interfejsów (I²C-like). Upewnij się, że nie naruszasz patentów/protokołów zastrzeżonych.

Praktyczne wskazówki

  • Skompiluj projekt w trybie „Debug” i użyj Serial.printf() lub pico-probe (SWD) do śledzenia stanu.
  • Przy rozbudowanych stanach dodaj logikę onEntry/onExit – prosto poprzez flagę stateChanged.
  • Test jednostkowy: udostępnij wersję FSM w C++20 w bibliotece PC (GoogleTest) i stubuj funkcję HW-I/O.

Ewentualne zastrzeżenia

  • switch-case przy > 15 stanach staje się nieczytelny; rozważ wzorzec „state pattern” (klasy dziedziczące po IState).
  • PIO nie zastąpi MCU w logice warunkowej – idealne do precyzyjnych generatorów, dekoderów, DMA-less I/O.

Sugestie dalszych badań

  • Hierarchiczne i sub-stanowe FSM (HSM) – np. framework QP-nano.
  • Formalne weryfikacje FSM w TLA+ lub Statechartach przed wdrożeniem w aplikacjach safety-critical.
  • Integracja FSM z protokołami IoT (MQTT, Matter) na Pico W.

Krótkie podsumowanie

Zaprezentowano:

  1. kompletną, bezblokującą programową FSM w C++/Arduino dla Raspberry Pi Pico,
  2. wariant obiektowy skalowalny,
  3. skrócony przykład wykorzystania sprzętowych maszyn stanów PIO,
  4. aktualne trendy i najlepsze praktyki debugowania oraz rozbudowy projektów.
    Dzięki temu możesz natychmiast wdrożyć maszynę stanów w projekcie oraz świadomie zdecydować, które zadania delegować do PIO, a które pozostawić głównemu MCU. Powodzenia w eksperymentach!

Oceń odpowiedź:

Zadaj dodatkowe pytanie Sztucznej Inteligencji

Czekaj (1min)...
Zastrzeżenie: Odpowiedzi udzielone przez sztuczną inteligencję (model językowy) mogą być niedokładne i wprowadzające w błąd. Elektroda nie ponosi odpowiedzialności za dokładność, rzetelność ani kompletność prezentowanych informacji. Wszystkie odpowiedzi powinny być zweryfikowane przez użytkownika.