6.19. UART in code¶
machine.UART wraps one hardware UART channel. Construct
one with the bus id and a baud rate; everything else has
reasonable defaults:
from machine import UART
uart = UART(3, baudrate=115200)
The id selects which hardware UART to use; the value
depends on the board (see the OpenMV Boards for
the available bus numbers and pin assignments on a given cam).
The default frame format is 8 data bits, no parity, one stop
bit – the “8N1” everyone expects.
6.19.1. Writing and reading¶
UART output goes through write():
uart.write("hello\n")
uart.write(b"\x01\x02\x03")
write accepts either a str (encoded as UTF-8) or a
bytes / bytearray. It returns immediately once the
bytes are queued in the TX buffer; the hardware finishes
clocking them out in the background.
Reads happen through three methods, depending on what is needed:
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() checks the RX buffer without
blocking. read() reads a fixed number of
bytes, returning None if the timeout (configurable via the
timeout argument in the constructor) expires first.
readline() reads up to and including the
next newline, useful for line-based protocols.
A simple loop that echoes whatever it receives:
uart = UART(3, baudrate=115200, timeout=100)
while True:
if uart.any():
data = uart.read()
uart.write(data)
read() with no length argument keeps reading bytes until
the receive line stays quiet for the configured timeout,
then returns whatever was accumulated. With timeout=100,
each call here returns one burst of bytes – everything the
sender clocked out without a 100 ms gap between bytes. Without
a timeout the call would have no signal that the sender is
done and could hang indefinitely.
6.19.2. Binary data with struct¶
Sending integers and floats over the wire is what the
struct module is for. It packs fixed-width values into
a bytes object using a format string that names the byte
order and the type of each field:
import struct
uart.write(struct.pack("<lhb", count, temperature_x100, status))
The leading "<" chooses little-endian byte order; "l"
is a 32-bit signed integer, "h" is a 16-bit signed integer,
"b" is an 8-bit signed integer. The other side unpacks with
the same format string:
payload = uart.read(7) # 4 + 2 + 1 = 7 bytes
count, temperature_x100, status = struct.unpack("<lhb", payload)
The format string is the contract between the two ends. Any mismatch – wrong byte order, wrong type sizes, wrong field order – produces nonsense values.
6.19.3. Buffering and high-rate reads¶
A polling loop that calls any() and
read() keeps up with most traffic on its
own, as long as the main loop iterates fast enough to drain
the receive buffer before it fills. Two constructor options
matter when the rate climbs or the loop is busy.
rxbuf sets the size of the software RX buffer. The default
is a few hundred bytes; for sensors that emit hundreds of
bytes per burst, or when the main loop does long work between
polls, enlarging the buffer keeps incoming bytes from being
dropped while the loop is busy elsewhere:
uart = UART(3, baudrate=115200, timeout=100, rxbuf=4096)
Anything that arrives while the buffer is full is lost; size
rxbuf to cover the longest gap between drains.
For sustained high rates, readinto() reads
into a pre-allocated buffer instead of returning a fresh
bytes object each call:
buf = bytearray(256)
while True:
n = uart.readinto(buf)
if n:
process(buf, n)
No allocation happens on the read path, which matters when the heap is fragmented or when allocation latency would otherwise push past the inter-byte timing budget.