3.19. UART en código

machine.UART envuelve un canal UART de hardware. Construye uno con el id del bus y una velocidad en baudios; todo lo demás tiene valores predeterminados razonables:

from machine import UART

uart = UART(3, baudrate=115200)

El id selecciona qué UART de hardware usar; el valor depende de la placa (consulta la Placas OpenMV para conocer los números de bus disponibles y las asignaciones de pines en una cámara dada). El formato de trama predeterminado es 8 bits de datos, sin paridad, un bit de parada – el «8N1» que todo el mundo espera.

3.19.1. Escritura y lectura

La salida UART pasa por write():

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

write acepta tanto un str (codificado como UTF-8) como un bytes / bytearray. Retorna inmediatamente una vez que los bytes están en cola en el búfer de TX; el hardware termina de transmitirlos en segundo plano.

Las lecturas se realizan a través de tres métodos, según lo que se necesite:

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() comprueba el búfer de RX sin bloquear. read() lee un número fijo de bytes, retornando None si primero expira el tiempo de espera (configurable mediante el argumento timeout en el constructor). readline() lee hasta el siguiente salto de línea inclusive, útil para protocolos basados en líneas.

Un bucle sencillo que hace eco de todo lo que recibe:

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

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

read() sin argumento de longitud sigue leyendo bytes hasta que la línea de recepción permanece en silencio durante el timeout configurado, y luego retorna lo que se haya acumulado. Con timeout=100, cada llamada aquí retorna una ráfaga de bytes – todo lo que el emisor transmitió sin una pausa de 100 ms entre bytes. Sin un tiempo de espera la llamada no tendría ninguna señal de que el emisor ha terminado y podría quedarse colgada indefinidamente.

3.19.2. Datos binarios con struct

Enviar enteros y números de coma flotante por el cable es para lo que sirve el módulo struct. Empaqueta valores de ancho fijo en un objeto bytes usando una cadena de formato que indica el orden de bytes y el tipo de cada campo:

import struct

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

El "<" inicial elige el orden de bytes little-endian; "l" es un entero con signo de 32 bits, "h" es un entero con signo de 16 bits, "b" es un entero con signo de 8 bits. El otro extremo desempaqueta con la misma cadena de formato:

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

La cadena de formato es el contrato entre los dos extremos. Cualquier discrepancia – orden de bytes incorrecto, tamaños de tipo incorrectos, orden de campos incorrecto – produce valores sin sentido.

3.19.3. Almacenamiento en búfer y lecturas a alta velocidad

Un bucle de sondeo que llama a any() y read() mantiene el ritmo con la mayor parte del tráfico por sí solo, siempre que el bucle principal itere lo bastante rápido para vaciar el búfer de recepción antes de que se llene. Dos opciones del constructor importan cuando la velocidad sube o el bucle está ocupado.

rxbuf establece el tamaño del búfer software de RX. El valor predeterminado es de unos pocos cientos de bytes; para sensores que emiten cientos de bytes por ráfaga, o cuando el bucle principal realiza trabajo prolongado entre sondeos, ampliar el búfer evita que los bytes entrantes se descarten mientras el bucle está ocupado en otra cosa:

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

Todo lo que llega mientras el búfer está lleno se pierde; dimensiona rxbuf para cubrir la pausa más larga entre vaciados.

Para velocidades altas sostenidas, readinto() lee en un búfer preasignado en lugar de retornar un objeto bytes nuevo en cada llamada:

buf = bytearray(256)

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

No se produce ninguna asignación en la ruta de lectura, lo cual importa cuando el montículo está fragmentado o cuando la latencia de asignación empujaría más allá del margen de temporización entre bytes.