ESP32-S3 Baremetal Support

uart_echo.c

UART: a loopback self-test, then an echo server.

make uart_echo flash monitor
Shows
Serial TX/RX, FIFO, baud rate
Needs
One jumper wire
make uart_echo flash monitor

Wiring - one jumper wire, nothing else:

GPIO5 (TX) ---- GPIO6 (RX)

With that wire in place the port is talking to itself, which is enough to prove the baud rate, the pin routing and both FIFOs without a second device on the bench. Take the wire out and connect a real device to GPIO5/GPIO6 instead: the self-test then fails (nothing answers), and the echo loop sends back whatever the device sends, which is a useful way to watch a chatty module like a GPS.

Results appear on the USB console, not on the UART, so the two are never confused with each other. Watch them with `make monitor`.

Note the two different serial ports in play here. console_print() goes out of the USB-C socket to your PC; uart_print() goes out of a pin to whatever is wired to it. They have nothing to do with each other.

The self-test repeats rather than running once at startup, and that is not cosmetic. Resetting this board tears the USB console down and re-enumerates it, which takes a few hundred milliseconds; anything printed before the host comes back is written into a FIFO nobody is draining and dropped. A one-shot banner is therefore never seen - `make monitor` always attaches long after it has been thrown away, and the screen stays blank forever. Repeating it means that whenever you start watching, the next report is a couple of seconds away. It stops once real bytes start arriving, so echoed traffic is not interleaved with a banner you have already read.

Source

examples/uart_echo.c
examples/uart_echo.c
140 lines
/*
 * UART: a loopback self-test, then an echo server.
 *
 *   make uart_echo flash monitor
 *
 * Wiring - one jumper wire, nothing else:
 *
 *   GPIO5 (TX) ---- GPIO6 (RX)
 *
 * With that wire in place the port is talking to itself, which is enough to
 * prove the baud rate, the pin routing and both FIFOs without a second device
 * on the bench. Take the wire out and connect a real device to GPIO5/GPIO6
 * instead: the self-test then fails (nothing answers), and the echo loop
 * sends back whatever the device sends, which is a useful way to watch a
 * chatty module like a GPS.
 *
 * Results appear on the USB console, not on the UART, so the two are never
 * confused with each other. Watch them with `make monitor`.
 *
 * Note the two different serial ports in play here. console_print() goes out
 * of the USB-C socket to your PC; uart_print() goes out of a pin to whatever
 * is wired to it. They have nothing to do with each other.
 *
 * The self-test repeats rather than running once at startup, and that is not
 * cosmetic. Resetting this board tears the USB console down and re-enumerates
 * it, which takes a few hundred milliseconds; anything printed before the
 * host comes back is written into a FIFO nobody is draining and dropped. A
 * one-shot banner is therefore never seen - `make monitor` always attaches
 * long after it has been thrown away, and the screen stays blank forever.
 * Repeating it means that whenever you start watching, the next report is a
 * couple of seconds away. It stops once real bytes start arriving, so echoed
 * traffic is not interleaved with a banner you have already read.
 */

#include "esp32s3.h"
#include "board_pins.h"

#define UART_PORT   1
#define PIN_TX      PIN_GPIO5
#define PIN_RX      PIN_GPIO6
#define BAUD        115200

#define REPORT_MS   2000

static const char probe[] = "loopback";

// Sends a known string and checks it comes back byte for byte.
static int loopback_ok(void)
{
    char got[sizeof probe];
    uint32_t want = sizeof probe - 1;      // the string without its terminator

    // Start from an empty receiver: on a repeat run the previous attempt may
    // have left a partial reply behind, and that would fail the count below
    // for the wrong reason.
    while (uart_read_byte(UART_PORT) >= 0) { }

    uart_print(UART_PORT, probe);
    uart_drain(UART_PORT);                 // wait for the last bit to leave TX

    // The bytes are already on their way back through the wire, but the
    // receiver still has to shift the last one in. At 115200 baud eight bytes
    // take about 0.7 ms, so this is a generous margin.
    delay_ms(10);

    if (uart_rx_available(UART_PORT) != want) {
        return 0;
    }

    uart_read(UART_PORT, got, want);
    for (uint32_t i = 0; i < want; i++) {
        if (got[i] != probe[i]) {
            return 0;
        }
    }
    return 1;
}

static void report(void)
{
    console_print("uart port ");
    console_print_u32(UART_PORT);
    console_print(" on tx=");
    console_print_u32(PIN_TX);
    console_print(" rx=");
    console_print_u32(PIN_RX);
    console_print(" at ");
    console_print_u32(BAUD);
    console_print(" baud\r\n");

    if (loopback_ok()) {
        console_print("loopback ok - tx and rx agree\r\n");
    } else {
        console_print("loopback failed - no jumper, or a device is attached\r\n");
    }

    console_print("echoing anything that arrives on rx\r\n");
}

void _start(void)
{
    board_init();

    uart_init(UART_PORT, PIN_TX, PIN_RX, BAUD);

    // cycle_count() rather than a delay: the echo loop below has to keep
    // coming round, so the report cannot be scheduled by blocking on one.
    uint32_t period = cpu_mhz() * 1000u * REPORT_MS;
    uint32_t last   = cycle_count() - period;   // report on the first pass
    int      quiet  = 1;                        // nothing has arrived yet

    // Nothing is buffered in RAM behind our back, so this loop has to come
    // round often enough to keep the 128-byte hardware FIFO from filling. At
    // this baud rate that is roughly every 11 ms.
    for (;;) {
        // Empty the FIFO rather than taking one byte per pass: a burst can
        // arrive faster than a pass that also prints can consume it.
        int c;
        while ((c = uart_read_byte(UART_PORT)) >= 0) {
            quiet = 0;
            uart_write_byte(UART_PORT, (uint8_t)c);

            console_print("rx 0x");
            console_print_hex((uint32_t)c, 2);
            if (c >= 0x20 && c < 0x7F) {
                console_print(" '");
                console_print_char((char)c);
                console_print("'");
            }
            console_print("\r\n");
        }

        // Unsigned subtraction, so the 32-bit cycle counter wrapping - every
        // 27 seconds at 160 MHz - costs at most one late report.
        if (quiet && cycle_count() - last >= period) {
            last = cycle_count();
            report();
        }
    }
}