3.19. UART nel codice

machine.UART incapsula un canale UART hardware. Costruiscine uno con l’id del bus e un baud rate; tutto il resto ha valori predefiniti ragionevoli:

from machine import UART

uart = UART(3, baudrate=115200)

L”id seleziona quale UART hardware utilizzare; il valore dipende dalla scheda (vedi Schede OpenMV per i numeri di bus disponibili e le assegnazioni dei pin su una determinata cam). Il formato di frame predefinito è 8 bit di dati, nessuna parità, un bit di stop – l“«8N1» che tutti si aspettano.

3.19.1. Scrittura e lettura

L’output UART passa attraverso write():

uart.write("hello\n")
uart.write(b"\x01\x02\x03")

write accetta sia una str (codificata come UTF-8) sia un oggetto bytes / bytearray. Restituisce immediatamente il controllo una volta che i byte sono stati accodati nel buffer TX; l’hardware completa la loro trasmissione in background.

Le letture avvengono tramite tre metodi, a seconda di ciò che serve:

n = uart.any()              # bytes available to read right now
data = uart.read(8)         # up to 8 bytes, or None on timeout
line = uart.readline()      # bytes ending in '\n', or None on timeout

any() controlla il buffer RX senza bloccarsi. read() legge un numero fisso di byte, restituendo None se il timeout (configurabile tramite l’argomento timeout nel costruttore) scade prima. readline() legge fino al successivo carattere di newline incluso, utile per i protocolli basati su righe.

Un semplice ciclo che restituisce l’eco di tutto ciò che riceve:

uart = UART(3, baudrate=115200, timeout=100)

while True:
    if uart.any():
        data = uart.read()
        uart.write(data)

read() senza argomento di lunghezza continua a leggere byte finché la linea di ricezione resta inattiva per il timeout configurato, quindi restituisce tutto ciò che è stato accumulato. Con timeout=100, ogni chiamata qui restituisce una raffica di byte – tutto ciò che il mittente ha trasmesso senza un intervallo di 100 ms tra i byte. Senza un timeout, la chiamata non avrebbe alcun segnale che il mittente abbia terminato e potrebbe bloccarsi indefinitamente.

3.19.2. Dati binari con struct

L’invio di interi e numeri in virgola mobile sulla linea è ciò a cui serve il modulo struct. Impacchetta valori a larghezza fissa in un oggetto bytes utilizzando una stringa di formato che indica l’ordine dei byte e il tipo di ciascun campo:

import struct

uart.write(struct.pack("<lhb", count, temperature_x100, status))

Il "<" iniziale sceglie l’ordine dei byte little-endian; "l" è un intero con segno a 32 bit, "h" è un intero con segno a 16 bit, "b" è un intero con segno a 8 bit. L’altro lato decomprime con la stessa stringa di formato:

payload = uart.read(7)        # 4 + 2 + 1 = 7 bytes
count, temperature_x100, status = struct.unpack("<lhb", payload)

La stringa di formato è il contratto tra le due estremità. Qualsiasi discrepanza – ordine dei byte errato, dimensioni dei tipi errate, ordine dei campi errato – produce valori privi di senso.

3.19.3. Buffering e letture ad alta velocità

Un ciclo di polling che chiama any() e read() riesce a tenere il passo con la maggior parte del traffico da solo, a condizione che il ciclo principale iteri abbastanza velocemente da svuotare il buffer di ricezione prima che si riempia. Due opzioni del costruttore diventano importanti quando la velocità aumenta o quando il ciclo è impegnato.

rxbuf imposta la dimensione del buffer RX software. Il valore predefinito è di qualche centinaio di byte; per i sensori che emettono centinaia di byte per raffica, oppure quando il ciclo principale svolge lavori prolungati tra un polling e l’altro, ampliare il buffer evita che i byte in arrivo vengano persi mentre il ciclo è impegnato altrove:

uart = UART(3, baudrate=115200, timeout=100, rxbuf=4096)

Tutto ciò che arriva mentre il buffer è pieno viene perso; dimensiona rxbuf per coprire l’intervallo più lungo tra uno svuotamento e l’altro.

Per velocità elevate sostenute, readinto() legge in un buffer preallocato anziché restituire un nuovo oggetto bytes a ogni chiamata:

buf = bytearray(256)

while True:
    n = uart.readinto(buf)
    if n:
        process(buf, n)

Nessuna allocazione avviene sul percorso di lettura, il che è importante quando l’heap è frammentato o quando la latenza di allocazione spingerebbe altrimenti oltre il budget di temporizzazione tra i byte.