3.1. Microcontrollori¶
La OpenMV Cam funziona su un microcontrollore (MCU): un singolo chip che combina una CPU, memoria di lavoro (RAM), memoria per i programmi (flash) e una serie di periferiche – blocchi hardware per interagire con il mondo esterno.
Le periferiche sono la parte interessante. Ognuna è una porzione di silicio dedicata a un solo compito: portare un pin a livello alto o basso, misurare una tensione analogica, far uscire byte su un bus seriale. La CPU configura e legge ogni periferica tramite i registri – indirizzi di memoria fissi che l’hardware osserva e aggiorna.
MicroPython incapsula quei registri in classi all’interno del modulo machine. machine.Pin(...) restituisce un oggetto che controlla un pin di input/output general-purpose (GPIO) – un filo che il chip può mantenere alto (circa 3,3 V) o basso (circa 0 V), oppure leggere come uno di questi due stati quando qualcosa di esterno lo pilota. machine.ADC(...) espone il convertitore analogico-digitale, che misura la tensione su un pin e la riporta come numero. machine.UART(...) gestisce un universal asynchronous receiver/transmitter (UART) – una periferica che invia e riceve byte un bit alla volta su una coppia di fili, TX (trasmissione) e RX (ricezione). Altre classi coprono le restanti periferiche. Lo script legge e scrive oggetti Python; MicroPython traduce ogni accesso nelle corrispondenti letture e scritture di registro, e queste muovono bit su fili fisici.
Un MCU racchiude CPU, memoria e periferiche in un singolo chip. Ogni periferica è esposta a Python da una classe nel modulo machine.¶
3.1.1. Il ciclo principale¶
Quasi ogni programma per microcontrollore condivide la stessa struttura: una configurazione iniziale in cima allo script (import dei moduli, configurazione dei pin, apertura dei bus), seguita da un ciclo infinito while True: in fondo. All’interno del ciclo, il programma legge gli ingressi, prende decisioni e aggiorna le uscite più e più volte. Il ciclo è il programma; quando lo script termina, il dispositivo smette di fare qualsiasi cosa.
# setup, runs once
from machine import Pin
led = Pin("P0", Pin.OUT)
# main loop, runs forever
while True:
led.value(1)
# ... do work ...
led.value(0)
# ... do other work ...
Questa struttura – configura una volta, poi cicla all’infinito – è il pattern del ciclo principale. Tutto ciò che segue riguarda cosa va al suo interno.
3.1.2. Controllo in tempo reale¶
Un programma desktop viene eseguito insieme a molti altri. Il sistema operativo pianifica il suo lavoro su uno o più thread – flussi di esecuzione indipendenti tra cui passa millisecondo per millisecondo. Quando un thread attende un’operazione di I/O (disco, rete, l’utente che muove il mouse), il sistema operativo cede la CPU a un altro. Il programma è perlopiù guidato dagli eventi: il gestore delle finestre richiama il tuo codice quando arriva un input, la libreria HTTP riprende il tuo codice quando arrivano byte sul socket. È qualcosa di più grande che chiama te.
Un programma per microcontrollore è l’opposto. Per impostazione predefinita non c’è alcun sistema operativo, nessuno scheduler e nessun altro thread. Il ciclo principale appena mostrato è l”unico ciclo. Le periferiche generano interrupt o espongono flag di stato; il ciclo le interroga o gestisce direttamente gli interrupt. Se il ciclo si blocca in un time.sleep_ms(1000), il dispositivo non fa nulla per quel secondo; non c’è alcun altro thread a colmare il vuoto.
Ne derivano due conseguenze che valgono ovunque:
Il tempo è reale. Leggere un pin due volte in un ciclo stretto richiede microsecondi; restare in attesa per dieci millisecondi significa dieci millisecondi in cui non accade nient’altro. Il pattern di temporizzazione non bloccante è la risposta.
L’hardware è reale. Impostare
machine.Pin.valuea1mette circa 3,3 V su un filo fisico; impostarlo a0vi mette circa 0 V. Le altre parti del circuito vedono quella tensione immediatamente – compresi eventuali componenti che il pin può danneggiare se viene pilotato in modo errato.