UART
Uart drives the UART peripheral of an open adapter, from
Device.uart(). Both the Supernova and the
Pulsar have it. The async twin is AsyncUart.
Bring-up
from pycosmicsdk import Device, UartBaudRate, UartDataSize, UartParity, UartStopBit
with Device.open() as dev:
uart = dev.uart()
uart.set_voltage(voltage_mv=3300)
uart.bring_up(
baudrate=UartBaudRate.BAUD_115200,
parity=UartParity.NONE,
data_size=UartDataSize.BITS_8,
stop_bit=UartStopBit.ONE,
)
bring_up is the re-runnable bring-up and the one to
reach for: it tries initialize, falls back to
configure when the UART is already up, and returns
a BringUpResult. On return the UART is up and usable.
The contract is identical across the four protocols that have it and is
explained once under Bring-up is bring_up(); check
settings_applied when the baud rate is something your test depends on:
result = uart.bring_up(baudrate=UartBaudRate.BAUD_9600)
if not result.settings_applied:
raise result.refusal
Every bring-up argument is keyword-only. baudrate is required; parity,
data_size, stop_bit and hw_handshake default to 8-N-1 with handshaking
disabled. Baud rates come from the UartBaudRate ladder
(600 through 115200) rather than a free integer.
Warning
hw_handshake=UartHwHandshake.ENABLED is believed to have no effect on a
Supernova Rev B. Firmware’s UART setup branches on the hardware revision and
the Rev B branch does not wire RTS/CTS up; the call still returns successfully,
so a script asking for flow control on such a board gets none. The RTS/CTS pins
stay muxed to their alternate function, so they look configured on a scope
while the peripheral ignores them. The reading is from firmware source alone —
no bench run has confirmed it, and confirming it needs a peer that actually
asserts RTS/CTS plus enough traffic to force flow control.
This SDK does not refuse the request — refusing one that might work is the
worse error (see Refusals fail open) — but it does
say so, in three places. dev.capabilities.uart_hw_handshake_effective is
False on such a unit, so you can branch on it before asking.
initialize and
configure log a warning, which is all a call
returning None can do. And bring_up reports it:
settings_applied is False with the reason in refusal, even on a fresh
bring-up where nothing was refused on the wire. It is the one line parameter
whose answer is known from the capability table before the call, so a field
documented as the one to consult must not affirm it. Rev C Supernovas and
Pulsars honour the request and report settings_applied=True as usual.
send_only_mode is bring-up only — but bring_up does take it.
send_only_mode=True arms no reception: no RX queue, no dispatcher
subscription. It is a host-side setting with no firmware parameter behind it,
which is why configure has no such argument — and
why bring_up can still honour it on either path,
including the fallback onto a live UART: it selects the RX path in both
directions, so on a UART whose reception an earlier run armed, True releases
the queue and subscription again rather than flipping a flag behind machinery
that keeps running. A caller parked in receive/query when that happens is
woken with DisconnectError, exactly as
deinit wakes it. The choice is never what
settings_applied is reporting about. See
examples/blocking_api/uart/send_only.py.
Underneath, initialize is not idempotent: a
re-run on an already-up UART raises FirmwareError with
FW_INTERFACE_ALREADY_INITIALIZED, and the UART stays initialised across
reconnects. configure re-configures a live
interface; deinit tears the peripheral down without
closing the device — UART is the only protocol here that offers one, which is
one reason bring_up reports rather than promising to reach any given
settings. examples/helpers.py’s uart_bring_up() delegates to
bring_up and adds the examples’ console lines.
The rail is the shared multifunction VTARG — the same supply as I2C, SPI and
GPIO. get_bus_voltage reads it back, and
AsyncUart mirrors it.
Note
set_voltage takes voltage_mv keyword-only,
where the I2C, SPI and GPIO setters accept it positionally. So
uart.set_voltage(3300) is a TypeError while spi.set_voltage(3300) is
fine, and uart.set_voltage(voltage_mv=3300) is the form to use. This is an
inconsistency in this SDK rather than anything upstream imposes; the
keyword-only form works on all four, so write it that way everywhere and the
question does not arise.
Three reception patterns — pick one per interface
This is the part that bites. The RX path is a single internal queue shared
by receive and query, and the three patterns below do not mix.
queryRequest/response. Drains the queue, sends, waits for a reply. Not concurrency-safe — overlapping calls discard each other’s data and can return each other’s replies. Use it when the exchange is strictly one-at-a-time.
examples/blocking_api/uart/query.py.receivePull. Blocks for the next message, with an optional
timeout. Safe to call concurrently with competing-consumer semantics: each message goes to exactly one waiter, non-deterministically — UART has no request/response correlation, so there is nothing to correlate on.examples/blocking_api/uart/receive_stream.py.
Device.subscribe with
UartRxNotification
: Push. Register a callback once and the dispatcher invokes it — on its own
thread — for every message received. The natural style for telemetry or
reacting to each line a device emits. examples/blocking_api/uart/subscribe.py.
Mixing query with receive is unsound. Pick one pattern per interface.
On the async side, iter_receive is a fourth
option with no blocking equivalent: an async iterator that installs its own
subscription and queue, so iterating does not compete with receive or query
for messages.
async with uart.iter_receive() as stream:
async for message in stream:
print(message)
send takes bytes.
DeviceCapabilities.max_uart_message_bytes reports the advisory message cap (1024).
Errors
UartError is much narrower than its name suggests.
The 0x04xx band maps to it, and upstream declares exactly one code in that
band: FW_UART_IN_VCOM_PORT_MODE (0x0401), reported by uartInit alone, and
only when the port is held by the USB CDC.
Everything else lands outside the band:
Failure |
Code |
Exception |
|---|---|---|
Port held by USB CDC |
|
|
Rejected configuration |
|
|
Interface not initialised |
|
|
Rejected voltage |
|
Catch FirmwareError to cover every firmware-reported
UART failure. A receive/query that runs out of time raises
CosmicTimeoutError, which also satisfies
except TimeoutError:.
Examples
examples/blocking_api/uart/query.py(request/response),send_receive.py(separate send and receive calls — a console session),receive_stream.py(pull loop),subscribe.py(push path),send_only.py(send_only_mode=Trueplus explicit teardown).examples/async_api/uart/query.py,send_receive.py, andreceive_stream.py— the last built oniter_receive().