3.19. UART в коде

machine.UART оборачивает один аппаратный канал UART. Создайте объект с идентификатором шины и скоростью передачи; всё остальное имеет разумные значения по умолчанию:

from machine import UART

uart = UART(3, baudrate=115200)

id выбирает, какой аппаратный UART использовать; значение зависит от платы (см. Платы OpenMV для доступных номеров шин и назначений выводов на конкретной камере). Формат кадра по умолчанию – 8 битов данных, без чётности, один стоповый бит – тот самый «8N1», который все ожидают.

3.19.1. Запись и чтение

Вывод UART идёт через write():

uart.write("hello\n")
uart.write(b"\x01\x02\x03")

write принимает либо str (кодируется как UTF-8), либо bytes / bytearray. Он возвращает управление сразу же после постановки байтов в очередь TX-буфера; аппаратура завершает их вывод в фоне.

Чтение происходит тремя методами, в зависимости от того, что нужно:

n = uart.any()              # bytes available to read right now
data = uart.read(8)         # up to 8 bytes, or None on timeout
line = uart.readline()      # bytes ending in '\n', or None on timeout

any() проверяет RX-буфер без блокировки. read() читает фиксированное число байтов, возвращая None, если сначала истекает таймаут (настраивается через аргумент timeout в конструкторе). readline() читает вплоть до следующего символа новой строки включительно, что полезно для построчных протоколов.

Простой цикл, который отражает всё, что получает:

uart = UART(3, baudrate=115200, timeout=100)

while True:
    if uart.any():
        data = uart.read()
        uart.write(data)

read() без аргумента длины продолжает читать байты, пока приёмная линия не замолкнет на заданный timeout, затем возвращает всё, что накопилось. С timeout=100 каждый вызов здесь возвращает одну пачку байтов – всё, что отправитель вывел без паузы в 100 мс между байтами. Без таймаута у вызова не было бы сигнала о том, что отправитель закончил, и он мог бы зависнуть на неопределённое время.

3.19.2. Двоичные данные с помощью struct

Передача целых чисел и чисел с плавающей точкой по линии – это то, для чего предназначен модуль struct. Он упаковывает значения фиксированной ширины в объект bytes с помощью строки формата, которая задаёт порядок байтов и тип каждого поля:

import struct

uart.write(struct.pack("<lhb", count, temperature_x100, status))

Ведущий "<" выбирает порядок байтов little-endian; "l" – это 32-битное целое со знаком, "h" – 16-битное целое со знаком, "b" – 8-битное целое со знаком. Другая сторона распаковывает с помощью той же строки формата:

payload = uart.read(7)        # 4 + 2 + 1 = 7 bytes
count, temperature_x100, status = struct.unpack("<lhb", payload)

Строка формата – это контракт между двумя концами. Любое несоответствие – неправильный порядок байтов, неправильные размеры типов, неправильный порядок полей – даёт бессмысленные значения.

3.19.3. Буферизация и высокоскоростное чтение

Цикл опроса, вызывающий any() и read(), сам по себе справляется с большинством трафика, если основной цикл выполняет итерации достаточно быстро, чтобы опустошать приёмный буфер до его заполнения. Два параметра конструктора важны, когда скорость растёт или цикл занят.

rxbuf задаёт размер программного RX-буфера. По умолчанию это несколько сотен байтов; для датчиков, которые выдают сотни байтов за пачку, или когда основной цикл выполняет длительную работу между опросами, увеличение буфера не даёт входящим байтам теряться, пока цикл занят чем-то другим:

uart = UART(3, baudrate=115200, timeout=100, rxbuf=4096)

Всё, что приходит, пока буфер полон, теряется; размер rxbuf должен покрывать самый длинный промежуток между опустошениями.

Для устойчиво высоких скоростей readinto() читает в заранее выделенный буфер вместо возврата нового объекта bytes при каждом вызове:

buf = bytearray(256)

while True:
    n = uart.readinto(buf)
    if n:
        process(buf, n)

На пути чтения не происходит выделения памяти, что важно, когда куча фрагментирована или когда задержка выделения памяти иначе вышла бы за рамки бюджета межбайтового тайминга.