# Standard I/O and Console Output

[中文]

ESP-IDF provides C standard I/O facilities, such as `stdin`, `stdout`, and `stderr` streams, as well as C standard library functions such as `printf()` which operate on these streams.

As common in POSIX systems, these streams are buffering wrappers around file descriptors:

- `stdin` is a buffered stream for reading input from the user, wrapping file descriptor `STDIN_FILENO` (0).

- `stdout` is a buffered stream for writing output to the user, wrapping `STDOUT_FILENO` (1).

- `stderr` is a buffered stream for writing error messages to the user, wrapping `STDERR_FILENO` (2).

In ESP-IDF, there is no practical distinction between `stdout` and `stderr`, as both streams are sent to the same physical interface. Most applications will use only `stdout`. For example, ESP-IDF logging functions always write to `stdout` regardless of the log level.

The underlying stdin, stdout, and stderr file descriptors are implemented based on VFS drivers.

On ESP32-C5, ESP-IDF provides implementations of VFS drivers for I/O over:

- UART

- USB Serial/JTAG

- "Null" (no output)

Standard I/O is not limited to these options, though. See below on enabling custom destinations for standard I/O.

## Configuration

Built-in implementations of standard I/O can be selected using several Kconfig options:

- CONFIG_ESP_CONSOLE_UART_DEFAULT — Enables UART with default options (pin numbers, baud rate) for standard I/O.

- CONFIG_ESP_CONSOLE_UART_CUSTOM — Enables UART for standard I/O, with TX/RX pin numbers and baud rate configurable via Kconfig.

- CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG — Enables USB Serial/JTAG for standard I/O. See USB Serial/JTAG Controller Console for details about hardware connections required.

- CONFIG_ESP_CONSOLE_NONE — Disables standard I/O. If this option is selected, `stdin`, `stdout`, and `stderr` will be mapped to `/dev/null` and won't produce any output or generate any input.

Enabling one of these options will cause the corresponding VFS driver to be built into the application and used to open `stdin`, `stdout`, and `stderr` streams. Data written to `stdout` and `stderr` will be sent over the selected interface, and input from the selected interface will be available on `stdin`.

### Secondary output

ESP-IDF has built-in support for sending standard output to a secondary destination. This option makes the application output visible on two interfaces at once, for example on both UART and USB Serial/JTAG.

Note that secondary console is output-only:

> - data written to `stdout` and `stderr` by the application will be sent to both primary and secondary consoles

> - `stdin` will only contain data sent by the host to the primary console.

The following secondary console options are available:

> - CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG

## Standard Streams and FreeRTOS Tasks

ESP-IDF provides two different implementations of standard I/O streams based on the selected LibC implementation defined by CONFIG_LIBC. The behavior of `stdin`, `stdout`, and `stderr` streams differs between these implementations, particularly regarding how they are shared across FreeRTOS tasks.

Common to both implementations, each stream (`stdin`, `stdout`, `stderr`) has a mutex associated with it to protect the stream from concurrent access by multiple tasks. For example, if two tasks are writing to `stdout` at the same time, the mutex ensures that the outputs from each task are not mixed together.

### Newlib

In ESP-IDF, to save RAM, `FILE` objects for `stdin`, `stdout`, and `stderr` are shared between all FreeRTOS tasks, but the pointers to these objects are unique for every task. This means that:

- It is possible to change `stdin`, `stdout`, and `stderr` for any given task without affecting other tasks, e.g., by doing `stdin = fopen("/dev/uart/1", "r")`.

- To change the default `stdin`, `stdout`, `stderr` streams for new tasks, modify `_GLOBAL_REENT->_stdin` (`_stdout`, `_stderr`) before creating the task.

- Closing default `stdin`, `stdout`, or `stderr` using `fclose` closes the `FILE` stream object, which will affect all other tasks.

### Picolibc

According to the POSIX standard, all default `stdin`, `stdout`, and `stderr` streams are global and shared between all FreeRTOS tasks. This means that:

- Modifying `stdin`, `stdout`, or `stderr` will affect all other tasks. It is not possible to change standard I/O streams for specific tasks.

- If a thread-local stream is needed, it should be implemented in the application code by opening a file stream and using it within tasks, e.g., `fscanf()`, `fprintf()`, etc.

## Blocking and non-blocking I/O

### UART

By default, UART VFS uses simplified functions for reading from and writing to UART. Writes busy-wait until all data is put into UART FIFO, and reads are non-blocking, returning only the data present in the FIFO. Due to this non-blocking read behavior, higher level C library calls, such as `fscanf("%d\n", &var);`, might not have desired results.

Applications which use the UART driver can instruct VFS to use the driver's interrupt driven, blocking read and write functions instead. This can be done using a call to the `uart_vfs_dev_use_driver()` function. It is also possible to revert to the basic non-blocking functions using a call to `uart_vfs_dev_use_nonblocking()`.

When the interrupt-driven driver is installed, it is also possible to enable/disable non-blocking behavior using `fcntl` function with `O_NONBLOCK` flag.

### USB Serial/JTAG

Similar to UART, the VFS driver for USB Serial/JTAG defaults to a simplified implementation: writes are blocking (busy-wait until all the data has been sent) and reads are non-blocking, returning only the data present in the FIFO. This behavior can be changed to use the interrupt-driven, blocking read and write functions of the USB Serial/JTAG driver by calling `usb_serial_jtag_vfs_use_nonblocking()`. Note that the USB Serial/JTAG driver has to be initialized using `usb_serial_jtag_driver_install()` beforehand. It is also possible to revert to the basic non-blocking functions using a call to `usb_serial_jtag_vfs_use_nonblocking()`.

When the interrupt-driven driver is installed, it is also possible to enable/disable non-blocking behavior using `fcntl` function with `O_NONBLOCK` flag.

## Newline conversion

VFS drivers provide an optional newline conversion feature for input and output. Internally, most applications send and receive lines terminated by the LF (`\n`) character. Different terminal programs may require different line termination, such as CR or CRLF.

Applications can configure this behavior globally using the following Kconfig options:

> - For output

>     - CONFIG_LIBC_STDOUT_LINE_ENDING_CRLF

>     - CONFIG_LIBC_STDOUT_LINE_ENDING_CR

>     - CONFIG_LIBC_STDOUT_LINE_ENDING_LF

> - For input

>     - CONFIG_LIBC_STDIN_LINE_ENDING_CRLF

>     - CONFIG_LIBC_STDIN_LINE_ENDING_CR

>     - CONFIG_LIBC_STDIN_LINE_ENDING_LF

It is also possible to configure line ending conversion for the specific VFS driver:

- For UART: `uart_vfs_dev_port_set_rx_line_endings()` and `uart_vfs_dev_port_set_tx_line_endings()`

- For USB Serial/JTAG: `usb_serial_jtag_vfs_set_rx_line_endings()` and `usb_serial_jtag_vfs_set_tx_line_endings()`

## Buffering

By default, standard I/O streams are line buffered. This means that data written to the stream is not sent to the underlying device until a newline character is written, or the buffer is full. This means, for example, that if you call `printf("Hello")`, the text will not be sent to the UART until you call `printf("\n")` or the stream buffer fills up due to other prints.

This behavior can be changed using the `setvbuf()` function. For example, to disable buffering for `stdout`:

    setvbuf(stdout, NULL, _IONBF, 0);

You can also use `setvbuf()` to increase the buffer size, or switch to fully buffered mode.

## Custom channels for standard I/O

To send application output to a custom channel (for example, a WebSocket connection), it is possible to create a custom VFS driver. See the VFS documentation for details. The VFS driver has to implement at least the following functions:

> - `open()` and `close()`

> - `write()`

> - `read()` — only if the custom channel is also used for input

> - `fstat()` — recommended, to provide correct buffering behavior for the I/O streams

> - `fcntl()` — only if non-blocking I/O has to be supported

Once you have created a custom VFS driver, use `esp_vfs_register_fs()` to register it with VFS. Then, use `fopen()` to redirect `stdout` and `stderr` to the custom channel. For example:

    FILE *f = fopen("/dev/mychannel", "w");
    if (f == NULL) {
        // handle the error here
    }
    stdout = f;
    stderr = f;

Note that logging functions (`ESP_LOGE()`, etc.) write their output to `stdout`. Keep this in mind when using logging within the implementation of your custom VFS (or any components which it calls). For example, if the custom VFS driver's `write()` operation fails and uses `ESP_LOGE()` to log the error, this will cause the output to be sent to `stdout`, which would again call the custom VFS driver's `write()` operation. This would result in an infinite loop. It is recommended to keep track of this re-entry condition in the VFS driver's `write()` implementation, and return immediately if the write operation is still in progress.

Was this page helpful?

- Thank you! We received your feedback.
- If you have any comments, fill in Espressif Documentation Feedback Form.

- We value your feedback.
- Let us know how we can improve this page by filling in Espressif Documentation Feedback Form.