Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

UART

Hardware revision note: UART requires hw-rev2 firmware. On v1 hardware, UART endpoints return UartError::Unsupported.

Pico de Gallo provides UART support through the RP2350’s hardware UART0 peripheral. The TX pin is on GPIO 0 and RX is on GPIO 1. The UART is buffered and interrupt-driven, so reads and writes do not block the firmware’s main loop.

Operations

OperationDescription
ReadReads up to N bytes from the receive buffer with an optional timeout
WriteWrites raw bytes to the transmit buffer
FlushFlushes the transmit buffer, blocking until all bytes are sent
Set ConfigReplaces the baud rate and framing configuration
Get ConfigReturns the current UART configuration

Framing Configuration

The firmware initializes UART0 to 115200 8N1: 115200 bits per second, eight data bits, no parity, and one stop bit. At runtime, set-config can change the baud rate and every supported framing parameter.

The RP2350 UART0 capability envelope is:

ParameterValuesResult
Data bits5, 6, 7, 8Supported
Data bits9Not available
ParityNone, odd, evenSupported
ParityMark, spaceSupported as stick parity
Stop bits1, 2Supported
Stop bits0.5, 1.5Not available
Flow controlNoneSupported
Flow controlRTS/CTSNot available
Flow controlDTR/DSRNot available
Flow controlRS-485Not available

The mark parity setting always means a parity bit of 1; the space parity setting always means 0. These modes are not exposed by embassy’s Parity enum, so the firmware configures the PL011 framing register directly.

Warning

Reconfiguration is not atomic at the UART pins. The device applies the baud divisor first and the framing second, and drains neither direction. There is a window in which the new divisor is active with the old framing. Quiesce transmit and receive traffic while changing the configuration.

Warning

set-config replaces the complete configuration. In the CLI, omitting a framing flag selects its 8N1 power-on value and overwrites the previous setting. A baud-only change must therefore repeat all framing flags.

For example, configure 9600 baud with seven data bits, even parity, and two stop bits:

gallo uart set-config --baud-rate 9600 \
    --data-bits 7 --parity even --stop-bits 2
gallo uart get-config

get-config reports the last successfully requested configuration. This is a software shadow, not a UART register read-back, so the reported baud rate does not reflect divisor rounding.

Loopback Example

The simplest way to verify UART operation is a loopback test: connect GPIO 0 (TX) directly to GPIO 1 (RX) with a jumper wire. Everything you write will be received back.

CLI

# 1. Check the current configuration
gallo uart get-config

# 2. Set the complete configuration to 115200 8N1 (default)
gallo uart set-config --baud-rate 115200 \
    --data-bits 8 --parity none --stop-bits 1

# 3. Write "Hello" (ASCII bytes)
gallo uart write --bytes 0x48 0x65 0x6C 0x6C 0x6F

# 4. Read back 5 bytes with a 100ms timeout
gallo uart read --count 5 --timeout 100

# 5. Flush the transmit buffer
gallo uart flush

Rust Library

#![allow(unused)]
fn main() {
use pico_de_gallo_lib::{
    PicoDeGallo, UartDataBits, UartParity, UartStopBits,
};

async fn uart_loopback(gallo: &PicoDeGallo) {
    // Configure 115200 8N1
    gallo
        .uart_set_config(
            115_200,
            UartDataBits::Eight,
            UartParity::None,
            UartStopBits::One,
        )
        .await
        .unwrap();

    // Verify configuration
    let config = gallo.uart_get_config().await.unwrap();
    println!("Baud rate: {}", config.baud_rate);

    // Write "Hello"
    gallo.uart_write(&[0x48, 0x65, 0x6C, 0x6C, 0x6F]).await.unwrap();

    // Flush to ensure all bytes are transmitted
    gallo.uart_flush().await.unwrap();

    // Read back with 100ms timeout
    let data = gallo.uart_read(5, 100).await.unwrap();
    assert_eq!(&data, &[0x48, 0x65, 0x6C, 0x6C, 0x6F]);
    println!("Received: {:?}", String::from_utf8_lossy(&data));
}
}

C (FFI)

#include "pico_de_gallo.h"
#include <stdio.h>
#include <string.h>

void uart_loopback(const PicoDeGallo *gallo) {
    /* Configure 115200 8N1 */
    Status rc = gallo_uart_set_config(
        gallo,
        115200,
        GalloUartDataBits_Eight,
        GalloUartParity_None,
        GalloUartStopBits_One);
    if (rc != Ok) {
        fprintf(stderr, "set-config failed: %d\n", rc);
        return;
    }

    /* Read back current config */
    uint32_t baud_rate;
    uint8_t data_bits;
    uint8_t parity;
    uint8_t stop_bits;
    rc = gallo_uart_get_config(
        gallo, &baud_rate, &data_bits, &parity, &stop_bits);
    if (rc != Ok) {
        fprintf(stderr, "get-config failed: %d\n", rc);
        return;
    }
    printf("Baud rate: %u\n", baud_rate);

    /* Write "Hello" */
    uint8_t tx[] = {0x48, 0x65, 0x6C, 0x6C, 0x6F};
    rc = gallo_uart_write(gallo, tx, sizeof(tx));
    if (rc != Ok) {
        fprintf(stderr, "write failed: %d\n", rc);
        return;
    }

    /* Flush */
    gallo_uart_flush(gallo);

    /* Read back */
    uint8_t rx[5];
    uint16_t out_read;
    rc = gallo_uart_read(gallo, rx, sizeof(rx), 100, &out_read);
    if (rc != Ok) {
        fprintf(stderr, "read failed: %d\n", rc);
        return;
    }

    printf("Received %u bytes: %.*s\n", out_read, out_read, rx);
}

HAL

The HAL layer implements the standard embedded_io and embedded_io_async traits, so the UART can be used with any driver that accepts generic readers or writers.

The embedded-io-06 Cargo feature remains enabled by default, while embedded-io-07 can be enabled additively. See Feature Flags for the details. The snippets below are identical either way.

Blockingembedded_io::Read + embedded_io::Write:

#![allow(unused)]
fn main() {
use embedded_io::{Read, Write};
use pico_de_gallo_hal::Hal;

fn uart_loopback_blocking(hal: &Hal) {
    let mut uart = hal.uart();

    // Write "Hello"
    uart.write_all(&[0x48, 0x65, 0x6C, 0x6C, 0x6F]).unwrap();
    uart.flush().unwrap();

    // Read back
    let mut buf = [0u8; 5];
    uart.read_exact(&mut buf).unwrap();
    assert_eq!(&buf, b"Hello");
}
}

Asyncembedded_io_async::Read + embedded_io_async::Write:

#![allow(unused)]
fn main() {
use embedded_io_async::{Read, Write};
use pico_de_gallo_hal::Hal;

async fn uart_loopback_async(hal: &Hal) {
    let mut uart = hal.uart();

    uart.write_all(&[0x48, 0x65, 0x6C, 0x6C, 0x6F]).await.unwrap();
    uart.flush().await.unwrap();

    let mut buf = [0u8; 5];
    uart.read_exact(&mut buf).await.unwrap();
    assert_eq!(&buf, b"Hello");
}
}

Connecting an External Device

To communicate with an external UART device (e.g., a GPS module or microcontroller), connect:

Pico de Gallo          External Device
──────────────         ───────────────
GPIO 0 (TX) ────────── RX
GPIO 1 (RX) ────────── TX
GND ────────────────── GND

Note

Cross the TX/RX lines: the transmit pin of one device connects to the receive pin of the other.

Non-blocking Read

A timeout of 0 performs a single non-blocking poll and returns whatever bytes are already in the receive buffer (possibly none). This is a deliberate exception to GPIO wait semantics. Non-zero values above the firmware’s 30-minute ceiling are clamped to that ceiling:

# Non-blocking: return whatever is buffered right now
gallo uart read --count 64 --timeout 0
#![allow(unused)]
fn main() {
use pico_de_gallo_lib::PicoDeGallo;

async fn drain_buffer(gallo: &PicoDeGallo) -> Vec<u8> {
    // timeout_ms = 0 → one non-blocking poll
    gallo.uart_read(64, 0).await.unwrap()
}
}

Error Handling

UART direction determines the payload ceiling. uart_read may return at most MAX_RESPONSE_PAYLOAD (1014 bytes), while uart_write may send at most MAX_TRANSFER_SIZE (4096 bytes). Every host surface checks this before transmitting and reports BufferTooLong for an over-ceiling call.

UART operations return PicoDeGalloError<UartError> on failure. The UartError variants cover both protocol-level and configuration errors:

VariantDescription
BufferTooLongRead exceeds the response ceiling or write exceeds the transfer ceiling
OverrunReceive buffer overflowed before host read the data
BreakBreak condition detected on the line
ParityParity check failed
FramingInvalid stop bit detected
InvalidBaudRateRequested baud rate is out of range or unsupported
OtherCatch-all for unexpected firmware errors
UnsupportedUART is not available on this hardware revision

API Reference

Lib Methods

All methods are async and available on PicoDeGallo:

MethodSignature
uart_readuart_read(count: u16, timeout_ms: u32) -> Result<Vec<u8>, PicoDeGalloError<UartError>>
uart_writeuart_write(contents: &[u8]) -> Result<(), PicoDeGalloError<UartError>>
uart_flushuart_flush() -> Result<(), PicoDeGalloError<UartError>>
uart_set_configuart_set_config(baud_rate: u32, data_bits: UartDataBits, parity: UartParity, stop_bits: UartStopBits) -> Result<(), PicoDeGalloError<UartError>>
uart_get_configuart_get_config() -> Result<UartConfigurationInfo, PicoDeGalloError<UartError>>

Note

PicoDeGallo::new() is not async. Only the peripheral methods listed above are async.

FFI Functions

All FFI functions return a Status code:

Status gallo_uart_read(const PicoDeGallo *gallo,
                       uint8_t *buf, uint16_t buf_len,
                       uint32_t timeout_ms, uint16_t *out_read);

Status gallo_uart_write(const PicoDeGallo *gallo,
                        const uint8_t *buf, uint16_t len);

Status gallo_uart_flush(const PicoDeGallo *gallo);

Status gallo_uart_set_config(const PicoDeGallo *gallo,
                             uint32_t baud_rate,
                             uint8_t data_bits,
                             uint8_t parity,
                             uint8_t stop_bits);

Status gallo_uart_get_config(const PicoDeGallo *gallo,
                             uint32_t *out_baud_rate,
                             uint8_t *out_data_bits,
                             uint8_t *out_parity,
                             uint8_t *out_stop_bits);

CLI Commands

gallo uart read       --count <N> --timeout <MS>
gallo uart write      --bytes <BYTE>...
gallo uart flush
gallo uart set-config --baud-rate <BAUD_RATE> [--data-bits <5|6|7|8>] \
                      [--parity <none|odd|even|mark|space>] \
                      [--stop-bits <1|2>]
gallo uart get-config
gallo uart help

Pin Mapping

FunctionGPIORP2350 Peripheral
TX0UART0 TX
RX1UART0 RX