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.

Il contorno di un singolo chip che contiene blocchi etichettati -- CPU, RAM, flash e una fila di blocchi periferici (GPIO, ADC, Timer/PWM, UART/SPI/I2C, CAN) collegati da un bus interno, con frecce che da ogni periferica escono dal chip verso etichette di pin 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.value a 1 mette circa 3,3 V su un filo fisico; impostarlo a 0 vi 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.