3.2. Pomiar czasu

Moduł time grupuje funkcje do usypiania (wstrzymywania skryptu na znany czas) oraz do mierzenia, jak długo coś trwa. Na mikrokontrolerze nie są to udogodnienia opcjonalne; to dzięki nim skrypt nadaje tempo interakcjom ze światem zewnętrznym – jak długo utrzymywać pin w stanie wysokim, jak długo czekać między próbkami, ile czasu minęło od ostatniego naciśnięcia przycisku.

3.2.1. Usypianie

Trzy funkcje usypiania blokują skrypt na żądany czas:

  • time.sleep(s) – wstrzymuje na s sekund. Przyjmuje liczbę zmiennoprzecinkową, więc time.sleep(0.5) czeka pół sekundy.

  • time.sleep_ms(ms) – wstrzymuje na ms milisekund. Argument musi być liczbą całkowitą.

  • time.sleep_us(us) – wstrzymuje na us mikrosekund.

import time

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

Użyj time.sleep_ms() do typowych potrzeb „poczekaj chwilę”, a time.sleep_us() tylko wtedy, gdy pomiar czasu musi być precyzyjny. Zwykłe time.sleep() również jest w porządku, ale warianty z argumentem całkowitym unikają konwersji zmiennoprzecinkowej i czytają się bardziej naturalnie dla krótkich odstępów.

Sleep to wywołanie blokujące. Gdy skrypt śpi, nie robi nic innego – nie odczytuje pinu, nie obsługuje UART, nie aktualizuje diody LED. Sięgaj po sleep, gdy blokowanie jest tym, czego chcesz; gdy nie jest, użyj wzorca nieblokującego opisanego poniżej.

3.2.2. Odczyt zegara

Aby zmierzyć, jak długo trwa fragment kodu, odczytaj zegar przed i po:

import time

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

Działa to przez większość czasu. Licznik taktów przekręca się (wraca do zera) po dużej, ale skończonej liczbie taktów, a naiwne odejmowanie obejmujące to przekręcenie daje skrajnie błędną liczbę ujemną lub dodatnią.

Oś liczbowa od 0 do MAX z taktem początkowym blisko MAX oraz taktem końcowym blisko 0; przerywana strzałka przekręcenia pokazuje, że po MAX licznik wraca do 0.

Licznik taktów wraca do zera, gdy osiąga limit liczby całkowitej. Zwykłe odejmowanie obejmujące to przekręcenie jest błędne.

3.2.3. ticks_diff

Aby poprawnie uzyskać liczbę upłyniętych taktów, nawet przy przekręceniu, użyj time.ticks_diff():

import time

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

Kolejność argumentów to ticks_diff(later, earlier) – wyrażenie czyta się jako „jak daleko jest later po earlier„. Wynik jest liczbą całkowitą ze znakiem; dodatni oznacza, że later faktycznie jest później, ujemny oznacza, że jest w przeszłości. Funkcja obsługuje przekręcenie wewnętrznie.

Wskazówka

Zawsze łącz time.ticks_ms() / time.ticks_us() z time.ticks_diff(). Surowe odejmowanie jest poprawne przez większość czasu; momentem, w którym jest błędne, jest sytuacja, gdy skrypt działa od dłuższego czasu – zwykle najgorszy moment na debugowanie usterki czasowej.

3.2.4. Pomiar czasu bez blokowania

Skrypt mikrokontrolera zazwyczaj ma do zrobienia więcej niż jedną rzecz „jednocześnie”: odczyt przycisku, miganie diodą LED, odpytywanie sensora, obsługa UART. sleep się do tego nie nadaje – gdy działa jeden sleep, pozostałe zadania są wstrzymane.

Standardowy wzorzec polega na utrzymywaniu terminu (deadline) dla każdego zadania i sprawdzaniu w każdej iteracji pętli, czy termin już minął. Decyzja o działaniu opiera się na time.ticks_diff(), nigdy na 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)

Dioda LED przełącza się co 500 ms, sensor jest odpytywany co 100 ms, a żadne z zadań nie blokuje drugiego. time.ticks_add() przesuwa termin o znany przyrost, nie wpadając w pułapkę przekręcenia.

Ten kształt – pojedyncza pętla, kilka zadań sterowanych czasem, bez sleep w ciele – pojawia się wszędzie tam, gdzie trafia kod sprzętowy: programowe eliminowanie drgań styków przełączników, sekwencjonowanie silników, limity czasu odczytu UART. Te same trzy funkcje (time.ticks_ms(), time.ticks_diff(), time.ticks_add()) obejmują każdy przypadek.