mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
console: fix a bug preventing us from starting a CLI on non-default UART
It is now possible to start a REPL CLI on another UART than the default one. Closes https://github.com/espressif/esp-idf/issues/6897
This commit is contained in:
parent
0664c38e34
commit
b911951cad
@ -22,6 +22,7 @@
|
||||
static const char *TAG = "console.repl";
|
||||
|
||||
#define CONSOLE_PROMPT_MAX_LEN (32)
|
||||
#define CONSOLE_PATH_MAX_LEN (ESP_VFS_PATH_MAX)
|
||||
|
||||
typedef enum {
|
||||
CONSOLE_REPL_STATE_DEINIT,
|
||||
@ -40,11 +41,7 @@ typedef struct {
|
||||
typedef struct {
|
||||
esp_console_repl_com_t repl_com; // base class
|
||||
int uart_channel; // uart channel number
|
||||
} esp_console_repl_uart_t;
|
||||
|
||||
typedef struct {
|
||||
esp_console_repl_com_t repl_com; // base class
|
||||
} esp_console_repl_usb_cdc_t;
|
||||
} esp_console_repl_universal_t;
|
||||
|
||||
static void esp_console_repl_task(void *args);
|
||||
static esp_err_t esp_console_repl_uart_delete(esp_console_repl_t *repl);
|
||||
@ -56,21 +53,18 @@ static esp_err_t esp_console_setup_history(const char *history_path, uint32_t ma
|
||||
esp_err_t esp_console_new_repl_usb_cdc(const esp_console_dev_usb_cdc_config_t *dev_config, const esp_console_repl_config_t *repl_config, esp_console_repl_t **ret_repl)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
esp_console_repl_usb_cdc_t *cdc_repl = NULL;
|
||||
esp_console_repl_universal_t *cdc_repl = NULL;
|
||||
if (!repl_config | !dev_config | !ret_repl) {
|
||||
ret = ESP_ERR_INVALID_ARG;
|
||||
goto _exit;
|
||||
}
|
||||
// allocate memory for console REPL context
|
||||
cdc_repl = calloc(1, sizeof(esp_console_repl_usb_cdc_t));
|
||||
cdc_repl = calloc(1, sizeof(esp_console_repl_universal_t));
|
||||
if (!cdc_repl) {
|
||||
ret = ESP_ERR_NO_MEM;
|
||||
goto _exit;
|
||||
}
|
||||
|
||||
/* Disable buffering on stdin */
|
||||
setvbuf(stdin, NULL, _IONBF, 0);
|
||||
|
||||
/* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
|
||||
esp_vfs_dev_cdcacm_set_rx_line_endings(ESP_LINE_ENDINGS_CR);
|
||||
/* Move the caret to the beginning of the next line on '\n' */
|
||||
@ -95,15 +89,18 @@ esp_err_t esp_console_new_repl_usb_cdc(const esp_console_dev_usb_cdc_config_t *d
|
||||
// setup prompt
|
||||
esp_console_setup_prompt(repl_config->prompt, &cdc_repl->repl_com);
|
||||
|
||||
/* Fill the structure here as it will be used directly by the created task. */
|
||||
cdc_repl->uart_channel = CONFIG_ESP_CONSOLE_UART_NUM;
|
||||
cdc_repl->repl_com.state = CONSOLE_REPL_STATE_INIT;
|
||||
cdc_repl->repl_com.repl_core.del = esp_console_repl_usb_cdc_delete;
|
||||
|
||||
/* spawn a single thread to run REPL */
|
||||
if (xTaskCreate(esp_console_repl_task, "console_repl", repl_config->task_stack_size,
|
||||
&cdc_repl->repl_com, repl_config->task_priority, &cdc_repl->repl_com.task_hdl) != pdTRUE) {
|
||||
cdc_repl, repl_config->task_priority, &cdc_repl->repl_com.task_hdl) != pdTRUE) {
|
||||
ret = ESP_FAIL;
|
||||
goto _exit;
|
||||
}
|
||||
|
||||
cdc_repl->repl_com.state = CONSOLE_REPL_STATE_INIT;
|
||||
cdc_repl->repl_com.repl_core.del = esp_console_repl_usb_cdc_delete;
|
||||
*ret_repl = &cdc_repl->repl_com.repl_core;
|
||||
return ESP_OK;
|
||||
_exit:
|
||||
@ -120,13 +117,13 @@ _exit:
|
||||
esp_err_t esp_console_new_repl_uart(const esp_console_dev_uart_config_t *dev_config, const esp_console_repl_config_t *repl_config, esp_console_repl_t **ret_repl)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
esp_console_repl_uart_t *uart_repl = NULL;
|
||||
esp_console_repl_universal_t *uart_repl = NULL;
|
||||
if (!repl_config | !dev_config | !ret_repl) {
|
||||
ret = ESP_ERR_INVALID_ARG;
|
||||
goto _exit;
|
||||
}
|
||||
// allocate memory for console REPL context
|
||||
uart_repl = calloc(1, sizeof(esp_console_repl_uart_t));
|
||||
uart_repl = calloc(1, sizeof(esp_console_repl_universal_t));
|
||||
if (!uart_repl) {
|
||||
ret = ESP_ERR_NO_MEM;
|
||||
goto _exit;
|
||||
@ -136,9 +133,6 @@ esp_err_t esp_console_new_repl_uart(const esp_console_dev_uart_config_t *dev_con
|
||||
fflush(stdout);
|
||||
fsync(fileno(stdout));
|
||||
|
||||
/* Disable buffering on stdin */
|
||||
setvbuf(stdin, NULL, _IONBF, 0);
|
||||
|
||||
/* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
|
||||
esp_vfs_dev_uart_port_set_rx_line_endings(dev_config->channel, ESP_LINE_ENDINGS_CR);
|
||||
/* Move the caret to the beginning of the next line on '\n' */
|
||||
@ -186,16 +180,19 @@ esp_err_t esp_console_new_repl_uart(const esp_console_dev_uart_config_t *dev_con
|
||||
// setup prompt
|
||||
esp_console_setup_prompt(repl_config->prompt, &uart_repl->repl_com);
|
||||
|
||||
/* spawn a single thread to run REPL */
|
||||
/* Fill the structure here as it will be used directly by the created task. */
|
||||
uart_repl->uart_channel = dev_config->channel;
|
||||
uart_repl->repl_com.state = CONSOLE_REPL_STATE_INIT;
|
||||
uart_repl->repl_com.repl_core.del = esp_console_repl_uart_delete;
|
||||
|
||||
/* Spawn a single thread to run REPL, we need to pass `uart_repl` to it as
|
||||
* it also requires the uart channel. */
|
||||
if (xTaskCreate(esp_console_repl_task, "console_repl", repl_config->task_stack_size,
|
||||
&uart_repl->repl_com, repl_config->task_priority, &uart_repl->repl_com.task_hdl) != pdTRUE) {
|
||||
uart_repl, repl_config->task_priority, &uart_repl->repl_com.task_hdl) != pdTRUE) {
|
||||
ret = ESP_FAIL;
|
||||
goto _exit;
|
||||
}
|
||||
|
||||
uart_repl->uart_channel = dev_config->channel;
|
||||
uart_repl->repl_com.state = CONSOLE_REPL_STATE_INIT;
|
||||
uart_repl->repl_com.repl_core.del = esp_console_repl_uart_delete;
|
||||
*ret_repl = &uart_repl->repl_com.repl_core;
|
||||
return ESP_OK;
|
||||
_exit:
|
||||
@ -236,19 +233,10 @@ static esp_err_t esp_console_setup_prompt(const char *prompt, esp_console_repl_c
|
||||
}
|
||||
snprintf(repl_com->prompt, CONSOLE_PROMPT_MAX_LEN - 1, LOG_COLOR_I "%s " LOG_RESET_COLOR, prompt_temp);
|
||||
|
||||
printf("\r\n"
|
||||
"Type 'help' to get the list of commands.\r\n"
|
||||
"Use UP/DOWN arrows to navigate through command history.\r\n"
|
||||
"Press TAB when typing command name to auto-complete.\r\n");
|
||||
|
||||
/* Figure out if the terminal supports escape sequences */
|
||||
int probe_status = linenoiseProbe();
|
||||
if (probe_status) {
|
||||
/* zero indicates success */
|
||||
printf("\r\n"
|
||||
"Your terminal application does not support escape sequences.\n\n"
|
||||
"Line editing and history features are disabled.\n\n"
|
||||
"On Windows, try using Putty instead.\r\n");
|
||||
linenoiseSetDumbMode(1);
|
||||
#if CONFIG_LOG_COLORS
|
||||
/* Since the terminal doesn't support escape sequences,
|
||||
@ -317,7 +305,7 @@ static esp_err_t esp_console_repl_uart_delete(esp_console_repl_t *repl)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
esp_console_repl_com_t *repl_com = __containerof(repl, esp_console_repl_com_t, repl_core);
|
||||
esp_console_repl_uart_t *uart_repl = __containerof(repl_com, esp_console_repl_uart_t, repl_com);
|
||||
esp_console_repl_universal_t *uart_repl = __containerof(repl_com, esp_console_repl_universal_t, repl_com);
|
||||
// check if already de-initialized
|
||||
if (repl_com->state == CONSOLE_REPL_STATE_DEINIT) {
|
||||
ESP_LOGE(TAG, "already de-initialized");
|
||||
@ -337,7 +325,7 @@ static esp_err_t esp_console_repl_usb_cdc_delete(esp_console_repl_t *repl)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
esp_console_repl_com_t *repl_com = __containerof(repl, esp_console_repl_com_t, repl_core);
|
||||
esp_console_repl_usb_cdc_t *cdc_repl = __containerof(repl_com, esp_console_repl_usb_cdc_t, repl_com);
|
||||
esp_console_repl_universal_t *cdc_repl = __containerof(repl_com, esp_console_repl_universal_t, repl_com);
|
||||
// check if already de-initialized
|
||||
if (repl_com->state == CONSOLE_REPL_STATE_DEINIT) {
|
||||
ESP_LOGE(TAG, "already de-initialized");
|
||||
@ -353,9 +341,45 @@ _exit:
|
||||
|
||||
static void esp_console_repl_task(void *args)
|
||||
{
|
||||
esp_console_repl_com_t *repl_com = (esp_console_repl_com_t *)args;
|
||||
// waiting for task notify
|
||||
esp_console_repl_universal_t *repl_conf = (esp_console_repl_universal_t *) args;
|
||||
esp_console_repl_com_t *repl_com = &repl_conf->repl_com;
|
||||
const int uart_channel = repl_conf->uart_channel;
|
||||
|
||||
/* Waiting for task notify. This happens when `esp_console_start_repl()`
|
||||
* function is called. */
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
|
||||
/* Change standard input and output of the task if the requested UART is
|
||||
* NOT the default one. This block will replace stdin, stdout and stderr.
|
||||
*/
|
||||
if (uart_channel != CONFIG_ESP_CONSOLE_UART_NUM) {
|
||||
char path[CONSOLE_PATH_MAX_LEN] = { 0 };
|
||||
snprintf(path, CONSOLE_PATH_MAX_LEN, "/dev/uart/%d", uart_channel);
|
||||
|
||||
stdin = fopen(path, "r");
|
||||
stdout = fopen(path, "w");
|
||||
stderr = stdout;
|
||||
}
|
||||
|
||||
/* Disable buffering on stdin of the current task.
|
||||
* If the console is ran on a different UART than the default one,
|
||||
* buffering shall only be disabled for the current one. */
|
||||
setvbuf(stdin, NULL, _IONBF, 0);
|
||||
|
||||
/* This message shall be printed here and not earlier as the stdout
|
||||
* has just been set above. */
|
||||
printf("\r\n"
|
||||
"Type 'help' to get the list of commands.\r\n"
|
||||
"Use UP/DOWN arrows to navigate through command history.\r\n"
|
||||
"Press TAB when typing command name to auto-complete.\r\n");
|
||||
|
||||
if (linenoiseIsDumbMode()) {
|
||||
printf("\r\n"
|
||||
"Your terminal application does not support escape sequences.\n\n"
|
||||
"Line editing and history features are disabled.\n\n"
|
||||
"On Windows, try using Putty instead.\r\n");
|
||||
}
|
||||
|
||||
while (repl_com->state == CONSOLE_REPL_STATE_START) {
|
||||
char *line = linenoise(repl_com->prompt);
|
||||
if (line == NULL) {
|
||||
|
@ -115,10 +115,12 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include "linenoise.h"
|
||||
|
||||
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
|
||||
#define LINENOISE_MAX_LINE 4096
|
||||
#define LINENOISE_COMMAND_MAX_LEN 32
|
||||
|
||||
static linenoiseCompletionCallback *completionCallback = NULL;
|
||||
static linenoiseHintsCallback *hintsCallback = NULL;
|
||||
@ -203,6 +205,11 @@ void linenoiseSetDumbMode(int set) {
|
||||
dumbmode = set;
|
||||
}
|
||||
|
||||
/* Returns whether the current mode is dumbmode or not. */
|
||||
bool linenoiseIsDumbMode(void) {
|
||||
return dumbmode;
|
||||
}
|
||||
|
||||
static void flushWrite(void) {
|
||||
if (__fbufsize(stdout) > 0) {
|
||||
fflush(stdout);
|
||||
@ -214,47 +221,106 @@ static void flushWrite(void) {
|
||||
* and return it. On error -1 is returned, on success the position of the
|
||||
* cursor. */
|
||||
static int getCursorPosition(void) {
|
||||
char buf[32];
|
||||
int cols, rows;
|
||||
unsigned int i = 0;
|
||||
char buf[LINENOISE_COMMAND_MAX_LEN] = { 0 };
|
||||
int cols = 0;
|
||||
int rows = 0;
|
||||
int i = 0;
|
||||
const int out_fd = fileno(stdout);
|
||||
const int in_fd = fileno(stdin);
|
||||
/* The following ANSI escape sequence is used to get from the TTY the
|
||||
* cursor position. */
|
||||
const char get_cursor_cmd[] = "\x1b[6n";
|
||||
|
||||
/* Report cursor location */
|
||||
fprintf(stdout, "\x1b[6n");
|
||||
/* Send the command to the TTY on the other end of the UART.
|
||||
* Let's use unistd's write function. Thus, data sent through it are raw
|
||||
* reducing the overhead compared to using fputs, fprintf, etc... */
|
||||
write(out_fd, get_cursor_cmd, sizeof(get_cursor_cmd));
|
||||
|
||||
/* For USB CDC, it is required to flush the output. */
|
||||
flushWrite();
|
||||
/* Read the response: ESC [ rows ; cols R */
|
||||
|
||||
/* The other end will send its response which format is ESC [ rows ; cols R
|
||||
* We don't know exactly how many bytes we have to read, thus, perform a
|
||||
* read for each byte.
|
||||
* Stop right before the last character of the buffer, to be able to NULL
|
||||
* terminate it. */
|
||||
while (i < sizeof(buf)-1) {
|
||||
if (fread(buf+i, 1, 1, stdin) != 1) break;
|
||||
if (buf[i] == 'R') break;
|
||||
i++;
|
||||
/* Keep using unistd's functions. Here, using `read` instead of `fgets`
|
||||
* or `fgets` guarantees us that we we can read a byte regardless on
|
||||
* whether the sender sent end of line character(s) (CR, CRLF, LF). */
|
||||
if (read(in_fd, buf + i, 1) != 1 || buf[i] == 'R') {
|
||||
/* If we couldn't read a byte from STDIN or if 'R' was received,
|
||||
* the transmission is finished. */
|
||||
break;
|
||||
}
|
||||
|
||||
/* For some reasons, it is possible that we receive new line character
|
||||
* after querying the cursor position on some UART. Let's ignore them,
|
||||
* this will not affect the rest of the program. */
|
||||
if (buf[i] != '\n') {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/* NULL-terminate the buffer, this is required by `sscanf`. */
|
||||
buf[i] = '\0';
|
||||
/* Parse it. */
|
||||
if (buf[0] != ESC || buf[1] != '[') return -1;
|
||||
if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1;
|
||||
|
||||
/* Parse the received data to get the position of the cursor. */
|
||||
if (buf[0] != ESC || buf[1] != '[' || sscanf(buf+2,"%d;%d",&rows,&cols) != 2) {
|
||||
return -1;
|
||||
}
|
||||
return cols;
|
||||
}
|
||||
|
||||
/* Try to get the number of columns in the current terminal, or assume 80
|
||||
* if it fails. */
|
||||
static int getColumns(void) {
|
||||
int start, cols;
|
||||
int fd = fileno(stdout);
|
||||
int start = 0;
|
||||
int cols = 0;
|
||||
int written = 0;
|
||||
char seq[LINENOISE_COMMAND_MAX_LEN] = { 0 };
|
||||
const int fd = fileno(stdout);
|
||||
|
||||
/* The following ANSI escape sequence is used to tell the TTY to move
|
||||
* the cursor to the most-right position. */
|
||||
const char move_cursor_right[] = "\x1b[999C";
|
||||
const size_t cmd_len = sizeof(move_cursor_right);
|
||||
|
||||
/* This one is used to set the cursor position. */
|
||||
const char set_cursor_pos[] = "\x1b[%dD";
|
||||
|
||||
/* Get the initial position so we can restore it later. */
|
||||
start = getCursorPosition();
|
||||
if (start == -1) goto failed;
|
||||
if (start == -1) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
/* Go to right margin and get position. */
|
||||
if (fwrite("\x1b[999C", 1, 6, stdout) != 6) goto failed;
|
||||
/* Send the command to go to right margin. Use `write` function instead of
|
||||
* `fwrite` for the same reasons explained in `getCursorPosition()` */
|
||||
if (write(fd, move_cursor_right, cmd_len) != cmd_len) {
|
||||
goto failed;
|
||||
}
|
||||
flushWrite();
|
||||
cols = getCursorPosition();
|
||||
if (cols == -1) goto failed;
|
||||
|
||||
/* Restore position. */
|
||||
/* After sending this command, we can get the new position of the cursor,
|
||||
* we'd get the size, in columns, of the opened TTY. */
|
||||
cols = getCursorPosition();
|
||||
if (cols == -1) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
/* Restore the position of the cursor back. */
|
||||
if (cols > start) {
|
||||
char seq[32];
|
||||
snprintf(seq,32,"\x1b[%dD",cols-start);
|
||||
if (write(fd, seq, strlen(seq)) == -1) {
|
||||
/* Generate the move cursor command. */
|
||||
written = snprintf(seq, LINENOISE_COMMAND_MAX_LEN, set_cursor_pos, cols-start);
|
||||
|
||||
/* If `written` is equal or bigger than LINENOISE_COMMAND_MAX_LEN, it
|
||||
* means that the output has been truncated because the size provided
|
||||
* is too small. */
|
||||
assert (written < LINENOISE_COMMAND_MAX_LEN);
|
||||
|
||||
/* Send the command with `write`, which is not buffered. */
|
||||
if (write(fd, seq, written) == -1) {
|
||||
/* Can't recover... */
|
||||
}
|
||||
flushWrite();
|
||||
|
@ -69,6 +69,7 @@ void linenoiseHistoryFree(void);
|
||||
void linenoiseClearScreen(void);
|
||||
void linenoiseSetMultiLine(int ml);
|
||||
void linenoiseSetDumbMode(int set);
|
||||
bool linenoiseIsDumbMode(void);
|
||||
void linenoisePrintKeyCodes(void);
|
||||
void linenoiseAllowEmpty(bool);
|
||||
|
||||
|
6
examples/peripherals/uart/uart_repl/CMakeLists.txt
Normal file
6
examples/peripherals/uart/uart_repl/CMakeLists.txt
Normal file
@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(uart_repl)
|
8
examples/peripherals/uart/uart_repl/Makefile
Normal file
8
examples/peripherals/uart/uart_repl/Makefile
Normal file
@ -0,0 +1,8 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := uart_repl
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
61
examples/peripherals/uart/uart_repl/README.md
Normal file
61
examples/peripherals/uart/uart_repl/README.md
Normal file
@ -0,0 +1,61 @@
|
||||
# UART REPL Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example demonstrates how to use REPL console on a different UART than the default one.
|
||||
It also shows how to connect these two UART together, either for testing or for sending commands
|
||||
without any human interaction.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
The example can be run on any ESP board that have at least 2 UARTs. The development board shall be connected to a
|
||||
PC with a single USB cable for flashing and monitoring. If you are willing to monitor the console UART, you may use
|
||||
a 3.3V compatible USB-to-Serial dongle on its GPIO pin.
|
||||
|
||||
### Setup the Hardware
|
||||
|
||||
No external connection is needed in order to run the example. However, as stated before, if you are willing to see what
|
||||
is going on on the second UART (console UART), you can connect pins CONSOLE_UART_TX_PIN (5 by default) and
|
||||
CONSOLE_UART_RX_PIN (4 by default) to a Serial-to-USB adapter.
|
||||
|
||||
### Configure the project
|
||||
|
||||
The default values, located at the top of `main/uart_repl_example_main.c` can be changed such as:
|
||||
DEFAULT_UART_CHANNEL, CONSOLE_UART_CHANNEL, DEFAULT_UART_RX_PIN, DEFAULT_UART_TX_PIN, CONSOLE_UART_RX_PIN,
|
||||
CONSOLE_UART_TX_PIN, UARTS_BAUD_RATE, TASK_STACK_SIZE, and READ_BUF_SIZE.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view default UART's serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
The example will set up the default UART to use DEFAULT_UART_RX_PIN and DEFAULT_UART_TX_PIN. Then, it will set up
|
||||
the REPL console on the second UART. Finally, it will connect both UARTs together in order to let default UART
|
||||
be able to send commands and receive replies to and from the console UART.
|
||||
|
||||
Here is a diagram of what UARTs will look like:
|
||||
|
||||
```
|
||||
UART default UART console
|
||||
|
||||
USB monitoring <------ TX -----------> RX----+
|
||||
v
|
||||
Parse command
|
||||
and output result
|
||||
| Optional 3.3V
|
||||
RX <----------- TX<---+ (----------->) Serial-to-USB
|
||||
Adapter
|
||||
```
|
||||
|
||||
If everything goes fine, the output on default UART should be "Result: Success". Else, it should be "Result: Failure".
|
2
examples/peripherals/uart/uart_repl/main/CMakeLists.txt
Normal file
2
examples/peripherals/uart/uart_repl/main/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "uart_repl_example_main.c"
|
||||
INCLUDE_DIRS ".")
|
3
examples/peripherals/uart/uart_repl/main/component.mk
Normal file
3
examples/peripherals/uart/uart_repl/main/component.mk
Normal file
@ -0,0 +1,3 @@
|
||||
#
|
||||
# Main Makefile. This is basically the same as a component makefile.
|
||||
#
|
@ -0,0 +1,181 @@
|
||||
/* UART Echo Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/uart.h"
|
||||
#include "soc/uart_periph.h"
|
||||
#include "esp_rom_gpio.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "hal/gpio_hal.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_console.h"
|
||||
#include "linenoise/linenoise.h"
|
||||
#include <string.h>
|
||||
|
||||
#define DEFAULT_UART_CHANNEL (0)
|
||||
#define CONSOLE_UART_CHANNEL (1 - DEFAULT_UART_CHANNEL)
|
||||
#define DEFAULT_UART_RX_PIN (3)
|
||||
#define DEFAULT_UART_TX_PIN (2)
|
||||
#define CONSOLE_UART_RX_PIN (4)
|
||||
#define CONSOLE_UART_TX_PIN (5)
|
||||
|
||||
#define UARTS_BAUD_RATE (115200)
|
||||
#define TASK_STACK_SIZE (2048)
|
||||
#define READ_BUF_SIZE (1024)
|
||||
|
||||
/* Message printed by the "consoletest" command.
|
||||
* It will also be used by the default UART to check the reply of the second
|
||||
* UART. As end of line characters are not standard here (\n, \r\n, \r...),
|
||||
* let's not include it in this string. */
|
||||
const char test_message[] = "This is an example string, if you can read this, the example is a success!";
|
||||
|
||||
/**
|
||||
* @brief This function connects default UART TX to console UART RX and default
|
||||
* UART RX to console UART TX. The purpose is to send commands to the console
|
||||
* and get the reply directly by reading RX FIFO.
|
||||
*/
|
||||
static void connect_uarts(void)
|
||||
{
|
||||
esp_rom_gpio_connect_out_signal(DEFAULT_UART_RX_PIN, uart_periph_signal[1].tx_sig, false, false);
|
||||
esp_rom_gpio_connect_in_signal(DEFAULT_UART_RX_PIN, uart_periph_signal[0].rx_sig, false);
|
||||
|
||||
esp_rom_gpio_connect_out_signal(DEFAULT_UART_TX_PIN, uart_periph_signal[0].tx_sig, false, false);
|
||||
esp_rom_gpio_connect_in_signal(DEFAULT_UART_TX_PIN, uart_periph_signal[1].rx_sig, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Disconnect default UART from the console UART, this is used once
|
||||
* testing is finished, it will let us print messages on the UART without
|
||||
* sending them back to the console UART. Else, we would get an infinite
|
||||
* loop.
|
||||
*/
|
||||
static void disconnect_uarts(void)
|
||||
{
|
||||
esp_rom_gpio_connect_out_signal(CONSOLE_UART_TX_PIN, uart_periph_signal[1].tx_sig, false, false);
|
||||
esp_rom_gpio_connect_in_signal(CONSOLE_UART_RX_PIN, uart_periph_signal[1].rx_sig, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure and install the default UART, then, connect it to the
|
||||
* console UART.
|
||||
*/
|
||||
static void configure_uarts(void)
|
||||
{
|
||||
/* Configure parameters of an UART driver,
|
||||
* communication pins and install the driver */
|
||||
uart_config_t uart_config = {
|
||||
.baud_rate = UARTS_BAUD_RATE,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.source_clk = UART_SCLK_APB,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(uart_driver_install(DEFAULT_UART_CHANNEL, READ_BUF_SIZE * 2, 0, 0, NULL, 0));
|
||||
ESP_ERROR_CHECK(uart_param_config(DEFAULT_UART_CHANNEL, &uart_config));
|
||||
ESP_ERROR_CHECK(uart_set_pin(DEFAULT_UART_CHANNEL, DEFAULT_UART_TX_PIN, DEFAULT_UART_RX_PIN,
|
||||
UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
|
||||
|
||||
|
||||
connect_uarts();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Function called when command `consoletest` will be invoked.
|
||||
* It will simply print `test_message` defined above.
|
||||
*/
|
||||
static int console_test(int argc, char **argv) {
|
||||
printf("%s\n", test_message);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Function executed in another task then main one (as the one main
|
||||
* executes REPL console).
|
||||
* It will send "consoletest" command to the console UART and then read back
|
||||
* the response on RX.
|
||||
* The response shall contain the test_message string.
|
||||
*/
|
||||
static void send_commands(void* arg) {
|
||||
static char data[READ_BUF_SIZE];
|
||||
char command[] = "consoletest\n";
|
||||
int len = 0;
|
||||
void* substring = NULL;
|
||||
|
||||
/* Discard the first messages sent by the console. */
|
||||
do {
|
||||
len = uart_read_bytes(DEFAULT_UART_CHANNEL, data, READ_BUF_SIZE, 100 / portTICK_RATE_MS);
|
||||
} while (len == 0);
|
||||
|
||||
if ( len == -1 ) {
|
||||
goto end;
|
||||
}
|
||||
/* Send the command `consoletest` to the console UART. */
|
||||
len = uart_write_bytes(DEFAULT_UART_CHANNEL, command, sizeof(command));
|
||||
if ( len == -1 ) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Get the answer back from the console, give it some delay. */
|
||||
do {
|
||||
len = uart_read_bytes(DEFAULT_UART_CHANNEL, data, READ_BUF_SIZE - 1, 250 / portTICK_RATE_MS);
|
||||
} while (len == 0);
|
||||
|
||||
if ( len == -1 ) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether we can find test_message in the received message. Before
|
||||
* that, we need to add a NULL character to terminate the string.
|
||||
*/
|
||||
data[len] = 0;
|
||||
substring = strcasestr(data, test_message);
|
||||
|
||||
end:
|
||||
/* This is a must to not send anything to the console anymore! */
|
||||
disconnect_uarts();
|
||||
printf("Result: %s\n", substring == NULL ? "Failure" : "Success");
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_console_repl_t *repl = NULL;
|
||||
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
|
||||
repl_config.prompt = "repl >";
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "consoletest",
|
||||
.help = "Test console by sending a message",
|
||||
.func = &console_test,
|
||||
};
|
||||
esp_console_dev_uart_config_t uart_config = {
|
||||
.channel = CONSOLE_UART_CHANNEL,
|
||||
.baud_rate = UARTS_BAUD_RATE,
|
||||
.tx_gpio_num = CONSOLE_UART_TX_PIN,
|
||||
.rx_gpio_num = CONSOLE_UART_RX_PIN,
|
||||
};
|
||||
/**
|
||||
* As we don't have a real serial terminal, (we just use default UART to
|
||||
* send and receive commands, ) we won't handle any escape sequence, so the
|
||||
* easiest thing to do is set the console to "dumb" mode. */
|
||||
linenoiseSetDumbMode(1);
|
||||
|
||||
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
|
||||
configure_uarts();
|
||||
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
|
||||
/* Create a task for sending and receiving commands to and from the second UART. */
|
||||
xTaskCreate(send_commands, "send_commands_task", TASK_STACK_SIZE, NULL, 10, NULL);
|
||||
|
||||
ESP_ERROR_CHECK(esp_console_start_repl(repl));
|
||||
}
|
Loading…
Reference in New Issue
Block a user