Programujeme Raspberry Pi Pico v jazyku MicroPython

základy práce s doskami RPi Pico v jazyku MicroPython

Videá pre cvičenie

Motivácia

Našim cieľom na nasledujúcich cvičeniach bude vytvoriť chytrý senzor pre meranie teploty a vlhkosti. Čo je podstatné - pri návrhu a implementácii sa budeme snažiť o to, aby sa nejednalo o prvoplánové riešenie, ale budeme sa snažiť o bezpečnú a funkčnú implementáciu s ohľadom na koncového zákazníka.

Ciele

  1. naučiť sa základy programovania v jazyku MicroPython na doske Raspberry Pi Pico

  2. zoznámiť sa s editorom Thonny a jeho možnosťami programovania mikrokontrolérov v jazyku MicroPython

  3. naučiť sa pracovať s LED diódou ako spätnou väzbou pre používateľa

  4. naučiť sa pracovať s objektom typu Pin

Postup

Predtým, ako naozaj začneme

Ešte predtým, ako začneme, si nainštalujte editor Thonny a na dosku Raspberry Pi Pico 2 nahrajte firmvér s jazykom MicroPython. Podrobnejšie pokyny nájdete na tejto stránke.

Niekoľko tipov pre prácu:

  • Pico na doske neobsahuje tlačidlo RESET. Najjednoduchšie ho môžete reštartovať odpojením a opätovným pripojením USB kábla. Nikdy ho však neodpájajte ani nepripájajte na strane dosky mikrokontroléra! Vyhnete sa tak možnosti odtrhnutia konektora na doske a tým pádom aj jej celkovému poškodeniu. Miesto toho odpájajte USB kábel vždy na strane počítača!

  • V prípade, že chcete mikrokontrolér reštartovať softvérovo, môžete tak urobiť príkazom:

    from machine import reset; reset()
  • Tzv. mäkký reset (z angl soft reset) zariadenia sa dá vykonať dokonca ešte jednoduchšie - stlačením klávesovej skratky CTRL+D v prostredí REPL (terminál, resp. v paneli Shell) vývojového prostredia Thonny.

Funkčné minimum

V prvom kroku vytvoríme veľmi jednoduchú a prvoplánovú implementáciu (budúceho) chytrého senzora. Vytvoríme teda jednoduchý kód, pomocou ktorého odmeriame aktuálne hodnoty zo senzora a vypíšeme ich do terminálu.

Rozloženie pinov na senzoroch DHT11 a DHT22

Task

V súbore main.py vytvorte kód, pomocou ktorého prečítate aktuálne hodnoty zo senzora a vypíšete ich do terminálu.

Súbor main.py má dôležité postavenie, pretože sa spustí po zapnutí mikrokontroléra.

Do tohto súboru vložte nasledujúci kód:

# main.py
from dht import DHT11 as DHT
from machine import Pin

pin = Pin(27, Pin.IN)
sensor = DHT(pin)

sensor.measure()
temp = sensor.temperature()
hum = sensor.humidity()

print(temp, hum)

Task

Spustite vytvorený program a overte jeho správnosť.

Spustiť program je možné viacerými spôsobmi:

  • V editore Thonny môžete spustiť aktuálne otvorený skript kliknutím na tlačidlo Spustiť aktuálny skript alebo stlačiť kláves F5.

  • Odpojením a pripojením dosky.

  • Stlačením klávesovej skratky CTRL+D, čím dôjde k mäkkému resetu zariadenia.

Konfigurácia chytrého senzora

Niektoré vlastnosti chytrého zariadenie bude môcť meniť používateľ za behu, ako napr. údaje pre pripojenie do WiFi siete alebo interval snímania. Niektoré naopak bude definovať výrobca a nebude ich možné nijako zmeniť, ako napr. pracovná frekvencia mikrokontroléra alebo čísla GPIO pinov, ku ktorým budú pripojené jednotlivé elektronické prvky. Nami vytváraný chytrý senzor pripravíme na obe možnosti konfigurácie.

Nemennú, resp. statickú konfiguráciu vyriešime v podobe samostatného modulu jazyka Python, v ktorom jednotlivé vlastnosti zadefinujeme v podobe premenných s príslušnými hodnotami. Tento modul sa bude volať constants.py.

Konfiguráciu, ktorú bude možné meniť, budeme volať používateľské nastavenia. To bude možné napr. pomocou webového rozhrania senzora, pomocou mobilnej aplikácie alebo vzdialene pomocou vhodného komunikačného protokolu. Samotné vlastnosti budeme ukladať na zariadení do súboru typu JSON, pretože manipulácia s týmto typom súborov je v jazyku Python veľmi jednoduchá. Súbor, v ktorom budú používateľské nastavenia uložené, sa bude volať settings.json.

V tomto kroku vytvoríme statickú konfiguráciu zariadenia.

Task

Vytvorte modul constants.py a v ňom vytvorte premenné označujúce GPIO piny, ku ktorým sú pripojené jednotlivé elektronické komponenty.

V našom prípade to budú premenné:

  • DHT_PIN - pin, ku ktorému je pripojený senzor DHT11
  • BTN_PIN - pin, ku ktorému je pripojené tlačidlo
  • NP_PIN - pin, ku ktorému je pripojený NeoPixel

Výsledný modul môže vyzerať takto:

# constants.py
DHT_PIN = 27
BTN_PIN = 20
NP_PIN = 18

Task

Upravte vašu implementáciu v module main.py tak, aby použila vytvorený modul so statickou konfiguráciou constants.py.

Task

Otestujte aktualizovaný kód.

Ak ste postupovali správne, funkcionalita kódu sa nezmení.

Používateľské nastavenia

Pre prácu s používateľskými nastaveniami (a nie len s nimi) budeme používať tzv. dátové triedy. Je to špeciálny typ triedy, ktorý slúži predovšetkým na ukladanie dát a uľahčuje prácu s objektmi, ktoré majú prevažne iba atribúty (premenné) a nepotrebujú veľa špeciálnej logiky.

Jazyk MicroPython však neobsahuje priamu podporu pre dátové triedy a ako jazyk má značné obmedzenia vo vlastnostiach, ktoré sa pre prácu s nimi používajú (hlavne tzv. typehinting). Pre naše potreby použijeme jednoduchú implementáciu, ktorá sa podobá na populárnu knižnicu pydantic.

Naša konfigurácia bude aktuálne obsahovať len jednu vlastnosť a tou bude jednotka teploty, v ktorej budeme vracať nameranú hodnotu. Senzor predvolene meria teplotu v stupňoch Celzia. Okrem toho bude náš chytrý senzor možné nastaviť, aby vedel vrátiť hodnotu v stupňoch Fahrenhaita alebo v Kelvinoch.

Vlastnosť v konfiguračnom súbore sa bude volať units a jej možné hodnoty sú:

  • metric - stupne Celzia
  • imperial - stupne Fahrenhaita
  • standard - Kelvin, predvolená hodnota

Task

V module constants.py vytvorte samostatnú triedu TempUnit, ktorá bude obsahovať konštanty pre reprezentáciu jednotlivých merných jednotiek teploty.

Trieda TempUnit bude fungovať podobne ako enumeračný typ - bude obsahovať iba potrebné konštanty. Jej implementácia môže vyzerať napr. takto:

class TempUnit:
    IMPERIAL: str = 'imperial'
    STANDARD: str = 'standard'
    METRIC: str = 'metric'

Task

V koreňovom priečinku zariadenia vytvorte balík models a stiahnite do neho modul udataclasses.py.

V tomto balíku sa budú nachádzať všetky naše modely, ktoré vytvoríme pre chytré zariadenie.

Task

Pridajte nový model Settings, ktorý bude potomkom triedy Dataclass a bude obsahovať jednu premennú s názvom units s predvolenou hodnotou TempUnit.STANDARD.

from udataclasses import Dataclass
from constants import TempUnit

class Settings(Dataclass):
    units: str = TempUnit.STANDARD

Task

V triede Settings vytvorte funkciu check_units(), ktorá bude reprezentovať validátor na overenie, či hodnota priradená do členskej premennej units obsahuje správnu hodnotu.

@validator('units')
def check_units(self, value):
    if value not in (TempUnit.METRIC, TempUnit.STANDARD, TempUnit.IMPERIAL):
        raise ValueError(f'Unit "{value}" is invalid.')

Task

Otestujte vytvorenú implementáciu.

Vašu vytvorenú triedu môžete otestovať priamo z REPL režímu napr. takto:

>>> from models.settings import Settings
>>> s = Settings()
>>> s
Settings(units='standard')
>>> s.units = 'kosicka standarda'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "models/udataclasses.py", line 76, in __setattr__
  File "models/udataclasses.py", line 17, in __call__
  File "models/settings.py", line 25, in check_units
ValueError: Unit "kosicka standarda" is invalid.

Načítanie používateľských nastavení zo súboru

Používateľské nastavenia sa na zariadení budú nachádzať v súbore settings.json. V tomto kroku vytvoríme funkciu get_settings(), pomocou ktorej ich načítame.

Task

Do modulu constants.py vložte premennú SETTINGS_FILE, ktorá bude obsahovať cestu k súboru s nastaveniami používateľa.

Hodnota premennej bude obsahovať absolútnu cestu k súboru s nastaveniami a teda /settings.json.

Task

Vytvorte súbor s používateľskými nastaveniami a do kľúču units uložte hodnotu metric.

Súbor settings.json bude vyzerať takto:

{
  "units": "metric"
}

Task

V module helpers.py vytvorte funkciu get_settings(), ktorá načíta konfiguračný súbor zo súborového systému mikrokontroléra a vráti ho ako objekt typu Settings.

Jednoduchá implementácia funkcie môže vyzerať napríklad takto:

# helpers.py
import json

from constants import SETTINGS_FILE
from models.settings import Settings


def get_settings() -> Settings:
    with open(SETTINGS_FILE, 'r') as file:
        settings = json.load(file)
        return Settings(**settings)

Task

Overte správnosť svojej implementácie.

Nezabudnite, že funkciu viete otestovať aj priamo z REPL (v paneli Shell) napríklad takto:

>>> from helpers import get_settings
>>> s = get_settings()
>>> s
Settings(units='metric')

Meriame teplotu v správnych jednotkách

Zo senzora teploty získame teplotu ako celé číslo v stupňoch Celzia. V tomto kroku vytvoríte funkciu convert_temp(), ktorá premení a vráti hodnotu teploty v jednotkách podľa nastavení používateľa. Funkciu vytvoríte v module helpers.py.

Task

Vytvorte funkciu convert_temp(), ktorá prevedie hodnotu teploty do správnych jednotiek.

Funkcia bude mať tieto parametre:

  • value - nameraná hodnota v stupňoch Celzia, ktorá bude typu int
  • units - reťazec, ktorého hodnota môže byť jedna z hodnôt standard | metric | imperial

Funkcia vráti hodnotu typu float, ktorá bude reprezentovať hodnotu teploty v jednotkách zadaných v parametri units. V prípade, že hodnota parametra units nebude správna, vyvolajte výnimku ValueError.

from constants import TempUnit


def convert_temp(value: float, units: str) -> float:
    if units == TempUnit.METRIC:
        return value

    if units == TempUnit.IMPERIAL:
        return value * 9/5 + 32

    if units == TempUnit.STANDARD:
        return value + 273.15

    raise ValueError(f'Unit "{units}" is invalid.')

Task

Overte vytvorenú implementáciu.

Ak ste postupovali správne, zmenou hodnoty kľúča v konfiguračnom súbore dostanete nameranú hodnotu teploty v rozdielnych jednotkách.

Signalizácia LED diódou

Chytrý senzor je vybavený viacfarebnou LED diódou, ktorej hlavnou funkciou je pomocou rozličných farieb poskytovať spätnú väzbu pre používateľa ohľadom stavu senzora. V tomto kroku túto LED diódu oživíme.

Najprv v module constants.py vytvoríme triedu Color, ktorá bude obsahovať definície RGB trojíc predstavujúcich farby používané senzorom. Následne na začiatku merania rozsvietime LED diódu na zeleno a po jeho ukončení ju zhasneme.

Task

V module constants.py vytvorte triedu Color, ktorá bude obsahovať definície RGB trojíc predstavujúcich farby používané senzorom.

Definícia triedy Color môže vyzerať napr. takto:

class Color:
    RED: tuple = (255, 0, 0)
    GREEN: tuple = (0, 255, 0)
    BLUE: tuple = (0, 0, 255)
    YELLOW: tuple = (255, 255, 0)
    CYAN: tuple = (0, 255, 255)
    MAGENTA: tuple = (255, 0, 255)
    ORANGE: tuple = (255, 165, 0)
    OFF: tuple = (0, 0, 0)

Task

Zabezpečte, aby sa dióda po zapnutí senzora rozsvietila na zeleno.

Implementácia závisí od použitej diódy. V prípade, že používate adresovateľné RGB LED diódy NeoPixel, tak rozsvietenie na zelenú farbu bude vyzerať takto:

from neopixel import NeoPixel

from constants import NP_PIN, Color


led = NeoPixel(Pin(NP_PIN, Pin.OUT), 1)
led[0] = Color.GREEN
led.write()

Task

Rovnako zabezpečte, aby dióda po ukončení merania zhasla.

Podobne, ako sme diódu rozsvietili, tak ju aj zhasneme. Za predpokladu, že je objekt led stále inicializovaný, LED diódu zhasneme nasledovne:

led[0] = Color.OFF
led.write()

Task

Overte vytvorenú implementáciu.

Ak ste postupovali správne, tak sa LED dióda pri spustení mikrokontroléra rozsvieti na zeleno a po ukončení merania zhasne. Nakoľko celý proces prebehne veľmi rýchlo, bude sa jednať o krátke bliknutie.

Pred odchodom z cvičenia…

Ďalšie úlohy

  1. Súbor s konfiguráciou nemusí byť valídny. V prípade, že sa tak stane, dôjde pri jeho načítaní k výnimke. Aktualizujte preto funkciu read_settings() tak, aby v prípade pokusu o načítanie nevalídneho konfiguračného súboru tento súbor prepísala predvolenou konfiguráciou.

  2. Jazyk MicroPython nemá k dispozícii modul na logovanie. Preto vytvorte samostatný modul log.py, ktorý chýbajúcu funkcionalitu poskytne. V tomto module vytvorte (minimálne) tieto funkcie:

    • debug() - Funkcia na vypísanie logovacích správ úrovne DEBUG.
    • info() - Funkcia na vypísanie logovacích správ úrovne INFO.
    • warning() - Funkcia na vypísanie logovacích správ úrovne WARNING.
    • error() - Funkcia na vypísanie logovacích správ úrovne ERROR.
    • critical() - Funkcia na vypísanie logovacích správ úrovne CRITICAL.

    Pre potreby logovania vytvorte globálnu premennú LOG_LEVEL, pomocou ktorej bude možné nastaviť úroveň logovania globálne pre celé zariadenie. To znamená, že ak bude nastavená vyššia úroveň logovania (napr. úroveň ERROR), nebudú sa zobrazovať logovacie správy nižších úrovní (napr. v prípade úrovne logovania ERROR sa nebudú zobrazovať správy úrovní DEBUG, INFO a WARNING).

Ďalšie zdroje

  1. Getting started with Raspberry Pi Pico - Návod ako začať s RPi Pico pre každého

  2. Getting started with your Raspberry Pi Pico W - Návod, ako začať s RPi Pico W

  3. Introduction to Raspberry Pi Pico: LEDs, buzzers, switches, and dials - sada návodov na prácu s RPi Pico

  4. Quick reference for the RP2 - (krátka) dokumentácia jazyka MicroPython pre dosku Raspberry Pi Pico

  5. MicroPython Chapters: Súborový systém

  6. MicroPython Chapters: Dátové triedy

  7. MicroPython: Controlling NeoPixels

  8. MicroPython: Quick reference for the RP2: NeoPixel and APA106 driver

  9. Random Nerd Tutorials: MicroPython: WS2812B Addressable RGB LEDs with ESP32 and ESP8266 - This tutorial shows how to control WS2812B addressable RGB LEDs (neopixels) with the ESP32 and ESP8266 using MicroPython.

  10. Last Minute Engineers: Interfacing DHT11 and DHT22 Sensors with Arduino

  11. Last Minute Engineers: Controlling WS2812B Addressable LEDs with Arduino