MicroPython:Библиотеки/utime

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Модуль utime – функции для работы со временем[1]

В этом модуле реализована часть функционала соответствующего модуля CPython. Более подробно читайте в документации к CPython о модуле time.

В модуле utime реализованы функции для считывания текущих времени и даты, измерения интервалов времени и создания задержек.

Начало отсчета времени: В Unix-порте используется стандартная для POSIX-систем точка отсчета 1970-01-01 00:00:00 по Всемирному скоординированному времени. Но в портах для встраиваемых систем используется другая точка отсчета – 2000-01-01 00:00:00 по Всемирному скоординированному времени.

Учет календарной даты/времени: Это требует часов реального времени (RTC). На системах с ОС (включая некоторые RTOS) RTC могут функционировать неявно. То есть в них настройка и синхронизация календарного времени – это ответственность ОС/RTOS, а не MicroPython. Для считывания даты/времени ОС тоже будет использовать собственные API. На «голых» портах системное время зависит от объекта machine.RTC(). Текущее календарное время задается при помощи функции machine.RTC().datetime(tuple), а синхронизируется при помощи следующих средств:

  • Резервная батарейка (она может быть опциональным компонентом платы).
  • Протокол сетевого времени (требуется настройка портом/пользователем).
  • Ручная настройка пользователем при каждом запуске (многие платы самостоятельно настраивают RTC-время после аппаратных сбросов, но есть и платы, которые в таких случаях нужно настраивать вручную).

Если RTC-инструментарий системы/MicroPython не выполняет синхронизацию с календарным временем, те функции ниже, которым требуются данные о текущем абсолютном времени, могут повести себя непредсказуемо.

Функции

  • utime.localtime([secs]) – преобразовывает время в секундах, прошедших с точки отсчета (см. выше), в 8-элементный кортеж (год, месяц, день_месяца, час, минута, секунда, день_недели, день_года). Если аргумента secs задано не будет или в нем будет задано значение None, будет использовано текущее RTC-значение.
    • В элементе год также указываются века (например, «2014»)
    • месяц: 1-12
    • день_месяца: 1-31
    • час: 0-23
    • минута: 0-59
    • секунда: 0-59
    • день_недели: 0-6 (понедельник-воскресенье)
    • день_года: 1-366
  • utime.mktime() – это функция, работающая обратно utime.localtime(). Аргументом для нее служит 8-элементный кортеж с данными о локальном времени, генерируемый функцией utime.localtime(), а ее возвращаемое значение – это целое число, выражающее количество секунд, прошедших с точки отсчета.
  • utime.sleep(seconds) – переводит устройство в режим сна на заданное количество секунд. На некоторых платах в аргументе seconds можно указать число с плавающей точкой, но на некоторых других платах такой возможности нет, поэтому для совместимости с ними используйте функции sleep_ms() и sleep_us().
  • utime.sleep_ms(ms) – создает задержку на заданное количество миллисекунд (число в ms должно быть положительным или нулем).
  • utime.sleep_ms(us) – создает задержку на заданное количество микросекунд (число в us должно быть положительным или нулем).
  • utime.ticks_ms() – возвращает значение счетчика в миллисекундах. Отсчет в счетчике ведется с любой контрольной точки, значение счетчика постоянно увеличивается, а при достижении некоторого порога – обнуляется о принципу модульной арифметики.

Это пороговое значение не демонстрируется явно, но в целях упрощения материала давайте назовем его TICKS_MAX. Периодом будет TICKS_PERIOD = TICKS_MAX + 1. Значение TICKS_PERIOD гарантированно будет степенью двойки, но в разных портах может быть разным. Для простоты в функциях ticks_ms(), ticks_us() и ticks_cpu() будет использоваться одно и то же периодное значение. Следовательно эти функции будут возвращать значение в диапазоне от 0 до TICKS_PERIOD включительно. Помните, что в этих значениях используются только неотрицательные значения. В большинстве случаев к значениям, возвращаемым этими функциями, стоит относиться как к скрытым структурам данных. Манипуляции с ними можно проводить только при помощи функций ticks_diff() и ticks_add().

Примечание: Если напрямую выполнять на этих значениях стандартные математические операции (+, -) и операции сравнения (<, <=, >, >=), это может привести к некорректным результатам. Выполнение на них математических операций и последующая передача в виде аргументов функциям ticks_diff() и ticks_add() также приведет к некорректным результатам от этих функций.

  • utime.ticks_us() – работает аналогично функции ticks_ms(), но в микросекундах.
  • utime.ticks_cpu() – работает аналогично функциям utime.ticks_ms() и utime.ticks_us(), но вместо милли- и микросекунд здесь используется наименьшая единица измерения в системе. Обычно это такт процессора, и именно поэтому функция и получила название ticks_cpu(). Но это необязательно должен быть такт процессора, вместо него может быть и другой источник информации о времени (например, высокоточный таймер). Средствами модуля эта единица измерения не задается, но информацию об этом можно найти в документации к используемому порту. Эта функция предназначена для очень точных сравнительных тестов и очень коротких циклов. В портативном коде ее использовать не рекомендуется.

Доступность: Эта функция есть не на всех портах.

  • utime.ticks_add(ticks, delta) – меняет значение ticks на заданное число delta (которое может быть и положительным, и отрицательным). То есть, имея модульно-арифметическое значение ticks и смещающее значение delta, эта функция позволяет рассчитать время до или после ticks. Значение в аргументе ticks должно быть прямым результатом функций ticks_ms(), ticks_us() или ticks_cpu() или самой ticks_add(). Но значение в delta может быть любым целым числом или численным выражением. Функция ticks_add() полезна для вычисления дедлайнов для событий/задач. Работа с дедлайнами осуществляется с помощью функции ticks_diff().

Примеры:

# Определяем значение ticks, которое было 100 миллисекунд назад: 
print(ticks_add(time.ticks_ms(), -100))

# Рассчитываем дедлайн для операции и тестируем ее:
deadline = ticks_add(time.ticks_ms(), 200)
while ticks_diff(deadline, time.ticks_ms()) > 0:
    do_a_little_of_something()

# Ищем значение TICKS_MAX для используемого порта:
print(ticks_add(0, -1))
  • utime.ticks_diff(ticks1, ticks2) – измеряет разницу между значениями, возвращенными функциями ticks_ms(), ticks_us() или ticks_cpu(). Результатом будет знаковое модульно-арифметическое значение (слово «модульно-арифметическое» означает, что это значение один или несколько раз преодолело порог TICKS_MAX).

Порядок аргументов должен быть таким же, как и при использовании оператора вычитания, т.е. ticks_diff(ticks1, ticks2) означает то же самое, что и ticks1 - ticks2. Но значения, возвращаемые функциями вроде ticks_ms(), могут быть модульно-арифметическими, поэтому если выполнить вычитание напрямую, это приведет к некорректному результату. Именно здесь вам и понадобится функция ticks_diff(), которая работает по принципу модульной (или, если точнее, циклической) арифметики и благодаря этому способна работать со значениями, сгенерированными функциями ticks_ms(), ticks_us() и ticks_cpu() (но при условии, что разница между ними не слишком велика; более подробно читайте ниже). Функция возвращает знаковое значение в диапазоне [-TICKS_PERIOD/2 … TICKS_PERIOD/2-1] (это стандартный диапазон для знаковых двоичных целых чисел дополнительного кода). Если результат будет отрицательным, то ticks1 произошло раньше ticks2, а если положительным, то ticks1 произошло позже ticks2. Но все это будет работать только в том случае, если разница между ticks1 и ticks2 не более TICKS_PERIOD/2-1. Если это условие соблюдено не будет, функция вернет некорректный результат. Если точнее, если значения будут удалены друг от друга на TICKS_PERIOD/2-1, то именно это значение функция и вернет. А если разница между ними будет равна TICKS_PERIOD/2, функция вернет -TICKS_PERIOD/2, то есть итоговое значение теперь будет находиться в отрицательном диапазоне возможных значений. Попробую немного объяснить это ограничение. Допустим, вас закрыли в комнате, где единственное средство слежения за временем – это стандартные часы с 12-часовым циферблатом. Соответственно, если вы посмотрите на эти часы сейчас, а в следующий раз – через 13 часов (предположим, вы надолго заснули), то вам будет казаться, что прошел всего один час. Чтобы избежать этой ошибки, на часы нужно смотреть регулярно. Ваше приложение, образно говоря, тоже должно регулярно смотреть на часы. Метафора «слишком долгого сна» напрямую подходит и для работы всего приложения: не позволяйте своему приложению держать задачу запущенной слишком долго. Запускайте задачи пошагово и следите за временем между запусками. Функция ticks_diff() может использоваться довольно широко. Например:

    • Для опроса с таймаутом. С условием, что вы знаете порядок событий, а функция tick_diff() возвращает положительное значение.
# Ждем, пока GPIO-контакт не получит значение «1»,
# но не больше 500 микросекунд:
start = time.ticks_us()
while pin.value() == 0:
    if time.ticks_diff(time.ticks_us(), start) > 500:
        raise TimeoutError
    • Для планирования событий. В этом случае результат ticks_diff() может быть отрицательным. Во фрагменте кода ниже это произойдет, если задача будет запущена слишком поздно.
# Этот фрагмент кода не оптимизирован:
now = time.ticks_ms()
scheduled_time = task.scheduled_time()
if ticks_diff(scheduled_time, now) > 0:
    print("Слишком рано, можно подремать")
    sleep_ms(ticks_diff(scheduled_time, now))
    task.run()
elif ticks_diff(scheduled_time, now) == 0:
    print("Самое время!")
    task.run()
elif ticks_diff(scheduled_time, now) < 0:
    print("Упс, опоздали, скорее запускаем задачу!")
    task.run(run_faster=true)

Примечание: Не передавайте функции ticks_diff() значения, возвращенные функцией time() – к этим значениям нужно применять обычные математические операции. Но помните, что счетчик функции time() тоже может переполниться. Это называется «проблемой 2038 года».

  • utime.time() – возвращает количество секунд с точки отсчета в виде целого числа с условием, что RTC-время настроено и синхронизируется как описано выше. Если RTC-время не задано, эта функция возвращает количество секунд с точки отсчета, специфичной для используемого порта (во встраиваемых платах без резервной RTC-батарейки это, как правило, момент с запуска или сброса). Если вы хотите разработать портативное MicroPython-приложение и вам нужна не просто секундная точность, а что-то получше, то на эту функцию полагаться не стоит. Для более высокой точности используйте функции ticks_ms() и ticks_us(), а если вам нужно календарное время, то здесь наилучший вариант – это localtime() без аргументов.

Отличие от Python: В CPython эта функция возвращает количество секунд с 1970-01-01 00:00:00 по Всемирному скоординированному времени в виде числа с плавающей точкой (обычно с микросекундной точностью). В MicroPython эту точку отсчета использует только Unix-порт, и при поддержке чисел с плавающей точкой время будет возвращаться с полусекундной точностью. У встраиваемых плат обычно нет полусекундной точности и поддержки чисел с плавающей точкой для больших диапазонов времени, поэтому в них используются просто целые числа с секундной точностью. Кроме того, у некоторых встраиваемых плат нет RTC-часов с батарейкой, из-за чего они возвращают количество секунд с последнего запуска или какой-то другой точки во времени, специфичной для этого устройства (например, с момента сброса).

См.также

Внешние ссылки