ESP-IDF: как сделать интерактивную консоль Печать
Добавил(а) microsin   

Рабочие примеры организации интерактивной консоли можно найти в папке examples/system/console каталога установки ESP-IDF. Там есть два примера: advanced и basic, отличаются они функциональными возможностями. У примера advanced доступны сохранение истории команд в файловой системе и редактирование строки команды.

Процесс добавления интерактивной консоли в проект ESP32-C3 на основе примера advanced, процесс по шагам:

1. idf.py menuconfig: поменяйте опцию Component config → ESP System Settings → Channel for console output на "(X) USB Serial/JTAG Controller" (это установит опцию CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y). Можно выбрать и другие варианты: Default: UART0, USB CDC, Custom UART, None.

ESP IDF menuconfig interactive console fig01

Замечание: набор доступных вариантов для опции Channel for console output может меняться в зависимости от используемого чипа. Например, для всех чипов доступен вариант UART0, для чипов ESP32S2 и ESP32S3 доступен вариант USB CDC.

2. Скопируйте папку components из примера advanced в корневой каталог своего проекта. Также скопируйте в свою папку main файлы console_settings.c и console_settings.h из папки advanced/main примера.

3. Откройте в текстовом редакторе файл main/CMakeLists.txt своего проекта. В секцию исходного кода SRCS добавьте console_settings.c, а в секцию PRIV_REQUIRES добавьте компоненты console, esp_driver_usb_serial_jtag, cmd_system. После этого файл main/CMakeLists.txt может выглядеть следующим образом (список файлов исходного кода и компонентов может отличаться, в зависимости от вашего проекта):

idf_component_register(SRCS "main.c"
                            "console_settings.c"
                    PRIV_REQUIRES spi_flash
                                  esp_driver_gpio
                                  esp_driver_uart
                                  console
                                  esp_driver_usb_serial_jtag
                                  cmd_system
                    INCLUDE_DIRS "")

4. Добавьте в модуль проекта, где будет использоваться консоль, подключение следующих заголовков:

#include "console_settings.h"
#include "esp_console.h"
#include "cmd_system.h"
#include "linenoise/linenoise.h"
#include "argtable3/argtable3.h"
#include "soc/soc_caps.h"

Замечание: если вы будете использовать сохранение в энергонезависимой памяти истории команд (определяется активацией опции CONFIG_CONSOLE_STORE_HISTORY), то может понадобиться также добавление в main/CMakeLists.txt компонентов fatfs, nvs_flash а также необходимо подключение заголовков:

#include "esp_vfs_fat.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "cmd_nvs.h"

5. По аналогии с модулем main/console_example_main.c из примера advanced добавьте инициализацию консоли:

static const char *prompt;

#define HISTORY_PATH NULL
#define PROMPT_STR CONFIG_IDF_TARGET

// Инициализация периферийного устройства вывода консоли
// (UART, USB_OTG, USB_JTAG): initialize_console_peripheral();

// Инициализация библиотеки linenoise и esp_console: initialize_console_library(HISTORY_PATH);

// Перед каждой строкой консоли печатается приглашение (promt).
// Его можно поменять по своему усмотрению, сделать динамическим,
// и так далее. prompt = setup_prompt(PROMPT_STR ">");

// Регистрация команд: esp_console_register_help_command(); register_system_common();printf("\n" "Это пример использования ESP-IDF компонента console.\n" "Введите 'help' для получения списка доступных команд.\n" "Используйте клавиши со стрелками UP/DOWN для навигации по истории команд.\n" "Нажимайте TAB, когда вводите имя команды, чтобы использовать auto-complete.\n" "Ctrl+C прервет работу рабочего окружения консоли.\n");

if (linenoiseIsDumbMode()) { printf("\n" "Ваше приложение терминала не поддерживает escape-последовательности.\n" "Редактирование строки функция истории запрещены.\n" "На Windows попробуйте другой терминальный клиент, например Putty.\n"); }

6. Добавьте в какой-нибудь поток обработку консоли, опять-таки на основе примера advanced (см. цикл while функции app_main в исходном коде main/console_example_main.c):

    // Получение введенной пользователем строки с помощью
    // библиотеки linenoise. Возврат из этого вызова
    // произойдет, когда пользователь нажал ENTER.
    char* line = linenoise(prompt);

#if CONFIG_CONSOLE_IGNORE_EMPTY_LINES if (line == NULL) { /* Игнорирование пустых строк */ continue; }
#else if (line == NULL) { /* Break on EOF или error */ return; }
#endif // CONFIG_CONSOLE_IGNORE_EMPTY_LINES
// Добавление команды в историю, если она не пустая: if (strlen(line) > 0) { linenoiseHistoryAdd(line);
#if CONFIG_CONSOLE_STORE_HISTORY // Сохранение истории команд в файловой системе: linenoiseHistorySave(HISTORY_PATH);
#endif // CONFIG_CONSOLE_STORE_HISTORY }
// Запуск команды: int ret; esp_err_t err = esp_console_run(line, &ret); if (err == ESP_ERR_NOT_FOUND) { printf("Неизвестная команда\n"); } else if (err == ESP_ERR_INVALID_ARG) { // Команда была пустая } else if (err == ESP_OK && ret != ESP_OK) { printf("Команда возвратила ненулевой код ошибки: 0x%x (%s)\n", ret, esp_err_to_name(ret)); } else if (err != ESP_OK) { printf("Внутренняя ошибка: %s\n", esp_err_to_name(err)); } // Библиотека linenoise выделяет буфер строки в куче, поэтому // его нужно освободить: linenoiseFree(line);

Обратите внимание, что вызов функции linenoise блокирующий (на ожидании окончания ввода пользователя по нажатию ENTER в консоли), поэтому для обеспечения работы другого функционала программы (в частности, для отображения отладочного вывода в той же консоли) для linenoise необходим отдельный поток.

Скомпилируйте и запустите проект, как обычно. Вы увидите вот такую консоль (выход из монитора по умолчанию Ctrl+]):

$ idf.py build flash monitor --port=/dev/ttyACM0
...
This is esp32c3 chip with 1 CPU core(s), WiFi/BLE, silicon revision v0.3, 2MB external flash
Minimum free heap size: 308528 bytes
Это пример использования ESP-IDF компонента console. Введите 'help' для получения списка доступных команд. Используйте клавиши со стрелками UP/DOWN для навигации по истории команд. Нажимайте TAB, когда вводите имя команды, чтобы использовать auto-complete. Ctrl+C прервет работу рабочего окружения консоли. esp32c3> help help [< string>] [-v < 0|1>] Print the summary of all registered commands if no arguments are given, otherwise print summary of given command. < string> Name of command -v, --verbose=< 0|1> If specified, list console commands with given verbose level
free Get the current size of free heap memory
heap Get minimum size of free heap memory that was available during program execution
version Get version of chip and SDK
restart Software reset of the chip
log_level < tag|*> < none|error|warn|debug|verbose> Set log level for all tags or a specific tag. < tag|*> Log tag to set the level for, or * to set for all tags < none|error|warn|debug|verbose> Log level to set. Abbreviated words are accepted.
esp32c3> free 305448 esp32c3>

[Добавление и изменение списка поддерживаемых команд консоли]

В папке components, которая была скопирована на шаге 2, имеется модуль cmd_system/cmd_system_common.c. В нем регистрируются все команды, которые поддерживает консоль. По умолчанию уже зарегистрированы следующие команды:

Команда Функция регистрации
free register_free
heap register_heap
version register_version
restart register_restart
log_level register_log_level

Свои собственные команды можно создать по аналогии этих команд. Пример регистрации команды "test", которая будет принимать один числовой аргумент:

1. Создайте функцию регистрации команды register_test:

static struct {
    struct arg_str *tag;
    struct arg_end *end;
} test_args;

static void register_test(void) { test_args.tag = arg_str1(NULL, NULL, "< число>", "Количество итераций теста (1 .. 100)."); test_args.end = arg_end(2);
const esp_console_cmd_t cmd = { .command = "test", .help = "Команда для запуска тестирования.", .hint = NULL, .func = &func_test, .argtable = &test_args }; ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) ); }

2. Создайте функцию обработчика команды func_test:

static int func_test(int argc, char **argv)
{
    int nerrors = arg_parse(argc, argv, (void **) &test_args);
    if (nerrors != 0) {
        arg_print_errors(stderr, test_args.end, argv[0]);
        return 1;
    }
    assert(test_args.tag->count == 1);
    //printf("argv[0]: %s\n", argv[0]);
    printf("Запуск %i проверок\n", atoi(argv[1]));
    return 0;
}

Параметры, передаваемые в команде, доступны в виде текстовых строк в массиве argv.

3. Добавьте вызов register_test в функцию register_system_common:

void register_system_common(void)
{
    register_free();
    register_heap();
    register_version();
    register_restart();
#if WITH_TASKS_INFO register_tasks();
#endif register_log_level(); register_test(); }

[Ссылки]

1. ESP-IDF: компонент консоли.