3.2. Отсчёт времени

Модуль time объединяет функции для сна (приостановки скрипта на известную продолжительность) и для измерения того, сколько времени занимает то или иное действие. На микроконтроллере это не приятные мелочи; именно так скрипт задаёт темп взаимодействия с внешним миром – как долго удерживать вывод в высоком уровне, как долго ждать между выборками, сколько времени прошло с момента последнего нажатия кнопки.

3.2.1. Сон

Три функции сна блокируют скрипт на запрошенную продолжительность:

  • time.sleep(s) – пауза на s секунд. Принимает число с плавающей точкой, так что time.sleep(0.5) ждёт полсекунды.

  • time.sleep_ms(ms) – пауза на ms миллисекунд. Аргумент должен быть целым числом.

  • time.sleep_us(us) – пауза на us микросекунд.

import time

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

Используйте time.sleep_ms() для типичной потребности «немного подождать» и time.sleep_us() только когда время должно быть выдержано точно. Обычная time.sleep() тоже подходит, но варианты с целочисленным аргументом избегают преобразования с плавающей точкой и читаются естественнее для коротких интервалов.

Сон – это блокирующий вызов. Пока скрипт спит, он не делает ничего другого – не читает вывод, не обслуживает UART, не обновляет светодиод. Используйте sleep, когда блокировка – это именно то, что вам нужно; используйте неблокирующий паттерн ниже, когда это не так.

3.2.2. Чтение часов

Чтобы измерить, сколько времени занимает фрагмент кода, считайте часы до и после:

  • time.ticks_ms() – текущее значение тиков в миллисекундах.

  • time.ticks_us() – то же самое в микросекундах.

import time

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

Это работает в большинстве случаев. Счётчик тиков переполняется (возвращается к нулю) после большого, но конечного числа тиков, и наивное вычитание через это переполнение даёт совершенно неверное отрицательное или положительное число.

Числовая ось от 0 до MAX с начальным тиком около MAX и конечным тиком около 0; пунктирная стрелка переполнения показывает, что после MAX счётчик возвращается к 0.

Счётчик тиков возвращается к нулю, когда достигает целочисленного предела. Простое вычитание через это переполнение даёт неверный результат.

3.2.3. ticks_diff

Чтобы корректно получить количество прошедших тиков, даже через переполнение, используйте time.ticks_diff():

import time

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

Порядок аргументов – ticks_diff(later, earlier) – выражение читается как «насколько later позже earlier». Результат – целое число со знаком; положительное значение означает, что later действительно позже, отрицательное – что оно в прошлом. Функция обрабатывает переполнение внутри себя.

Совет

Всегда сочетайте time.ticks_ms() / time.ticks_us() с time.ticks_diff(). Простое вычитание корректно в большинстве случаев; момент, когда оно ошибочно, наступает тогда, когда скрипт работает уже долгое время – обычно это худшее время для отладки сбоя в отсчёте времени.

3.2.4. Неблокирующий отсчёт времени

Скрипту для микроконтроллера обычно нужно делать несколько вещей «одновременно»: читать кнопку, мигать светодиодом, опрашивать датчик, обслуживать UART. sleep для этого не годится – пока один sleep выполняется, остальные задачи простаивают.

Стандартный паттерн – хранить крайний срок для каждой задачи и проверять в каждом проходе цикла, не наступил ли он. Решение о действии строится на time.ticks_diff(), а не на 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)

Светодиод переключается каждые 500 мс, датчик опрашивается каждые 100 мс, и ни одна задача не блокирует другую. time.ticks_add() сдвигает крайний срок на известное приращение, не попадая в ловушку переполнения.

Эта структура – единственный цикл, несколько задач по времени, без sleep в теле – встречается везде, где есть код для работы с аппаратурой: программное подавление дребезга переключателей, последовательное управление двигателем, тайм-ауты чтения UART. Одни и те же три функции (time.ticks_ms(), time.ticks_diff(), time.ticks_add()) покрывают все случаи.