3.17. Битовые потоки и измерение импульсов¶
Некоторым устройствам нужны точно таймированные импульсные последовательности, а не PWM-сигнал постоянной частоты. RGB-светодиод WS2812 кодирует каждый бит как импульс определённой ширины; ультразвуковой дальномер HC-SR04 отвечает эхо-импульсом, ширина которого равна времени полёта туда и обратно; ИК-пульт посылает заголовок, за которым следуют биты данных в виде последовательностей включения-выключения.
В модуле machine есть две функции для такого рода GPIO с точным таймингом:
bitstream()посылает последовательность импульсов с раздельным таймингом для битов0и1.time_pulse_us()измеряет ширину входящего импульса в микросекундах.
3.17.1. Отправка битового потока¶
machine.bitstream() принимает вывод, кодировку, спецификацию тайминга и байты для отправки. Самая распространённая кодировка (0) – это широтно-импульсная модуляция high-low: каждый бит – это высокий импульс одной ширины, за которым следует низкий импульс другой, причём биты 0 и 1 имеют различные тайминги.
Кодировка длительностью импульса high-low: 0 и 1 каждый состоят из высокой фазы, за которой следует низкая фаза, с различными ширинами.¶
Канонический пример – RGB-светодиод WS2812 (NeoPixel), который ожидает биты на частоте 800 кГц со следующими таймингами:
0: 400 нс высокий, затем 850 нс низкий.1: 800 нс высокий, затем 450 нс низкий.
from machine import Pin, bitstream
pin = Pin("P7", Pin.OUT)
# (high_0, low_0, high_1, low_1) in nanoseconds
timing = (400, 850, 800, 450)
# one LED: GRB order, three bytes per LED (red shown here)
bitstream(pin, 0, timing, b"\x00\xff\x00")
MCU формирует импульсы программно (bit-bang) с запрошенными ширинами; на достаточно быстрых для этого камерах тайминг точен с погрешностью в десятки наносекунд.
Предупреждение
bitstream() отключает все прерывания на всю длительность передачи, чтобы сохранять точный контроль над таймингом импульсов. Длительность вызова линейно масштабируется с числом байтов – при тайминге WS2812 (около 10 мкс на байт) отправка 100 байтов приостанавливает CPU примерно на 1 мс, 1000 байтов – на 10 мс, а 10000 байтов – на полные 100 мс. Всё, что превышает несколько сотен байтов за вызов, рискует заметными блокировками – разбивайте длинные обновления на меньшие фрагменты, чтобы вызов возвращал управление между каждым фрагментом и остальная часть скрипта могла выполняться.
Примечание
В частности, для управления лентами WS2812 / NeoPixel модуль neopixel оборачивает bitstream() в более высокоуровневый интерфейс, который занимается перестановкой порядка цветов и массовым обновлением ленты. Обращайтесь к bitstream() напрямую, когда протокол не WS2812, но всё же укладывается в форму high-low PDM.
3.17.2. Измерение входящего импульса¶
machine.time_pulse_us() измеряет ширину одиночного импульса на выводе. Она ждёт, пока линия достигнет указанного уровня (начало импульса), затем измеряет, как долго линия остаётся на этом уровне (конец импульса), и возвращает длительность в микросекундах.
Классическое применение – ультразвуковой датчик расстояния HC-SR04. Камера посылает триггерный импульс длительностью 10 мкс, затем ждёт, пока вывод эха вернёт импульс, ширина которого равна времени прохождения звука туда и обратно:
import time
from machine import Pin, time_pulse_us
trigger = Pin("P7", Pin.OUT, value=0)
echo = Pin("P8", Pin.IN)
while True:
trigger.value(1)
time.sleep_us(10)
trigger.value(0)
width = time_pulse_us(echo, 1, timeout_us=30_000)
if width > 0:
# sound at ~343 m/s; round trip / 2 / 343 = distance (m)
distance_cm = (width * 343) / 2 / 10_000
print(distance_cm, "cm")
time.sleep_ms(100)
Третий аргумент – это тайм-аут в микросекундах, применяемый отдельно к «ожиданию начала импульса» и «ожиданию конца импульса». По тайм-ауту функция возвращает отрицательное значение, указывающее, какая фаза не удалась: -2, если импульс так и не начался, -1, если он начался, но так и не закончился в пределах окна.
Обе половины тайм-аута важны для реальных датчиков. HC-SR04 может потребоваться от одной до двух миллисекунд, чтобы начать своё эхо, а само эхо для далёких объектов может длиться десятки миллисекунд. Подбор timeout_us под максимальную нужную дальность удерживает измерение ограниченным.