vfs: implement reading from UART

This commit is contained in:
Ivan Grokhotkov 2017-01-09 22:50:42 +08:00
parent 7f751fd0fe
commit 0d00a1ba01
5 changed files with 210 additions and 6 deletions

View file

@ -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"

View file

@ -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 <https://github.com/espressif/esp-idf/blob/master/components/newlib/include/sys/reent.h#L370-L417>`_). 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.

View file

@ -0,0 +1 @@
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive

View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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

View file

@ -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,