From 0d00a1ba01fd5a774e4da65970a88c535585ebb2 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 9 Jan 2017 22:50:42 +0800 Subject: [PATCH] vfs: implement reading from UART --- components/esp32/Kconfig | 5 ++ components/vfs/README.rst | 32 +++++-- components/vfs/test/component.mk | 1 + components/vfs/test/test_vfs_uart.c | 124 ++++++++++++++++++++++++++++ components/vfs/vfs_uart.c | 54 +++++++++++- 5 files changed, 210 insertions(+), 6 deletions(-) create mode 100644 components/vfs/test/component.mk create mode 100644 components/vfs/test/test_vfs_uart.c diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index 5a36b30a5..0afe5ba0f 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -134,6 +134,11 @@ config NEWLIB_STDOUT_ADDCR 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. + + With this option enabled, C standard library functions which read from UART + (like scanf) will convert "\r\n" character sequences back to "\n". + + This option doesn't affect behavior of the UART driver (drivers/uart.h). 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 c58161c90..2ad7aa7ec 100644 --- a/components/vfs/README.rst +++ b/components/vfs/README.rst @@ -23,11 +23,6 @@ To register an FS driver, application needs to define in instance of esp_vfs_t s .fstat = &myfs_fstat, .close = &myfs_close, .read = &myfs_read, - .lseek = NULL, - .stat = NULL, - .link = NULL, - .unlink = NULL, - .rename = NULL }; ESP_ERROR_CHECK(esp_vfs_register("/data", &myfs, NULL)); @@ -125,4 +120,31 @@ When VFS component receives a call from newlib which has a file descriptor, this +-------------+ +Standard IO streams (stdin, stdout, stderr) +------------------------------------------- + +If "UART for console output" menuconfig option is not set to "None", then ``stdin``, ``stdout``, and ``stderr`` are configured to read from, and write to, a UART. It is possible to use UART0 or UART1 for standard IO. By default, UART0 is used, with 115200 baud rate, TX pin is GPIO1 and RX pin is GPIO3. These parameters can be changed in menuconfig. + +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. + +Standard streams and FreeRTOS tasks +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``FILE`` objects for ``stdin``, ``stdout``, and ``stderr`` are shared between all FreeRTOS tasks, but the pointers to these objects are are stored in per-task ``struct _reent``. The following code:: + + fprintf(stderr, "42\n"); + +actually is translated to to this (by the preprocessor): + + fprintf(__getreent()->_stderr, "42\n"); + +where the ``__getreent()`` function returns a per-task pointer to ``struct _reent`` (`source `_). This structure is allocated on the TCB of each task. When a task is initialized, ``_stdin``, ``_stdout`` and ``_stderr`` members of ``struct _reent`` are set to the values of ``_stdin``, ``_stdout`` and ``_stderr`` of ``_GLOBAL_REENT`` (i.e. the structure which is used before FreeRTOS is started). + +Such a design has the following consequences: + +- It is possible to set ``stdin``, ``stdout``, and ``stderr`` for any given task without affecting other tasks, e.g. by doing ``stdin = fopen("/dev/uart/1", "r")``. +- Closing default ``stdin``, ``stdout``, or ``stderr`` using ``fclose`` will close the ``FILE`` stream object — this will affect all other tasks. +- To change the default ``stdin``, ``stdout``, ``stderr`` streams for new tasks, modify ``_GLOBAL_REENT->_stdin`` (``_stdout``, ``_stderr``) before creating the task. diff --git a/components/vfs/test/component.mk b/components/vfs/test/component.mk new file mode 100644 index 000000000..ce464a212 --- /dev/null +++ b/components/vfs/test/component.mk @@ -0,0 +1 @@ +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/vfs/test/test_vfs_uart.c b/components/vfs/test/test_vfs_uart.c new file mode 100644 index 000000000..892ca527c --- /dev/null +++ b/components/vfs/test/test_vfs_uart.c @@ -0,0 +1,124 @@ +// Copyright 2015-2016 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. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include "unity.h" +#include "rom/uart.h" +#include "soc/uart_struct.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdkconfig.h" + +static void fwrite_str_loopback(const char* str, size_t size) +{ + uart_tx_wait_idle(CONFIG_CONSOLE_UART_NUM); + UART0.conf0.loopback = 1; + fwrite(str, 1, size, stdout); + fflush(stdout); + uart_tx_wait_idle(CONFIG_CONSOLE_UART_NUM); + vTaskDelay(2 / portTICK_PERIOD_MS); + UART0.conf0.loopback = 0; +} + +static void flush_stdin_stdout() +{ + vTaskDelay(10 / portTICK_PERIOD_MS); + char *bitbucket = (char*) 0x3f000000; + while (fread(bitbucket, 1, 128, stdin) > 0) { + ; + } + fflush(stdout); + uart_tx_wait_idle(CONFIG_CONSOLE_UART_NUM); +} + +TEST_CASE("can read from stdin", "[vfs]") +{ + flush_stdin_stdout(); + + const size_t count = 12; + srand(count); + char* data = (char*) calloc(1, count * 8 + 1); + char* buf = (char*) calloc(1, count * 8 + 1); + char* p = data; + for (size_t i = 0; i < count; ++i) { + p += sprintf(p, "%08x", rand()); + } + p += sprintf(p, "\n"); + size_t len = p - data; + fwrite_str_loopback(data, len); + size_t cb = fread(buf, 1, len, stdin); + TEST_ASSERT_EQUAL(len, cb); + TEST_ASSERT_EQUAL_UINT8_ARRAY(data, buf, len); +} + + +#if CONFIG_NEWLIB_STDOUT_ADDCR + +TEST_CASE("CRs are removed from the stdin correctly", "[vfs]") +{ + flush_stdin_stdout(); + const char* send_str = "1234567890\n\r123\r\n4\n"; + /* with CONFIG_NEWLIB_STDOUT_ADDCR, the following will be sent on the wire. + * (last character of each part is marked with a hat) + * + * 1234567890\r\n\r123\r\r\n4\r\n + * ^ ^^ ^ + */ + char buf[128]; + char* dst = buf; + + fwrite_str_loopback(send_str, 11); // send up to the first \n + size_t rb = fread(dst, 1, 5, stdin); // read first 5 + TEST_ASSERT_EQUAL(5, rb); + dst += rb; + + rb = fread(dst, 1, 6, stdin); // ask for 6 + TEST_ASSERT_EQUAL(6, rb); // get 6 + + TEST_ASSERT_EQUAL_UINT8_ARRAY("1234567890\n", buf, 11); + dst += rb; + + rb = fread(dst, 1, 2, stdin); // any more characters? + TEST_ASSERT_EQUAL(0, rb); // nothing + + fwrite_str_loopback(send_str + 11, 1); // send the '\r' + vTaskDelay(10 / portTICK_PERIOD_MS); + + rb = fread(dst, 1, 2, stdin); // try to get somthing + TEST_ASSERT_EQUAL(0, rb); // still nothing (\r is buffered) + + fwrite_str_loopback(send_str + 12, 1); // Now send the '1' + vTaskDelay(10 / portTICK_PERIOD_MS); + rb = fread(dst, 1, 2, stdin); // try again + TEST_ASSERT_EQUAL(2, rb); // get two characters + TEST_ASSERT_EQUAL_UINT8_ARRAY("\r1", dst, 2); + dst += rb; + + fwrite_str_loopback(send_str + 13, 6); // Send the rest + vTaskDelay(10 / portTICK_PERIOD_MS); + + rb = fread(dst, 1, 4, stdin); // consume "23\r\n" + TEST_ASSERT_EQUAL(4, rb); + TEST_ASSERT_EQUAL_UINT8_ARRAY("23\r\n", dst, 4); + dst += rb; + + rb = fread(dst, 1, 4, stdin); // ask for more than the remainder + TEST_ASSERT_EQUAL(2, rb); + TEST_ASSERT_EQUAL_UINT8_ARRAY("4\n", dst, 2); +} + +#endif //CONFIG_NEWLIB_STDOUT_ADDCR + diff --git a/components/vfs/vfs_uart.c b/components/vfs/vfs_uart.c index d9d755f9b..545a5474f 100644 --- a/components/vfs/vfs_uart.c +++ b/components/vfs/vfs_uart.c @@ -70,6 +70,58 @@ static size_t IRAM_ATTR uart_write(int fd, const void * data, size_t size) return size; } +static ssize_t IRAM_ATTR 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]; + 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; + break; + } + } + } + if (c == '\r') { + buffered_char = c; + continue; + } +#endif //CONFIG_NEWLIB_STDOUT_ADDCR + data_c[received] = c; + ++received; + } + _lock_release_recursive(&s_uart_locks[fd]); + if (received > 0) { + return received; + } + errno = EWOULDBLOCK; + return -1; +} + static int IRAM_ATTR uart_fstat(int fd, struct stat * st) { assert(fd >=0 && fd < 3); @@ -92,7 +144,7 @@ void esp_vfs_dev_uart_register() .open = &uart_open, .fstat = &uart_fstat, .close = &uart_close, - .read = NULL, // TODO: implement reading from UART + .read = &uart_read, .lseek = NULL, .stat = NULL, .link = NULL,