console.c
Text output over USB-Serial-JTAG.
The board's single USB-C port is the chip's built-in USB-Serial-JTAG peripheral, which the ROM has already enumerated - so printing costs two registers, no driver, no UART and no pin. Output appears on the same COM port you flash over.
Writes are dropped rather than blocked when no host is reading the port, so an unattended board keeps running at full speed.
API
Declared in console.hFunctions
void console_print_char(char c);Prints a single character to the USB console.
- c
- the character to print
- returns
- nothing
void console_print(const char *text);Prints a null-terminated string to the USB console.
- text
- the string to print
- returns
- nothing
void console_print_u32(uint32_t value);Prints an unsigned number in decimal to the USB console.
- value
- the number to print (0 to 4294967295)
- returns
- nothing
void console_print_hex(uint32_t value, uint32_t digits);Prints a number in hexadecimal, zero-padded, without a "0x" prefix. Register values and I2C addresses are far easier to read this way.
- value
- the number to print
- digits
- how many hex digits to pad to, 1 to 8 (2 for a byte)
- returns
- nothing
int console_read_byte(void);Reads one byte typed by the host on the USB console, if one is waiting. Never blocks: the host-to-device FIFO is polled once and the call returns straight away, so a loop can keep drawing while it waits for input.
- returns
- the byte, 0 to 255, or -1 when nothing has arrived
Source
esp32s3/src/console.c/*
* Text output over the chip's built-in USB-Serial-JTAG peripheral.
*/
#include "console.h"
#include "regs.h"
// How many polls to spend waiting for FIFO space before giving up. Counted in
// loop passes, not time, so this costs nothing when a host is connected.
#define CONSOLE_FIFO_SPIN_LIMIT 200000u
void console_print_char(char c)
{
// With no host reading the port the FIFO never drains, so give up rather
// than stall the caller forever.
for (uint32_t spin = 0; spin < CONSOLE_FIFO_SPIN_LIMIT; spin++) {
if (ESP32S3_REG(USB_SERIAL_JTAG_EP1_CONF_REG) & USB_SERIAL_JTAG_IN_EP_DATA_FREE) {
ESP32S3_REG(USB_SERIAL_JTAG_EP1_REG) = (uint8_t)c;
ESP32S3_REG(USB_SERIAL_JTAG_EP1_CONF_REG) = USB_SERIAL_JTAG_WR_DONE; // ship it
return;
}
}
}
void console_print(const char *text)
{
while (*text) {
console_print_char(*text++);
}
}
void console_print_u32(uint32_t value)
{
char digits[10]; // 4294967295 is the longest a uint32_t gets
int count = 0;
do {
digits[count++] = (char)('0' + value % 10);
value /= 10;
} while (value);
while (count--) {
console_print_char(digits[count]);
}
}
void console_print_hex(uint32_t value, uint32_t digits)
{
if (digits < 1) {
digits = 1;
} else if (digits > 8) {
digits = 8;
}
// Most significant nibble first, so the shift counts down.
while (digits--) {
uint32_t nibble = (value >> (4 * digits)) & 0xFu;
console_print_char((char)(nibble < 10 ? '0' + nibble : 'A' + nibble - 10));
}
}
int console_read_byte(void)
{
// The OUT endpoint is the host-to-device direction: whatever was typed
// into the terminal on the other end of the USB cable. One flag says
// whether the FIFO holds anything, and reading the data register pops a
// byte off it. Draining the last byte is also what lets the peripheral
// accept the host's next packet, so there is nothing to acknowledge by
// hand - keep reading until this returns -1 and the flow control looks
// after itself.
if (!(ESP32S3_REG(USB_SERIAL_JTAG_EP1_CONF_REG) & USB_SERIAL_JTAG_OUT_EP_DATA_AVAIL)) {
return -1;
}
return (int)(ESP32S3_REG(USB_SERIAL_JTAG_EP1_REG) & 0xFFu);
}esp32s3/include/console.h— the header, in full
/*
* Text output over USB-Serial-JTAG.
*
* The board's single USB-C port is the chip's built-in USB-Serial-JTAG
* peripheral, which the ROM has already enumerated - so printing costs two
* registers, no driver, no UART and no pin. Output appears on the same COM
* port you flash over.
*
* Writes are dropped rather than blocked when no host is reading the port, so
* an unattended board keeps running at full speed.
*/
#ifndef CONSOLE_H
#define CONSOLE_H
#include <stdint.h>
// Prints a single character to the USB console.
// - c: the character to print
// returns: nothing
void console_print_char(char c);
// Prints a null-terminated string to the USB console.
// - text: the string to print
// returns: nothing
void console_print(const char *text);
// Prints an unsigned number in decimal to the USB console.
// - value: the number to print (0 to 4294967295)
// returns: nothing
void console_print_u32(uint32_t value);
// Prints a number in hexadecimal, zero-padded, without a "0x" prefix.
// Register values and I2C addresses are far easier to read this way.
// - value: the number to print
// - digits: how many hex digits to pad to, 1 to 8 (2 for a byte)
// returns: nothing
void console_print_hex(uint32_t value, uint32_t digits);
// Reads one byte typed by the host on the USB console, if one is waiting.
// Never blocks: the host-to-device FIFO is polled once and the call returns
// straight away, so a loop can keep drawing while it waits for input.
// returns: the byte, 0 to 255, or -1 when nothing has arrived
int console_read_byte(void);
#endif // ESP32S3_CONSOLE_H