diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index c5d410f76..3a7ce72f1 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -179,20 +179,53 @@ config IPC_TASK_STACK_SIZE It can be shrunk if you are sure that you do not use any custom IPC functionality. -config NEWLIB_STDOUT_ADDCR - bool "Standard-out output adds carriage return before newline" - default y +choice NEWLIB_STDOUT_LINE_ENDING + prompt "Line ending for UART output" + default NEWLIB_STDOUT_LINE_ENDING_CRLF help - Most people are used to end their printf strings with a newline. If this - is sent as is to the serial port, most terminal programs will only move the - cursor one line down, not also move it to the beginning of the line. This - is usually done by an added CR character. Enabling this will make the - standard output code automatically add a CR character before a LF. + This option allows configuring the desired line endings sent to UART + when a newline ('\n', LF) appears on stdout. + Three options are possible: - With this option enabled, C standard library functions which read from UART - (like scanf) will convert "\r\n" character sequences back to "\n". + CRLF: whenever LF is encountered, prepend it with CR + + LF: no modification is applied, stdout is sent as is + + CR: each occurence of LF is replaced with CR This option doesn't affect behavior of the UART driver (drivers/uart.h). + +config NEWLIB_STDOUT_LINE_ENDING_CRLF + bool "CRLF" +config NEWLIB_STDOUT_LINE_ENDING_LF + bool "LF" +config NEWLIB_STDOUT_LINE_ENDING_CR + bool "CR" +endchoice + +choice NEWLIB_STDIN_LINE_ENDING + prompt "Line ending for UART input" + default NEWLIB_STDIN_LINE_ENDING_CR + help + This option allows configuring which input sequence on UART produces + a newline ('\n', LF) on stdin. + Three options are possible: + + CRLF: CRLF is converted to LF + + LF: no modification is applied, input is sent to stdin as is + + CR: each occurence of CR is replaced with LF + + This option doesn't affect behavior of the UART driver (drivers/uart.h). + +config NEWLIB_STDIN_LINE_ENDING_CRLF + bool "CRLF" +config NEWLIB_STDIN_LINE_ENDING_LF + bool "LF" +config NEWLIB_STDIN_LINE_ENDING_CR + bool "CR" +endchoice config NEWLIB_NANO_FORMAT bool "Enable 'nano' formatting options for printf/scanf family" diff --git a/components/vfs/README.rst b/components/vfs/README.rst index cc47e2c84..943705539 100644 --- a/components/vfs/README.rst +++ b/components/vfs/README.rst @@ -140,7 +140,13 @@ If "UART for console output" menuconfig option is not set to "None", then ``stdi Writing to ``stdout`` or ``stderr`` will send characters to the UART transmit FIFO. Reading from ``stdin`` will retrieve characters from the UART receive FIFO. -Note that while writing to ``stdout`` or ``stderr`` will block until all characters are put into the FIFO, reading from ``stdin`` is non-blocking. The function which reads from UART will get all the characters present in the FIFO (if any), and return. I.e. doing ``fscanf("%d\n", &var);`` may not have desired results. This is a temporary limitation which will be removed once ``fcntl`` is added to the VFS interface. +By default, VFS uses simple 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. Because of this non-blocking read behavior, higher level C library calls, such as ``fscanf("%d\n", &var);`` may not have desired results. + +Applications which use UART driver may instruct VFS to use the driver's interrupt driven, blocking read and write functions instead. This can be done using a call to ``esp_vfs_dev_uart_use_driver`` function. It is also possible to revert to the basic non-blocking functions using a call to ``esp_vfs_dev_uart_use_nonblocking``. + +VFS also provides optional newline conversion feature for input and output. Internally, most applications send and receive lines terminated by LF (''\n'') character. Different terminal programs may require different line termination, such as CR or CRLF. Applications can configure this separately for input and output either via menuconfig, or by calls to ``esp_vfs_dev_uart_set_rx_line_endings`` and ``esp_vfs_dev_uart_set_tx_line_endings`` functions. + + Standard streams and FreeRTOS tasks ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/components/vfs/include/esp_vfs_dev.h b/components/vfs/include/esp_vfs_dev.h index bb2579ee0..b51527fcf 100644 --- a/components/vfs/include/esp_vfs_dev.h +++ b/components/vfs/include/esp_vfs_dev.h @@ -1,4 +1,4 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,6 +17,15 @@ #include "esp_vfs.h" +/** + * @brief Line ending settings + */ +typedef enum { + ESP_LINE_ENDINGS_CRLF,//!< CR + LF + ESP_LINE_ENDINGS_CR, //!< CR + ESP_LINE_ENDINGS_LF, //!< LF +} esp_line_endings_t; + /** * @brief add /dev/uart virtual filesystem driver * @@ -24,5 +33,52 @@ */ void esp_vfs_dev_uart_register(); +/** + * @brief Set the line endings expected to be received on UART + * + * This specifies the conversion between line endings received on UART and + * newlines ('\n', LF) passed into stdin: + * + * - ESP_LINE_ENDINGS_CRLF: convert CRLF to LF + * - ESP_LINE_ENDINGS_CR: convert CR to LF + * - ESP_LINE_ENDINGS_LF: no modification + * + * @note this function is not thread safe w.r.t. reading from UART + * + * @param mode line endings expected on UART + */ +void esp_vfs_dev_uart_set_rx_line_endings(esp_line_endings_t mode); + +/** + * @brief Set the line endings to sent to UART + * + * This specifies the conversion between newlines ('\n', LF) on stdout and line + * endings sent over UART: + * + * - ESP_LINE_ENDINGS_CRLF: convert LF to CRLF + * - ESP_LINE_ENDINGS_CR: convert LF to CR + * - ESP_LINE_ENDINGS_LF: no modification + * + * @note this function is not thread safe w.r.t. writing to UART + * + * @param mode line endings to send to UART + */ +void esp_vfs_dev_uart_set_tx_line_endings(esp_line_endings_t mode); + +/** + * @brief set VFS to use simple functions for reading and writing UART + * Read is non-blocking, write is busy waiting until TX FIFO has enough space. + * These functions are used by default. + * @param uart_num UART peripheral number + */ +void esp_vfs_dev_uart_use_nonblocking(int uart_num); + +/** + * @brief set VFS to use UART driver for reading and writing + * @note application must configure UART driver before calling these functions + * With these functions, read and write are blocking and interrupt-driven. + * @param uart_num UART peripheral number + */ +void esp_vfs_dev_uart_use_driver(int uart_num); #endif //__ESP_VFS_DEV_H__ diff --git a/components/vfs/vfs_uart.c b/components/vfs/vfs_uart.c index 4b3748764..a03f59ac8 100644 --- a/components/vfs/vfs_uart.c +++ b/components/vfs/vfs_uart.c @@ -1,4 +1,4 @@ -// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,17 +13,74 @@ // limitations under the License. #include +#include #include "esp_vfs.h" +#include "esp_vfs_dev.h" #include "esp_attr.h" #include "sys/errno.h" #include "sys/lock.h" #include "soc/uart_struct.h" +#include "driver/uart.h" #include "sdkconfig.h" -static uart_dev_t* s_uarts[3] = {&UART0, &UART1, &UART2}; -static _lock_t s_uart_locks[3]; // per-UART locks, lazily initialized +// TODO: make the number of UARTs chip dependent +#define UART_NUM 3 -static int IRAM_ATTR uart_open(const char * path, int flags, int mode) +// Token signifying that no character is available +#define NONE -1 + +// UART write bytes function type +typedef void (*tx_func_t)(int, int); +// UART read bytes function type +typedef int (*rx_func_t)(int); + +// Basic functions for sending and receiving bytes over UART +static void uart_tx_char(int fd, int c); +static int uart_rx_char(int fd); + +// Functions for sending and receiving bytes which use UART driver +static void uart_tx_char_via_driver(int fd, int c); +static int uart_rx_char_via_driver(int fd); + +// Pointers to UART peripherals +static uart_dev_t* s_uarts[UART_NUM] = {&UART0, &UART1, &UART2}; +// per-UART locks, lazily initialized +static _lock_t s_uart_locks[UART_NUM]; +// One-character buffer used for newline conversion code, per UART +static int s_peek_char[UART_NUM] = { NONE, NONE, NONE }; + +// Newline conversion mode when transmitting +static esp_line_endings_t s_tx_mode = +#if CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF + ESP_LINE_ENDINGS_CRLF; +#elif CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR + ESP_LINE_ENDINGS_CR; +#else + ESP_LINE_ENDINGS_LF; +#endif + +// Newline conversion mode when receiving +static esp_line_endings_t s_rx_mode = +#if CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF + ESP_LINE_ENDINGS_CRLF; +#elif CONFIG_NEWLIB_STDIN_LINE_ENDING_CR + ESP_LINE_ENDINGS_CR; +#else + ESP_LINE_ENDINGS_LF; +#endif + +// Functions used to write bytes to UART. Default to "basic" functions. +static tx_func_t s_uart_tx_func[UART_NUM] = { + &uart_tx_char, &uart_tx_char, &uart_tx_char +}; + +// Functions used to read bytes from UART. Default to "basic" functions. +static rx_func_t s_uart_rx_func[UART_NUM] = { + &uart_rx_char, &uart_rx_char, &uart_rx_char +}; + + +static int uart_open(const char * path, int flags, int mode) { // this is fairly primitive, we should check if file is opened read only, // and error out if write is requested @@ -38,81 +95,120 @@ static int IRAM_ATTR uart_open(const char * path, int flags, int mode) return -1; } -static void IRAM_ATTR uart_tx_char(uart_dev_t* uart, int c) +static void uart_tx_char(int fd, int c) { + uart_dev_t* uart = s_uarts[fd]; while (uart->status.txfifo_cnt >= 127) { ; } uart->fifo.rw_byte = c; } +static void uart_tx_char_via_driver(int fd, int c) +{ + char ch = (char) c; + uart_write_bytes(fd, &ch, 1); +} -static ssize_t IRAM_ATTR uart_write(int fd, const void * data, size_t size) +static int uart_rx_char(int fd) +{ + uart_dev_t* uart = s_uarts[fd]; + if (uart->status.rxfifo_cnt == 0) { + return NONE; + } + return uart->fifo.rw_byte; +} + +static int uart_rx_char_via_driver(int fd) +{ + uint8_t c; + int n = uart_read_bytes(fd, &c, 1, portMAX_DELAY); + if (n == 0) { + return NONE; + } + return c; +} + +static ssize_t uart_write(int fd, const void * data, size_t size) { assert(fd >=0 && fd < 3); const char *data_c = (const char *)data; - uart_dev_t* uart = s_uarts[fd]; - /* - * Even though newlib does stream locking on each individual stream, we need + /* Even though newlib does stream locking on each individual stream, we need * a dedicated UART lock if two streams (stdout and stderr) point to the * same UART. */ _lock_acquire_recursive(&s_uart_locks[fd]); for (size_t i = 0; i < size; i++) { -#if CONFIG_NEWLIB_STDOUT_ADDCR - if (data_c[i]=='\n') { - uart_tx_char(uart, '\r'); + int c = data_c[i]; + if (c == '\n' && s_tx_mode != ESP_LINE_ENDINGS_LF) { + s_uart_tx_func[fd](fd, '\r'); + if (s_tx_mode == ESP_LINE_ENDINGS_CR) { + continue; + } } -#endif - uart_tx_char(uart, data_c[i]); + s_uart_tx_func[fd](fd, c); } _lock_release_recursive(&s_uart_locks[fd]); return size; } -static ssize_t IRAM_ATTR uart_read(int fd, void* data, size_t size) +/* Helper function which returns a previous character or reads a new one from + * UART. Previous character can be returned ("pushed back") using + * uart_return_char function. + */ +static int uart_read_char(int fd) +{ + /* return character from peek buffer, if it is there */ + if (s_peek_char[fd] != NONE) { + int c = s_peek_char[fd]; + s_peek_char[fd] = NONE; + return c; + } + return s_uart_rx_func[fd](fd); +} + +/* Push back a character; it will be returned by next call to uart_read_char */ +static void uart_return_char(int fd, int c) +{ + assert(s_peek_char[fd] == NONE); + s_peek_char[fd] = c; +} + +static ssize_t uart_read(int fd, void* data, size_t size) { assert(fd >=0 && fd < 3); - uint8_t *data_c = (uint8_t *) data; - uart_dev_t* uart = s_uarts[fd]; + char *data_c = (char *) data; size_t received = 0; _lock_acquire_recursive(&s_uart_locks[fd]); - while (uart->status.rxfifo_cnt > 0 && received < size) { - uint8_t c = uart->fifo.rw_byte; -#if CONFIG_NEWLIB_STDOUT_ADDCR - /* Convert \r\n sequences to \n. - * If \r is received, it is put into 'buffered_char' until the next - * character is received. Then depending on the character, we either - * drop \r (if the next one is \n) or output \r and then proceed to output - * the new character. - */ - const int NONE = -1; - static int buffered_char = NONE; - if (buffered_char != NONE) { - if (buffered_char == '\r' && c == '\n') { - buffered_char = NONE; - } else { - data_c[received] = buffered_char; - buffered_char = NONE; - ++received; - if (received == size) { - /* We have placed the buffered character into the output buffer - * but there won't be enough space for the newly received one. - * Keep the new character in buffered_char until read is called - * again. - */ - buffered_char = c; + while (received < size) { + int c = uart_read_char(fd); + if (c == '\r') { + if (s_rx_mode == ESP_LINE_ENDINGS_CR) { + c = '\n'; + } else if (s_rx_mode == ESP_LINE_ENDINGS_CRLF) { + /* look ahead */ + int c2 = uart_read_char(fd); + if (c2 == NONE) { + /* could not look ahead, put the current character back */ + uart_return_char(fd, c); break; } + if (c2 == '\n') { + /* this was \r\n sequence. discard \r, return \n */ + c = '\n'; + } else { + /* \r followed by something else. put the second char back, + * it will be processed on next iteration. return \r now. + */ + uart_return_char(fd, c2); + } } } - if (c == '\r') { - buffered_char = c; - continue; - } -#endif //CONFIG_NEWLIB_STDOUT_ADDCR - data_c[received] = c; + data_c[received] = (char) c; ++received; + if (c == '\n') { + break; + } } _lock_release_recursive(&s_uart_locks[fd]); if (received > 0) { @@ -122,14 +218,14 @@ static ssize_t IRAM_ATTR uart_read(int fd, void* data, size_t size) return -1; } -static int IRAM_ATTR uart_fstat(int fd, struct stat * st) +static int uart_fstat(int fd, struct stat * st) { assert(fd >=0 && fd < 3); st->st_mode = S_IFCHR; return 0; } -static int IRAM_ATTR uart_close(int fd) +static int uart_close(int fd) { assert(fd >=0 && fd < 3); return 0; @@ -153,3 +249,29 @@ void esp_vfs_dev_uart_register() }; ESP_ERROR_CHECK(esp_vfs_register("/dev/uart", &vfs, NULL)); } + +void esp_vfs_dev_uart_set_rx_line_endings(esp_line_endings_t mode) +{ + s_rx_mode = mode; +} + +void esp_vfs_dev_uart_set_tx_line_endings(esp_line_endings_t mode) +{ + s_tx_mode = mode; +} + +void esp_vfs_dev_uart_use_nonblocking(int fd) +{ + _lock_acquire_recursive(&s_uart_locks[fd]); + s_uart_tx_func[fd] = uart_tx_char; + s_uart_rx_func[fd] = uart_rx_char; + _lock_release_recursive(&s_uart_locks[fd]); +} + +void esp_vfs_dev_uart_use_driver(int fd) +{ + _lock_acquire_recursive(&s_uart_locks[fd]); + s_uart_tx_func[fd] = uart_tx_char_via_driver; + s_uart_rx_func[fd] = uart_rx_char_via_driver; + _lock_release_recursive(&s_uart_locks[fd]); +}