3.2. Temporizzazione#

Il modulo time raggruppa le funzioni per mettere in pausa (sospendere lo script per una durata nota) e per misurare quanto tempo richiede qualcosa. Su un microcontrollore queste non sono comodità accessorie; sono il modo in cui lo script regola il ritmo delle interazioni con il mondo esterno – per quanto tempo tenere un pin alto, quanto attendere tra un campione e l’altro, quanto tempo è passato dall’ultima pressione di un pulsante.

3.2.1. Mettere in pausa#

Tre funzioni di pausa bloccano lo script per la durata richiesta:

  • time.sleep(s) – mette in pausa per s secondi. Accetta un float, quindi time.sleep(0.5) attende mezzo secondo.

  • time.sleep_ms(ms) – mette in pausa per ms millisecondi. L’argomento deve essere un intero.

  • time.sleep_us(us) – mette in pausa per us microsecondi.

import time

print("now")
time.sleep_ms(500)
print("half a second later")

Usa time.sleep_ms() per le tipiche esigenze di «attendere un po”» e time.sleep_us() solo quando la temporizzazione deve essere precisa. Anche la semplice time.sleep() va bene, ma le varianti con argomento intero evitano la conversione in virgola mobile e risultano più naturali per intervalli brevi.

La pausa è una chiamata bloccante. Mentre lo script è in pausa, non sta facendo nient’altro – non legge un pin, non serve una UART, non aggiorna un LED. Ricorri a sleep quando il blocco è ciò che desideri; usa il pattern non bloccante più avanti quando non lo è.

3.2.2. Leggere l’orologio#

Per misurare quanto tempo richiede un pezzo di codice, leggi l’orologio prima e dopo:

import time

start = time.ticks_ms()
do_work()
elapsed = time.ticks_ms() - start
print("took", elapsed, "ms")

Questo funziona quasi sempre. Il contatore di tick va in overflow (torna a zero) dopo un numero grande ma finito di tick, e una sottrazione ingenua a cavallo di quell’overflow produce un numero negativo o positivo del tutto errato.

Number line from 0 to MAX with a start tick near MAX and an end tick near 0; a dashed wrap-around arrow shows that after MAX the counter returns to 0.

Il contatore di tick torna a zero quando raggiunge il limite intero. Una semplice sottrazione a cavallo di quell’overflow è errata.#

3.2.3. ticks_diff#

Per ottenere correttamente i tick trascorsi, anche a cavallo di un overflow, usa time.ticks_diff():

import time

start = time.ticks_ms()
do_work()
elapsed = time.ticks_diff(time.ticks_ms(), start)
print("took", elapsed, "ms")

L’ordine degli argomenti è ticks_diff(later, earlier) – l’espressione si legge «quanto è distante later dopo earlier«. Il risultato è un intero con segno; positivo significa che later è effettivamente successivo, negativo che è nel passato. La funzione gestisce internamente l’overflow.

Suggerimento

Abbina sempre time.ticks_ms() / time.ticks_us() a time.ticks_diff(). La sottrazione grezza è corretta quasi sempre; la volta in cui è errata è quando uno script è in esecuzione da molto tempo – di solito il momento peggiore per fare il debug di un problema di temporizzazione.

3.2.4. Temporizzazione non bloccante#

Uno script per microcontrollore di solito ha più di una cosa da fare «contemporaneamente»: leggere un pulsante, far lampeggiare un LED, interrogare un sensore, servire una UART. sleep non va bene per questo – mentre un sleep è in esecuzione, gli altri compiti sono fermi.

Il pattern standard consiste nel mantenere una scadenza per ogni compito e nel controllare a ogni ciclo se la scadenza è passata. La decisione di agire si basa su time.ticks_diff(), mai su sleep:

import time

next_blink = time.ticks_ms()
next_poll  = time.ticks_ms()
state = False

while True:
    now = time.ticks_ms()

    if time.ticks_diff(now, next_blink) >= 0:
        state = not state
        # update LED here
        next_blink = time.ticks_add(next_blink, 500)

    if time.ticks_diff(now, next_poll) >= 0:
        # read sensor here
        next_poll = time.ticks_add(next_poll, 100)

Il LED commuta ogni 500 ms, il sensore viene interrogato ogni 100 ms e nessuno dei due compiti blocca l’altro. time.ticks_add() fa avanzare una scadenza di un incremento noto senza incappare nell’overflow.

Questa struttura – un singolo ciclo, diversi compiti temporizzati, nessuno sleep nel corpo – compare ovunque vada il codice hardware: debouncing software degli interruttori, sequenziamento dei motori, timeout di lettura della UART. Le stesse tre funzioni (time.ticks_ms(), time.ticks_diff(), time.ticks_add()) coprono ogni caso.