Przejdź do treści
Wszystkie wpisy

29 października 20269 min czytania

Raspberry Pi Pico 2 W i MicroPython: Wi‑Fi i dioda sterowana z przeglądarki

Pierwszy projekt na Pico 2 W: wgranie MicroPythona, narzędzia na Fedorze, połączenie z Wi‑Fi i mała strona w sieci domowej, która włącza i wyłącza diodę. Najpierw na gołych socketach, potem z Microdot.

Raspberry Pi Pico 2 W i MicroPython: Wi‑Fi i dioda sterowana z przeglądarki

Raspberry Pi Pico 2 W to mała płytka z mikrokontrolerem RP2350 i modułem radiowym Infineon CYW43439 (Wi‑Fi 2,4 GHz i Bluetooth). Pokazuję na niej prosty pierwszy projekt „internetu rzeczy”: stronę WWW w sieci lokalnej z przyciskami ON i OFF do sterowania diodą. Po kolei: wgranie MicroPythona, narzędzia na Fedorze (Thonny albo mpremote), Wi‑Fi i serwer HTTP, najpierw na gołych socketach, potem z biblioteką Microdot.

Polecenia i API opieram na oficjalnej dokumentacji Raspberry Pi i MicroPythona. Kod jest pisany pod aktualne wydanie MicroPythona; w starszych brakuje m.in. stałej WLAN.IF_STA i metody ipconfig().

Co będzie potrzebne

  • Raspberry Pi Pico 2 W (koniecznie wersja W, bo tylko ona ma radio),

  • kabel micro‑USB z liniami danych (sam kabel do ładowania nie wystarczy),

  • komputer z Linuksem (przykłady są dla Fedory),

  • sieć Wi‑Fi w paśmie 2,4 GHz. CYW43439 na Pico 2 W jest jednopasmowy, więc sieci „tylko 5 GHz” nie zobaczy.

Krok 1: wgranie MicroPythona (UF2 i BOOTSEL)

RP2350 ma w pamięci ROM bootloader, który po podłączeniu do USB udaje zwykły dysk. Firmware wgrywa się, kopiując na ten dysk plik .uf2.

  1. Pobierz plik UF2 dla Pico 2 W ze strony MicroPythona (micropython.org/download/RPI_PICO2_W/) albo z dokumentacji Raspberry Pi. Uważaj, bo osobne pliki są dla Pico, Pico W, Pico 2 i Pico 2 W. Plik od innej płytki nie obsłuży Wi‑Fi albo w ogóle nie ruszy.

  2. Przytrzymaj przycisk BOOTSEL i dopiero wtedy podłącz kabel USB do komputera.

  3. Gdy pojawi się dysk o nazwie RP2350, puść przycisk.

  4. Przeciągnij plik .uf2 na ten dysk. Płytka sama się zrestartuje, dysk zniknie, a w systemie pojawi się port szeregowy z konsolą REPL MicroPythona.

kod
   [BOOTSEL wciśnięty] ──► podłącz USB ──► dysk "RP2350"
                                              │
                           skopiuj *.uf2  ◄───┘
                                │
                       restart ─┴─► /dev/ttyACM0 (REPL MicroPythona)

Na stronie pobierania jest też wariant dla rdzeni RISC‑V (RP2350 ma parę rdzeni Arm Cortex‑M33 i parę Hazard3 RISC‑V). Na początek wybierz zwykły, domyślny plik.

Później, gdy MicroPython już działa, do bootloadera wejdziesz bez przycisku: wystarczy w REPL wywołać machine.bootloader() albo w terminalu mpremote bootloader.

Krok 2: narzędzia na Fedorze

Uprawnienia do portu szeregowego

Na Fedorze urządzenia /dev/ttyACM* należą do grupy dialout. Dodaj się do niej i wyloguj się, a potem zaloguj ponownie, bo samo otwarcie nowego terminala nie wystarczy:

bash
sudo usermod -aG dialout "$USER"

Opcja A: Thonny (graficznie)

Thonny jest w oficjalnych repozytoriach Fedory:

bash
sudo dnf install thonny

W prawym dolnym rogu okna wybierz interpreter MicroPython dla Raspberry Pi Pico i odpowiedni port (nazwy pozycji w menu różnią się nieco między wersjami Thonny). Od tej chwili dolny panel to REPL na płytce, a „Zapisz jako… → Raspberry Pi Pico” zapisuje plik bezpośrednio w pamięci flash.

Opcja B: mpremote (terminal)

mpremote to oficjalne narzędzie projektu MicroPython. Nie ma go w repozytoriach Fedory, ale jest na PyPI, więc najwygodniej zainstalować je przez pipx:

bash
sudo dnf install pipx
pipx install mpremote

mpremote connect list        # lista wykrytych płytek
mpremote                     # połączenie z pierwszą znalezioną i REPL
mpremote a0                  # skrót dla /dev/ttyACM0

Przydatne jest też mpremote run plik.py, które uruchamia plik z dysku komputera bez kopiowania go na płytkę.

Szybki test w REPL:

Python
from machine import Pin
led = Pin("LED", Pin.OUT)
led.on()
led.off()
led.toggle()

Na Pico 2 W (tak jak na Pico W) dioda nie jest podłączona do RP2350, tylko do pinu WL_GPIO0 układu radiowego CYW43439. Dlatego nie odwołuję się do niej numerem GPIO, tylko nazwą "LED". MicroPython sam przekazuje polecenie przez sterownik radia. Potwierdza to zarówno datasheet Pico 2 W, jak i dokument Raspberry Pi o łączeniu płytek z serii Pico W z internetem.

Krok 3: połączenie z Wi‑Fi

Dane logowania trzymaj w osobnym pliku secrets.py. Dzięki temu nie trafią przypadkiem do repozytorium: dopisz go do .gitignore.

Python
# secrets.py
SSID = "NazwaTwojejSieci"
PASSWORD = "TwojeHasloWiFi"

Funkcja łącząca się z siecią (używam jej w main.py):

Python
import time
import network
from machine import Pin

led = Pin("LED", Pin.OUT)


def polacz_wifi(ssid, haslo, limit_s=20):
    network.country("PL")  # kod kraju dla przepisów radiowych
    wlan = network.WLAN(network.WLAN.IF_STA)
    wlan.active(True)
    wlan.config(pm=0xA11140)  # wyłącza oszczędzanie energii, serwer szybciej odpowiada
    wlan.connect(ssid, haslo)

    for _ in range(limit_s):
        status = wlan.status()
        if status == 3:  # CYW43_LINK_UP: połączono i jest adres IP
            break
        if status < 0:  # -1 błąd, -2 brak sieci, -3 złe hasło
            raise RuntimeError("Błąd Wi-Fi, status %d" % status)
        led.toggle()  # mruganie = trwa łączenie
        time.sleep(1)
    else:
        raise RuntimeError("Nie udało się połączyć w %d s" % limit_s)

    led.off()
    ip = wlan.ipconfig("addr4")[0]
    print("Połączono, adres IP:", ip)
    return ip

Wyjaśnienie:

  • network.country("PL"): domyślnie kraj to „XX” (worldwide), a według Raspberry Pi bez ustawienia kraju część kanałów może być niedostępna.

  • Kody statusu pochodzą ze sterownika CYW43 i są opisane w dokumencie Raspberry Pi: 0 rozłączony, 1 łączenie, 2 połączono bez IP, 3 połączono z IP, -1 błąd, -2 nie znaleziono sieci, -3 błąd uwierzytelnienia. Pętla z limitem czasu jest potrzebna, bo bez niej literówka w haśle wygląda jak zawieszona płytka.

  • pm=0xA11140 to wartość z dokumentacji Raspberry Pi wyłączająca oszczędzanie energii Wi‑Fi (dokumentacja MicroPythona opisuje też stałe WLAN.PM_NONE, PM_PERFORMANCE, PM_POWERSAVE).

  • wlan.ipconfig("addr4") zwraca parę (adres, maska). Starsze poradniki używają wlan.ifconfig(), które dokumentacja MicroPythona oznacza jako przestarzałe.

Krok 4: serwer HTTP na socketach

Najpierw wersja bez żadnych bibliotek, bo tak najlepiej widać, co się dzieje „pod maską”. Przeglądarka wysyła żądanie w stylu GET /on HTTP/1.1. Program czyta pierwszą linię, wyciąga z niej ścieżkę i reaguje:

kod
 przeglądarka                     Pico 2 W
 ───────────                      ────────
 GET /        ───────────────►    strona z przyciskami
 GET /on      ───────────────►    led.on(),  odpowiedź 303 → /
 GET /off     ───────────────►    led.off(), odpowiedź 303 → /
 GET /cokolwiek ─────────────►    404

Przekierowanie 303 See Other po kliknięciu sprawia, że w pasku adresu zostaje /, więc odświeżenie strony nie przełącza diody ponownie.

Cały plik main.py (funkcję polacz_wifi wklej z poprzedniego kroku, w miejscu komentarza):

Python
# main.py
import socket
import time
import network
from machine import Pin
import secrets

led = Pin("LED", Pin.OUT)
stan_led = False

# ... tutaj funkcja polacz_wifi() z poprzedniego kroku ...

STRONA = """<!DOCTYPE html>
<html lang="pl"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Pico 2 W - LED</title>
<style>
body { font-family: sans-serif; text-align: center; margin-top: 2em; }
a { display: inline-block; font-size: 1.6em; margin: .4em; padding: .4em 1.2em;
    border: 2px solid #333; border-radius: .4em; text-decoration: none; color: #333; }
</style></head><body>
<h1>Dioda na Pico 2 W</h1>
<p>Stan diody: <b>__STAN__</b></p>
<a href="/on">ON</a> <a href="/off">OFF</a>
</body></html>
"""


def odpowiedz(klient, status, naglowki=b"", tresc=b""):
    klient.sendall(b"HTTP/1.0 " + status + b"\r\n" + naglowki + b"Connection: close\r\n\r\n")
    if tresc:
        klient.sendall(tresc)


def obsluz(klient):
    global stan_led
    plik = klient.makefile("rwb", 0)
    pierwsza = plik.readline()  # np. b"GET /on HTTP/1.1\r\n"
    while True:  # pomijamy pozostałe nagłówki
        linia = plik.readline()
        if not linia or linia == b"\r\n":
            break
    czesci = pierwsza.split()
    sciezka = czesci[1] if len(czesci) > 1 else b"/"

    if sciezka == b"/on":
        led.on()
        stan_led = True
        odpowiedz(klient, b"303 See Other", b"Location: /\r\n")
    elif sciezka == b"/off":
        led.off()
        stan_led = False
        odpowiedz(klient, b"303 See Other", b"Location: /\r\n")
    elif sciezka == b"/":
        html = STRONA.replace("__STAN__", "włączona" if stan_led else "wyłączona")
        odpowiedz(klient, b"200 OK", b"Content-Type: text/html; charset=utf-8\r\n", html.encode())
    else:
        odpowiedz(klient, b"404 Not Found", b"Content-Type: text/plain\r\n", b"Nie ma takiej strony\n")


def serwer(ip):
    adres = socket.getaddrinfo("0.0.0.0", 80)[0][-1]
    s = socket.socket()
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(adres)
    s.listen(1)
    print("Otwórz w przeglądarce: http://%s/" % ip)
    while True:
        klient, zdalny = s.accept()
        try:
            klient.settimeout(5)
            obsluz(klient)
        except OSError as e:
            print("Błąd połączenia:", e)
        finally:
            klient.close()


ip = polacz_wifi(secrets.SSID, secrets.PASSWORD)
serwer(ip)

Wgrywanie i uruchomienie:

bash
mpremote fs cp secrets.py :secrets.py   # dwukropek oznacza płytkę
mpremote fs cp main.py :main.py
mpremote reset
mpremote repl        # tu zobaczysz adres IP, wyjście z REPL: Ctrl-]

MicroPython uruchamia main.py automatycznie po starcie, więc płytka podłączona do zasilacza USB sama połączy się z siecią i wystawi stronę. Otwórz w tej samej sieci adres z konsoli, np. http://192.168.1.50/.

Program przerwiesz, naciskając Ctrl‑C w REPL.

Ograniczenia tej wersji

Ten serwer jest synchroniczny: obsługuje jedno połączenie naraz i w czasie oczekiwania na klienta nie robi nic innego. Dokument Raspberry Pi sam zaznacza, że w solidniejszych zastosowaniach lepiej obsługiwać żądania asynchronicznie. Do nauki w zupełności wystarczy.

Krok 5: to samo z Microdot

Microdot to mały framework webowy w stylu Flaska dla CPythona i MicroPythona. Jest aktywnie rozwijany i obsługuje żądania asynchronicznie (asyncio).

Dokumentacja Microdot zaleca na MicroPythonie ręczne skopiowanie pliku microdot.py z repozytorium (katalog src/microdot/). Do podstawowego serwera wystarczy ten jeden plik:

bash
mpremote fs cp microdot.py :microdot.py
Python
# main.py - wersja z Microdot
from machine import Pin
from microdot import Microdot, Response, redirect
import secrets

led = Pin("LED", Pin.OUT)
stan_led = False

# ... tutaj importy time i network, funkcja polacz_wifi() i stała STRONA
# z poprzednich kroków ...

app = Microdot()
Response.default_content_type = "text/html; charset=utf-8"


@app.route("/")
async def index(request):
    return STRONA.replace("__STAN__", "włączona" if stan_led else "wyłączona")


@app.route("/on")
async def wlacz(request):
    global stan_led
    led.on()
    stan_led = True
    return redirect("/")


@app.route("/off")
async def wylacz(request):
    global stan_led
    led.off()
    stan_led = False
    return redirect("/")


polacz_wifi(secrets.SSID, secrets.PASSWORD)
app.run(port=80)  # domyślnie Microdot słucha na porcie 5000

Microdot nie konfiguruje sieci, więc z Wi‑Fi łączę się sam. Domyślny typ odpowiedzi to text/plain, stąd ustawienie default_content_type.

Bezpieczeństwo: tylko sieć domowa

Ten serwer nie ma uwierzytelniania ani szyfrowania. Każdy w tej samej sieci, kto zna adres IP, może przełączać diodę. Przy diodzie to nic groźnego, ale zanim podłączysz przekaźnik, pamiętaj o kilku zasadach:

  • Używaj tylko w sieci lokalnej. Nie przekierowuj portu 80 na routerze i nie wystawiaj płytki do internetu.

  • HTTP to czysty tekst. Wszystko, co przesyłasz, da się podsłuchać w sieci. Nie przesyłaj w ten sposób haseł.

  • Żądania GET zmieniają stan. Wygodne w przykładzie, ale w poważniejszym projekcie przełączanie rób metodą POST.

  • Chroń secrets.py. Hasło leży na płytce jawnym tekstem i odczyta je każdy, kto podłączy Pico do USB.

Gdy coś nie działa

  • Brak dysku RP2350: sprawdź kabel (czy przesyła dane) i czy przycisk BOOTSEL był wciśnięty przed podłączeniem.

  • Permission denied na /dev/ttyACM0: brak grupy dialout albo nie było ponownego logowania. Sprawdź poleceniem groups.

  • Status -2: płytka nie widzi sieci. Sprawdź, czy sieć nadaje w 2,4 GHz, i czy SSID jest wpisany dokładnie (wielkość liter ma znaczenie).

  • Status -3: złe hasło.

  • Błąd „adres zajęty” (EADDRINUSE) po ponownym uruchomieniu skryptu z REPL: najprościej zrób mpremote reset.

  • Brak Wi‑Fi albo dioda nie reaguje: sprawdź, czy wgrałeś firmware RPI_PICO2_W, a nie RPI_PICO2. Plik dla Pico 2 bez W nie jest przeznaczony do obsługi układu radiowego, a na Pico 2 W dioda wisi właśnie na radiu.

Co dalej

Ten szkielet (Wi‑Fi, serwer i sterowanie wyjściem) łatwo rozbudować o odczyt czujnika albo endpoint z JSON-em. Jak zaprojektować panel, który uczciwie pokazuje stan urządzenia, opisuję we wpisie o interfejsie do sterowania urządzeniem, a Pico 2 W z ESP32 porównuję w osobnym wpisie. Jeśli potrzebujesz urządzenia zrobionego na zamówienie, zajrzyj na stronę elektronika. Pytania i własne wersje projektu można wrzucać na forum wirelab.

Źródła

Napisz