BERGSONNE

UART

A classic asynchronous serial port — the simplest way to talk to a sensor, a host terminal, or another MCU. core_serial gives you blocking printf-style TX and buffered RX over any USART; the hal_uart and ll_uart layers sit underneath. Use the Core / HAL / LL toggle at the top of the sidebar to switch.

Overview

A UART is identified by its USART instance (USART1, USART2, …). You give it a baud rate; everything is 8 data bits, no parity, 1 stop bit (8N1). The peripheral clock is resolved for you, so init is just the instance and a baud rate.

core_serial is handle-based (Tier 1): you hold a hal_uart_t and pass it to each call. There’s also a small Tier 2 *_bus surface (print / putc by config-declared bus id) for Studio.

Transmit

Initialize once, then write strings, bytes, or formatted output. All TX is blocking — the call returns once the bytes are on the wire:

#include "core.h"

  hal_uart_t uart;
  hal_uart_config_t cfg = { .baud = 115200, .rx_interrupt = 0 };
  core_serial_init(&uart, USART2, &cfg);

  core_serial_print(&uart, "boot\r\n");
  core_serial_printf(&uart, "adc=%u mv=%lu\r\n", raw, mv);

Receive

RX has two modes, chosen at init. With rx_interrupt = 0 you poll; with rx_interrupt = 1 the driver fills a background ring buffer you drain at your own pace:

hal_uart_config_t cfg = { .baud = 115200, .rx_interrupt = 1 };
  core_serial_init(&uart, USART2, &cfg);

  // later, in your loop:
  while (core_serial_available(&uart)) {
      uint8_t b = core_serial_getc(&uart);   // blocking; pair with available()
      handle(b);
  }
No Tier 2 read yet
The *_bus Studio surface is TX-only — there’s no default-instance read / available yet (a known coverage gap). Use the handle calls above for RX.

DMA transmit

Not implemented yet
hal_uart_tx_dma exists in the API but returns HAL_ERROR on every Core and never calls its callback: UART DMA needs a DMA channel assignment from coregen and per-family DMA setup that haven’t been written. Transmit is blocking today (core_serial_write / hal_uart_tx); for background receive, use the interrupt-driven ring buffer above.

Cross-architecture support

Polling TX/RX is the verified baseline; interrupt-driven RX and LPUART are further along on some Cores than others. The same core_serial contract holds across the family:

●L0M0+3/5●L4M43/5●W5M332/4●H5M333/5WCH (RISC-V) · Nordic (nRF54) — in development

See the implementation status for the full matrix.

Known gaps

What the SDK itself lists as missing from serial, straight from the header:

mediumDSLNo Tier 2 read surface

Tier 2 wraps the write side (print_bus, putc_bus) only. getc_bus, read_bus, available_bus need a Twin input affordance to be meaningful — without one the read functions would just return -1/0 always, defeating the point. Adding a "serial input" pane (analog of the GPIO toggle / ADC slider) closes both ends.

mediumDSLNo coregen auto-init for serial

Tier 2 wrappers assume the user has called core_serial_init on the matching handle before any send. Coregen doesn't yet read a `serial: { 1: { baud: 115200 } }` block out of config.json and emit the init in core_init(). Until it does, the bus has to be primed by hand even when using Tier 2.

highC APINo interrupt / DMA driven path

All calls block. SDK roadmap Tier 1 item: "UART IRQ + coregen" for non-blocking serial (GPS, RS-485). Long reads currently stall the main loop until the buffer fills or times out.

mediumC APINo flow control / parity / 9-bit modes

The thin wrappers expose only the basics — RTS/CTS, parity, stop bits, 9-bit data, and inversion are reachable only via the hal_uart_config_t struct passed to init.

mediumC APINo LPUART (low-power UART)

SDK roadmap Tier 2: LPUART works in Stop mode for wake-on-serial. Compile-only on every Core today; not surfaced through this header.

From the @studio unsupported notes in core_serial.h — tiles@6af026f.

API reference

Default-instance · Tier 2
int core_serial_print_bus(uint8_t bus, const char * str);
Write a null-terminated string to `bus` (blocking). Returns 0 on success or -1 on undeclared bus. The most-common DSL pattern — "drop a debug line on UART2" — is one line: serial.print(2, "ready\\n");
int core_serial_putc_bus(uint8_t bus, uint8_t byte);
Write a single byte to `bus` (blocking). Returns 0 on success or -1 on undeclared bus.
Lower-level · Tier 1
hal_status_t core_serial_init(hal_uart_t * h, USART_TypeDef * instance, const hal_uart_config_t * cfg);
Initialize a UART instance. Clock is auto-resolved from PCLK1_HZ (core_config.h).
hal_status_t core_serial_init_clk(hal_uart_t * h, USART_TypeDef * instance, uint32_t pclk_hz, const hal_uart_config_t * cfg);
void core_serial_write(hal_uart_t * h, const uint8_t * data, uint32_t len);
Transmit a buffer (blocking).
void core_serial_print(hal_uart_t * h, const char * str);
Transmit a null-terminated string (blocking).
void core_serial_putc(hal_uart_t * h, uint8_t byte);
Transmit a single byte (blocking).
uint16_t core_serial_available(hal_uart_t * h);
Number of bytes available in the RX buffer.
uint8_t core_serial_getc(hal_uart_t * h);
Receive a single byte (blocking -- waits for data).
uint16_t core_serial_read(hal_uart_t * h, uint8_t * buf, uint16_t max_len);
Read available bytes from the RX ring buffer (non-blocking). Returns number of bytes read.
hal_uart_t * core_serial_handle_for_bus(uint8_t bus);
The UART handle for a bus id declared in config.json, or NULL if the project doesn't declare that bus. Emitted per project by coregen into core_init.c (when the project declares any USART); the *_bus helpers below resolve their handle through it. Forward-declared here rather than `#include "core_init.h"` so this header compiles without a project (val tests, examples without config.json). The natives-side caller is gated on CORE_HAS_SERIAL_BUSES, so the linker never asks for the symbol unless the dispatcher exists.

Generated from core_serial.h — tiles@f70bca2.