3.19. UART w kodzie

machine.UART opakowuje jeden sprzętowy kanał UART. Utwórz obiekt, podając id magistrali oraz szybkość transmisji; wszystko inne ma rozsądne wartości domyślne:

from machine import UART

uart = UART(3, baudrate=115200)

Parametr id wybiera, który sprzętowy UART zostanie użyty; wartość zależy od płytki (zobacz Płytki OpenMV, aby poznać dostępne numery magistral i przypisania pinów na danej kamerze). Domyślny format ramki to 8 bitów danych, brak parzystości, jeden bit stopu – powszechnie oczekiwane „8N1”.

3.19.1. Zapis i odczyt

Wyjście UART odbywa się przez write():

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

write przyjmuje albo str (kodowany jako UTF-8), albo bytes / bytearray. Zwraca natychmiast, gdy tylko bajty zostaną umieszczone w kolejce bufora TX; sprzęt kończy ich wytaktowywanie w tle.

Odczyty odbywają się za pomocą trzech metod, w zależności od potrzeb:

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() sprawdza bufor RX bez blokowania. read() odczytuje stałą liczbę bajtów, zwracając None, jeśli najpierw upłynie limit czasu (konfigurowalny przez argument timeout w konstruktorze). readline() odczytuje bajty aż do następnego znaku nowej linii włącznie, co jest przydatne w protokołach liniowych.

Prosta pętla, która odsyła echem wszystko, co odbierze:

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

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

read() bez argumentu długości odczytuje bajty, dopóki linia odbiorcza pozostaje cicha przez skonfigurowany timeout, a następnie zwraca to, co zostało zgromadzone. Przy timeout=100 każde wywołanie zwraca tutaj jedną porcję bajtów – wszystko, co nadajnik wytaktował bez przerwy 100 ms między bajtami. Bez limitu czasu wywołanie nie miałoby żadnego sygnału, że nadajnik zakończył, i mogłoby zawiesić się na czas nieokreślony.

3.19.2. Dane binarne z modułem struct

Do wysyłania liczb całkowitych i zmiennoprzecinkowych po linii służy moduł struct. Pakuje on wartości o stałej szerokości w obiekt bytes, używając łańcucha formatu, który określa kolejność bajtów oraz typ każdego pola:

import struct

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

Wiodące "<" wybiera kolejność bajtów little-endian; "l" to 32-bitowa liczba całkowita ze znakiem, "h" to 16-bitowa liczba całkowita ze znakiem, "b" to 8-bitowa liczba całkowita ze znakiem. Druga strona rozpakowuje dane tym samym łańcuchem formatu:

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

Łańcuch formatu jest umową między dwoma końcami. Każda niezgodność – błędna kolejność bajtów, błędne rozmiary typów, błędna kolejność pól – daje bezsensowne wartości.

3.19.3. Buforowanie i odczyty z dużą szybkością

Pętla odpytująca, która wywołuje any() i read(), sama radzi sobie z większością ruchu, o ile główna pętla wykonuje się wystarczająco szybko, by opróżnić bufor odbiorczy, zanim się zapełni. Dwie opcje konstruktora mają znaczenie, gdy szybkość rośnie lub gdy pętla jest zajęta.

rxbuf ustawia rozmiar programowego bufora RX. Domyślnie wynosi kilkaset bajtów; w przypadku sensorów emitujących setki bajtów na porcję lub gdy główna pętla wykonuje długą pracę między odpytaniami, powiększenie bufora zapobiega gubieniu przychodzących bajtów, gdy pętla jest zajęta czymś innym:

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

Wszystko, co przybywa, gdy bufor jest pełny, zostaje utracone; dobierz rozmiar rxbuf tak, aby pokryć najdłuższą przerwę między opróżnieniami.

Przy trwale wysokich szybkościach readinto() odczytuje dane do wcześniej zaalokowanego bufora zamiast zwracać przy każdym wywołaniu nowy obiekt bytes:

buf = bytearray(256)

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

Na ścieżce odczytu nie dochodzi do żadnej alokacji, co ma znaczenie, gdy sterta jest pofragmentowana lub gdy opóźnienie alokacji w przeciwnym razie przekroczyłoby budżet czasowy między bajtami.