目录
前言
本文是基于第一章创建好的工程,来添加控制台终端交互的功能,同时详细介绍了ESP-IDF组件的构成,对理解工程文件很有帮助。开发板芯片是ESP32-P4、ESP-IDF版本是5.5.3。
控制台终端交互这是一个非常有用的功能,我们可以在终端输入一些命令来查询芯片的某种状态或者是执行某种操作。在ESP-IDF中其实很容易开启终端交互,但是官方手册写的不太友好,很难让人看懂它的目的。这部分功能也可以添加进工程模板中。
一、完善工程
系统设置
打开工程模板
编辑
点解左下角的齿轮图标(SDK配置编辑器),搜索console,改为如下设置
编辑
Channel for console output(控制台输出通道)
UART0:默认,通过硬件串口 UART0 输出日志和控制台信息。USB Serial/JTAG Controller:通过芯片内置的 USB Serial/JTAG 控制器输出(ESP32-C3/S3/P4 等支持),无需额外 USB 转串口芯片,直接用 USB 线连接电脑即可。Custom:自定义 UART 端口和引脚。None:关闭 UART 控制台输出。
Channel for console secondary output(控制台次要输出通道)
- 当主通道(比如 UART0)未连接时,可额外指定一个备用输出通道(如 USB_SERIAL_JTAG),仅支持非阻塞模式,不支持 REPL 交互式输入。也就是说该通道只能作为备用输出并且不能输入
Line ending for console output(输出换行符格式)
CRLF:\n→\r\n(Windows 风格,适配大部分串口工具)。LF:保持\n不变(Linux/macOS 风格)。CR:\n→\r(老式设备风格,极少用)。
推荐 CRLF:绝大多数串口终端(如串口助手、PuTTY、minicom)默认识别 \r\n 为换行。
Line ending for console input(输入换行符格式)
CRLF:将输入的\r\n转换为\n。LF:保持输入的\n不变。CR:将输入的\r转换为\n。
将键盘回车(\r)转换为 \n,完全适配 USB Serial/JTAG 控制台,能正常识别回车提交命令。
组件管理
声明:本节内容引用自《微雪官方文档》
ESP-IDF 采用组件化设计,将系统的各项功能(如操作系统、网络协议栈、驱动、外设支持等)划分为独立的“组件”(Component)。每个组件是一个可复用、独立的代码包,专注于实现某一特定功能。在项目构建时,组件会被编译为静态库,并由主应用程序或其他组件进行链接和调用。
在此架构基础上,开发者可以灵活地添加自定义组件或集成第三方组件(如特定的云服务、协议或驱动)。通过将外部组件与 ESP-IDF 的内部核心组件相结合,可实现项目功能的扩展与定制,进而达成项目整体的模块化与高效复用。带来的优势包括:清晰的分层与依赖管理、代码复用、易于扩展与更新,从而降低项目复杂度,加快开发迭代。
组件类型
在 ESP-IDF 项目中,组件主要分为以下三类:
- 内置组件(Core/Built-in Components) :这些是 ESP-IDF 框架自带的核心组件,位于 ESP-IDF 安装目录下的
components文件夹中,提供底层驱动、网络协议栈、FreeRTOS 操作系统等基础功能。开发者可以直接在代码中包含其头文件并使用,无需额外配置。 - 项目组件(Project Components) :这些是开发者在项目根目录下的
components文件夹中创建的组件,适合存放项目特有、可复用的功能模块,有助于保持主逻辑main的整洁和项目的模块化。 - 外部组件(External/Managed Components) :这些组件由社区或第三方开发者创建,并发布到 ESP-IDF 组件注册表 (ESP Component Registry)。可以通过 IDF 组件管理器 自动下载和集成到项目中,下载后会存放在项目根目录下的
managed_components文件夹中。
组件结构
一个完整的 ESP-IDF 组件通常包含以下内容:
-
源代码: 组件实现的核心功能代码文件。
-
头文件: 对外暴露的接口声明,供其他组件或主程序调用。
-
CMakeLists.txt
- 定义源代码和头文件的编译方式
- 声明组件依赖关系
- 注册组件到构建系统
- 配置可选特性
- 作为 CMake 构建描述文件,指示编译器如何编译、链接和构建该组件
-
idf_component.yml: 组件管理器描述文件,列出该组件依赖的其他组件及其版本信息。ESP-IDF 组件管理器会根据此文件自动下载和集成所需依赖,确保依赖关系满足。
一个完整的项目一般包含以下文件夹
- main文件夹
项目主组件目录,包含项目的主要源代码。main 目录下通常有自己的 CMakeLists.txt 和可选的 idf_component.yml,用于声明主组件的依赖关系。应用程序必须包含一个 main 组件(名称可更改),这是保存应用程序逻辑的主要组件。
- components文件夹
项目自定义组件目录。每个子目录为一个组件,包含源代码、头文件、CMakeLists.txt、Kconfig 等。可用于组织可复用代码或引入第三方组件。若有同名组件,优先使用 components文件夹下的版本。
- managed_components文件夹
由 IDF 组件管理器 自动创建,用于存放通过组件管理器下载的托管组件。每个托管组件通常包含 idf_component.yml,定义组件元数据和依赖关系。请勿手动修改该目录内容。如需修改,可将组件复制到 components文件夹 目录下。
- idf_component.yml
组件管理器描述文件,声明组件的元数据及其依赖项。可存在于 main/、components/ 下的每个组件目录,以及 managed_components/ 下的托管组件目录。该文件是可选的,仅在需要声明依赖时才需要。
- dependencies.lock
由 IDF 组件管理器 自动生成,记录当前项目使用的所有托管组件及其精确版本。请勿手动修改。只有当项目中存在 idf_component.yml 文件时才会生成该文件。
自定义组件
添加一个自定义组件,比如我这是user,接着在user文件夹下新建管理文件idf_component.yml,并在该文件中添加以下内容,显示效果如下。
version: "1.0.0"
dependencies: #依赖声明
cmd_system: #依赖组件名
path: ${IDF_PATH}/examples/system/console/advanced/components/cmd_system #依赖组件地址
编辑
- version: "1.0.0"
表示我自定义组件(user)的版本号,这是格式要求,必须包含,数字可以随便写,只要符合 数字.数字.数字 格式就行。
- dependencies:
文件格式,必须包含
-
cmd_system:
- path:
这是使用本地目录组件的依赖方式,cmd_system代表本地目录组件名,这是一个系统命令组件,path代表该组件的地址,注意缩进。至于为什么要添加这个组件,见本章2.3 user.c。
path的地址所用的是相对路径,第一章已经说明了${IDF_PATH}的含义。如果报错,提示无法找到这个文件,也使用绝对路径,点开打开终端,能从终端信息中找到IDF_PATH的路径如:
IDF_PATH: E:\esp\v6.0\esp-idf
替代后为:
E://esp/v6.0/esp-idf/examples/system/console/advanced/components/cmd_system
在我这,整体含义是:我自定义了组件user,该组件中需要依赖本地目录组件cmd_system,于是我的代码能够使用cmd_system中的函数
二、代码编写
2.1 user组件下的CMakeLists.txt
idf_component_register(SRCS "user.c"
INCLUDE_DIRS "include"
REQUIRES console)
不用说,按要求添加依赖:console
2.2 user.h
#ifndef USER_H
#define USER_H
#include <stdio.h> // 输入输出函数
#include <string.h> // 字符串处理函数
#include "esp_console.h" // ESP32控制台函数
#include "cmd_system.h" // 系统命令相关函数
#include "linenoise/linenoise.h" // 行编辑库
void CONSOLE_REPL_INIT(void); // REPL环境初始化函数声明
#endif // USER_H
2.3 user.c
#include "user.h"
void CONSOLE_REPL_INIT(void)
{
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT(); // 使用默认REPL配置
esp_console_repl_t *repl = NULL;
#if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
esp_console_dev_usb_serial_jtag_config_t usb_serial_jtag_config = ESP_CONSOLE_DEV_USB_SERIAL_JTAG_CONFIG_DEFAULT(); // 使用默认USB串行JTAG配置
ESP_ERROR_CHECK(esp_console_new_repl_usb_serial_jtag(&usb_serial_jtag_config, &repl_config, &repl)); // 创建USB串行JTAG REPL环境
#else
esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT(); // 使用默认UART配置
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl)); // 创建UART REPL环境
#endif
ESP_ERROR_CHECK(esp_console_start_repl(repl)); // 启动REPL环境
esp_console_register_help_command(); // 注册帮助命令
register_system_common(); // 注册系统常用命令
linenoiseSetDumbMode(1); // 设置linenoise为简单模式,适用于串行终端
}
在官方文档中《控制台终端》提供了很多esp_console_xxx、linenoisexxx的函数,我们大多不需要去使用,因为初始化 REPL 环境有三个一体化函数,这些函数已经调用了很多console初始化函数。重复使用会报错,当然你也可以用这些基础函数自定义REPL 环境。
初始化 REPL 环境一体化函数共有三个,分别是通过UART、USB CDC、USB-SERIAL-JTAG来建立一个控制台REPL环境。在SDK配置编辑器——console设置中需要确定**Channel for console output**,从而确定初始化什么环境,这样就比较麻烦。
我这用预编译指令对UART和USB-SERIAL-JTAG都做了初始化,只需要SDK配置编辑器——console设置就行,无需改动代码,不过只支持默认UART0,有其他串口需求自行更改。
我调用了函数register_system_common();,用来注册系统常用命令,这样控制台的交互就能使用这些命令。包括free、heap等。如果不叫这一句,控制台虽然能交互,但没有任何一条命令能使用。而该函数的声明在组件cmd_sysytem中,这就是为什么我前面要添加这个组件。
使用函数linenoiseSetDumbMode(1);可以避免输入命令时光标跳动、闪烁等异常。但是会导致控制台的方向键查历史、TAB 补全、行编辑功能被关闭了。自行选择
其他函数官方文档有讲解,这里不再重复了。
2.4 main
#include <stdio.h>
#include "user.h"
static int cmd_add(int argc, char **argv)
{
// 检查参数数量
if (argc != 3)
{
printf("Usage: add <num1> <num2>\n");
return -1;
}
// 字符串转数字
int a = atoi(argv[1]);
int b = atoi(argv[2]);
// 计算并输出
printf("Result: %d\n", a + b);
return 0;
}
void app_main(void)
{
esp_console_cmd_t cmd = {
.command = "add",
.help = "Add two numbers",
.func = cmd_add,
};
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd)); // 注册自定义命令
CONSOLE_REPL_INIT(); // 初始化REPL环境
}
这里我注册了一个加和任务,用来测试。
三、结果展示
编辑
工程下载: