Source code for openlabctrl.serial.uart

from __future__ import annotations
from ..io.digital import DigitalIo


[docs] class UART: """ Bit-banged UART transmitter over a :class:`~openlabctrl.io.digital.DigitalIo` port. Transmits standard UART frames (start bit + data bits LSB-first + optional parity + stop bits). TX is implemented open-drain: the output register is pre-loaded to ``0`` and the pin is toggled between driven-low (tristate=0) and high-Z (tristate=1). The internal pull-up resistors on the :class:`~openlabctrl.io.digital.DigitalIo` ports keep both TX and RX lines idle-high (mark state) without requiring external pull-ups. :param io: :class:`~openlabctrl.io.digital.DigitalIo` instance used for bit-banging. :param baud: Baud rate in bits per second. The bit period is derived from the IO clock frequency. :param data_len: Number of data bits per frame (default: 8). :param stop_len: Number of stop bits per frame (default: 1). :param parity: Parity mode. ``0`` = even, ``1`` = odd, ``None`` = no parity (default). :param tx_mask: Single-bit mask selecting the TX pin. :param rx_mask: Single-bit mask selecting the RX pin. """ def __init__(self, io: DigitalIo, baud: int, data_len=8, stop_len=1, parity=None, tx_mask: int = 0b0001, rx_mask: int = 0b0010): if type(io) != DigitalIo: raise Exception(f"UART requires a DigitalIo instance for IO control. Got {type(io)} instead.") if parity not in [0, 1, None]: raise Exception(f"Parity must be 0 (even), 1 (odd), or None (no parity). Got {self._parity} instead.") self._io = io self._baud = baud self._data_len = data_len self._stop_len = stop_len self._parity = parity self._tx_mask = tx_mask self._rx_mask = rx_mask
[docs] def io_config(self): """ Configure pin directions and pre-load the TX output register. Sets both TX and RX to high-Z (idle/input mode) and pre-loads the TX output register to ``0`` so that driving TX low (start bit) is simply a matter of switching the pin to driven mode. Call this once before the first transmission. """ self._io.tristate(val=(self._rx_mask | self._tx_mask), mask=(self._rx_mask | self._tx_mask)) self._io.output(val=0, mask=self._tx_mask)
[docs] def write(self, data: bytes | str): """ Transmit one or more bytes over TX. Each byte is framed as: start bit (low), ``data_len`` data bits LSB-first, optional parity bit, and ``stop_len`` stop bits (high). Strings are UTF-8 encoded before transmission. :param data: Data to transmit. Accepts ``bytes`` or ``str``. """ if not isinstance(data, (bytes, str)): raise Exception(f"UART write requires a bytes or str object. Got {type(data)} instead.") if isinstance(data, str): data = data.encode() t_bit = int(self._io._clk_freq / self._baud) for d in data: # Start bit self._io.tristate(val=0, mask=self._tx_mask) self._io.delay(t_bit) # Data bits (LSB first) for i in range(self._data_len): bit = ((d >> i) & 0x1) * self._tx_mask self._io.tristate(val=bit, mask=self._tx_mask) self._io.delay(t_bit) # Parity bit if self._parity is not None: parity_data = 0 for i in range(self._data_len): parity_data ^= ((d >> i) & 0x1) parity_bit = ((parity_data ^ self._parity) & 0x1) * self._tx_mask self._io.tristate(val=parity_bit, mask=self._tx_mask) self._io.delay(t_bit) # Stop bits self._io.tristate(val=self._tx_mask, mask=self._tx_mask) self._io.delay(t_bit * self._stop_len)