mirror of
				https://github.com/espressif/esp-idf.git
				synced 2025-10-31 15:11:40 +01:00 
			
		
		
		
	
		
			
	
	
		
			152 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
		
		
			
		
	
	
			152 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
	
	
|   | 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 :doc:`VFS drivers <../api-reference/storage/vfs>`.
 | ||
|  | 
 | ||
|  | On {IDF_TARGET_NAME}, ESP-IDF provides implementations of VFS drivers for I/O over:
 | ||
|  | 
 | ||
|  | .. list::
 | ||
|  | 
 | ||
|  |     - UART
 | ||
|  |     :SOC_USB_SERIAL_JTAG_SUPPORTED: - USB Serial/JTAG
 | ||
|  |     :esp32s2 or esp32s3: - USB CDC (using USB_OTG peripheral)
 | ||
|  |     - "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:
 | ||
|  | 
 | ||
|  | .. list::
 | ||
|  | 
 | ||
|  |     - :ref:`CONFIG_ESP_CONSOLE_UART_DEFAULT<CONFIG_ESP_CONSOLE_UART_DEFAULT>` — Enables UART with default options (pin numbers, baud rate) for standard I/O.
 | ||
|  |     - :ref:`CONFIG_ESP_CONSOLE_UART_CUSTOM<CONFIG_ESP_CONSOLE_UART_CUSTOM>` — Enables UART for standard I/O, with TX/RX pin numbers and baud rate configurable via Kconfig.
 | ||
|  |     :esp32s2 or esp32s3: - :ref:`CONFIG_ESP_CONSOLE_USB_CDC<CONFIG_ESP_CONSOLE_USB_CDC>` — Enables USB CDC (using USB_OTG peripheral) for standard I/O. See :doc:`usb-otg-console` for details about hardware connections required.
 | ||
|  |     :SOC_USB_SERIAL_JTAG_SUPPORTED: - :ref:`CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG<CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG>` — Enables USB Serial/JTAG for standard I/O. See :doc:`usb-serial-jtag-console` for details about hardware connections required.
 | ||
|  |     - :ref:`CONFIG_ESP_CONSOLE_NONE<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 option 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``.
 | ||
|  | 
 | ||
|  | .. only:: SOC_USB_SERIAL_JTAG_SUPPORTED
 | ||
|  | 
 | ||
|  |     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:
 | ||
|  | 
 | ||
|  |         - :ref:`CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG<CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG>`
 | ||
|  | 
 | ||
|  | Standard Streams and FreeRTOS Tasks
 | ||
|  | -----------------------------------
 | ||
|  | 
 | ||
|  | 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.
 | ||
|  | 
 | ||
|  | Each stream (``stdin``, ``stdout``, ``stderr``) has a mutex associated with it. This mutex is used 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 will ensure that the outputs from each task are not mixed together.
 | ||
|  | 
 | ||
|  | 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 :cpp:func:`uart_vfs_dev_use_driver` function. It is also possible to revert to the basic non-blocking functions using a call to :cpp:func:`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.
 | ||
|  | 
 | ||
|  | .. only:: SOC_USB_SERIAL_JTAG_SUPPORTED
 | ||
|  | 
 | ||
|  |     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 USB Serial/JTAG driver using a call to the :cpp:func:`usb_serial_jtag_vfs_use_nonblocking` function. Note that the USB Serial/JTAG driver has to be initialized using :cpp:func:`usb_serial_jtag_driver_install` beforehand. It is also possible to revert to the basic non-blocking functions using a call to :cpp:func:`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.
 | ||
|  | 
 | ||
|  | .. only:: esp32s2 or esp32s3
 | ||
|  | 
 | ||
|  |     USB CDC (using USB_OTG peripheral)
 | ||
|  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | ||
|  | 
 | ||
|  |     USB CDC VFS driver provides blocking I/O behavior by default. It is possible to enable 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:
 | ||
|  | 
 | ||
|  |     - :ref:`CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF<CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF>`, :ref:`CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR<CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR>`, :ref:`CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF<CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF>` - for output
 | ||
|  |     - :ref:`CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF<CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF>`, :ref:`CONFIG_NEWLIB_STDIN_LINE_ENDING_CR<CONFIG_NEWLIB_STDIN_LINE_ENDING_CR>`, :ref:`CONFIG_NEWLIB_STDIN_LINE_ENDING_LF<CONFIG_NEWLIB_STDIN_LINE_ENDING_LF>` - for input
 | ||
|  | 
 | ||
|  | 
 | ||
|  | It is also possible to configure line ending conversion for the specific VFS driver:
 | ||
|  | 
 | ||
|  | .. list::
 | ||
|  | 
 | ||
|  |     - For UART: :cpp:func:`uart_vfs_dev_port_set_rx_line_endings` and :cpp:func:`uart_vfs_dev_port_set_tx_line_endings`
 | ||
|  |     :SOC_USB_SERIAL_JTAG_SUPPORTED: - For USB Serial/JTAG: :cpp:func:`usb_serial_jtag_vfs_set_rx_line_endings` and :cpp:func:`usb_serial_jtag_vfs_set_tx_line_endings`
 | ||
|  |     :esp32s2 or esp32s3: - For USB CDC (using USB_OTG peripheral): :cpp:func:`esp_vfs_dev_cdcacm_set_rx_line_endings` and :cpp:func:`esp_vfs_dev_cdcacm_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``:
 | ||
|  | 
 | ||
|  | .. code-block:: c
 | ||
|  | 
 | ||
|  |     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 :doc:`VFS documentation <../api-reference/storage/vfs>` 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()`` to register it with VFS. Then, use ``fopen()`` to redirect ``stdout`` and ``stderr`` to the custom channel. For example:
 | ||
|  | 
 | ||
|  | .. code-block:: c
 | ||
|  | 
 | ||
|  |     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.
 |