嵌入式系统(基于FreeRTOS)串口命令行调试工具
【代码】嵌入式系统(基于FreeRTOS)串口命令行调试工具。
·
一、整体结构说明
嵌入式系统(基于FreeRTOS)串口命令行调试工具,采用模块化设计,核心结构分层如下:
| 模块层级 | 功能说明 |
|---|---|
| 1. 配置与宏定义 | 调试开关、缓冲区大小、密码/超时配置、硬件适配宏(UART/FreeRTOS) |
| 2. 类型定义 | 命令处理函数类型、命令表结构体、全局状态枚举 |
| 3. 全局变量 | 命令行缓冲、参数数组、认证/超时状态、命令表 |
| 4. 基础工具函数 | UART硬件收发(需用户适配)、十六/十进制解析、字符串辅助函数 |
| 5. 命令处理函数 | help/read/write/tasklist/info 核心功能实现 |
| 6. 核心解析逻辑 | 命令行分割、命令查找、主处理流程 |
| 7. UART字符处理 | 行缓冲、回显、退格、密码输入、超时检查 |
| 8. 安全保护逻辑 | 编译开关禁用、密码认证、超时自动登出 |
| 9. 初始化与任务 | CLI初始化、FreeRTOS任务入口(可选) |
二、完整代码实现
/*************************************************************************
* 文件名: cli_debug_tool.c
* 功能: 嵌入式串口命令行调试工具(基于FreeRTOS)
* 适配: ARM Cortex-M系列,需用户适配UART硬件驱动
* 编译开关: ENABLE_CLI_DEBUG - 启用调试工具(生产版本注释/undef)
*************************************************************************/
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <stdio.h>
/* ========================== 1. 配置与宏定义 ========================== */
// 调试工具总开关:生产版本注释此行或undef
#define ENABLE_CLI_DEBUG 1
// UART硬件配置(用户根据实际硬件修改)
#define UART_BAUDRATE 115200 // 串口波特率
#define UART_TX_PIN 0 // 发送引脚(示例)
#define UART_RX_PIN 1 // 接收引脚(示例)
// 命令行缓冲区配置
#define MAX_CMD_LENGTH 128 // 最大命令行长度
#define MAX_ARGC 16 // 最大参数个数
#define CMD_PROMPT "> " // 命令行提示符
// 安全保护配置
#define CLI_PASSWORD "EmbedDebug@2025" // 认证密码
#define CLI_TIMEOUT_MS (5 * 60 * 1000) // 5分钟超时登出
#define ADDR_VALID_MIN 0x20000000 // 合法内存起始地址(SRAM)
#define ADDR_VALID_MAX 0x20020000 // 合法内存结束地址
/* ========================== 2. 类型定义 ========================== */
// 命令处理函数类型:argc=参数个数,argv=参数数组,返回0成功/非0失败
typedef int (*cmd_handler_t)(int argc, char* argv[]);
// 命令表项结构体:关联命令名、帮助信息、处理函数
typedef struct {
const char* cmd_name; // 命令名称(如"read")
const char* help_info; // 帮助描述
cmd_handler_t handler; // 处理函数指针
} cmd_entry_t;
/* ========================== 3. 全局变量 ========================== */
#ifdef ENABLE_CLI_DEBUG
// 命令行缓冲区:存储用户输入的字符(行缓冲)
static char cmd_buffer[MAX_CMD_LENGTH];
static uint8_t cmd_index = 0; // 缓冲区当前索引
// 参数解析全局变量:argc=参数个数,argv=参数指针数组
static char* argv[MAX_ARGC];
static int argc = 0;
// 安全保护相关变量
static bool cli_authenticated = false; // 认证状态:false=未认证,true=已认证
static char password_buffer[32]; // 密码输入缓冲区
static uint8_t password_index = 0; // 密码缓冲区索引
static uint32_t last_activity_time = 0; // 最后操作时间(用于超时)
// FreeRTOS相关:portTICK_PERIOD_MS需在FreeRTOSConfig.h中定义
#ifndef portTICK_PERIOD_MS
#define portTICK_PERIOD_MS (1000 / configTICK_RATE_HZ)
#endif
#endif
/* ========================== 4. 基础工具函数 ========================== */
#ifdef ENABLE_CLI_DEBUG
/*************************************************************************
* 函数名: uart_send_char
* 功能: 发送单个字符到UART(需用户适配硬件驱动)
* 参数: ch - 要发送的字符
* 返回: 无
*************************************************************************/
void uart_send_char(uint8_t ch)
{
// 【用户适配】替换为实际硬件UART发送函数
// 示例(伪代码):
// while(!UART_TX_BUFFER_EMPTY);
// UART_DR_REG = ch;
}
/*************************************************************************
* 函数名: uart_send_string
* 功能: 发送字符串到UART(换行用\r\n,适配串口终端)
* 参数: str - 要发送的字符串
* 返回: 无
*************************************************************************/
void uart_send_string(const char* str)
{
if (str == NULL) return;
while (*str != '\0') {
uart_send_char(*str++);
}
}
/*************************************************************************
* 函数名: parse_hex
* 功能: 解析十六进制字符串为32位无符号整数(支持0x前缀)
* 参数: str - 输入字符串(如"0x1234"、"FF");value - 输出解析结果
* 返回: 0=成功,-1=失败(格式错误/空指针)
*************************************************************************/
static int parse_hex(const char* str, uint32_t* value)
{
if (str == NULL || value == NULL) return -1;
// 跳过0x/0X前缀
if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) {
str += 2;
}
*value = 0;
while (*str != '\0') {
char c = *str++;
uint32_t digit = 0;
// 解析数字/字母
if (c >= '0' && c <= '9') {
digit = c - '0';
} else if (c >= 'a' && c <= 'f') {
digit = c - 'a' + 10;
} else if (c >= 'A' && c <= 'F') {
digit = c - 'A' + 10;
} else {
return -1; // 非法字符
}
*value = (*value << 4) | digit; // 左移4位(16进制)+ 拼接
}
return 0;
}
/*************************************************************************
* 函数名: parse_dec
* 功能: 解析十进制字符串为32位有符号整数(支持负数)
* 参数: str - 输入字符串(如"123"、"-456");value - 输出解析结果
* 返回: 0=成功,-1=失败(格式错误/空指针)
*************************************************************************/
static int parse_dec(const char* str, int32_t* value)
{
if (str == NULL || value == NULL) return -1;
int sign = 1;
// 处理负号
if (*str == '-') {
sign = -1;
str++;
}
*value = 0;
while (*str != '\0') {
char c = *str++;
if (c >= '0' && c <= '9') {
*value = *value * 10 + (c - '0');
} else {
return -1; // 非法字符
}
}
*value *= sign;
return 0;
}
#endif
/* ========================== 5. 命令处理函数实现 ========================== */
#ifdef ENABLE_CLI_DEBUG
/*************************************************************************
* 命令: help
* 功能: 列出所有支持的命令及帮助信息
* 参数: argc - 参数个数;argv - 参数数组(无参数)
* 返回: 0=成功
*************************************************************************/
static int cmd_help(int argc, char* argv[])
{
uart_send_string("\r\n===== Available Commands =====\r\n");
// 提前声明命令表(避免编译报错)
extern const cmd_entry_t cmd_table[];
// 遍历命令表,格式化输出
for (int i = 0; cmd_table[i].cmd_name != NULL; i++) {
uart_send_string(" ");
uart_send_string(cmd_table[i].cmd_name);
// 对齐帮助信息(命令名占12个字符宽度)
int name_len = strlen(cmd_table[i].cmd_name);
int spaces = 12 - name_len;
for (int j = 0; j < spaces; j++) {
uart_send_char(' ');
}
uart_send_string(cmd_table[i].help_info);
uart_send_string("\r\n");
}
uart_send_string("==============================\r\n");
return 0;
}
/*************************************************************************
* 命令: read
* 功能: 读取指定内存地址的32位值(仅允许合法地址范围)
* 参数: argc=2;argv[1]=内存地址(十六进制,如0x20000000)
* 返回: 0=成功,-1=参数错误/地址非法
*************************************************************************/
static int cmd_read(int argc, char* argv[])
{
// 参数检查:必须传入地址参数
if (argc < 2) {
uart_send_string("Usage: read <addr> (hex, e.g. read 0x20000000)\r\n");
return -1;
}
// 解析十六进制地址
uint32_t addr;
if (parse_hex(argv[1], &addr) != 0) {
uart_send_string("Error: Invalid hex address format\r\n");
return -1;
}
// 地址合法性检查:防止访问非法内存导致崩溃
if (addr < ADDR_VALID_MIN || addr > ADDR_VALID_MAX) {
uart_send_string("Error: Address out of range (");
uart_send_string(argv[1]);
uart_send_string(")\r\n");
return -1;
}
// 读取内存(volatile防止编译器优化,确保读取实际内存)
volatile uint32_t* ptr = (volatile uint32_t*)addr;
uint32_t value = *ptr;
// 格式化输出结果
char buffer[64];
snprintf(buffer, sizeof(buffer), "Read: 0x%08X = 0x%08X\r\n", addr, value);
uart_send_string(buffer);
return 0;
}
/*************************************************************************
* 命令: write
* 功能: 写入32位值到指定内存地址(仅允许合法地址范围)
* 参数: argc=3;argv[1]=地址(十六进制);argv[2]=值(十六进制)
* 返回: 0=成功,-1=参数错误/地址非法/写入失败
*************************************************************************/
static int cmd_write(int argc, char* argv[])
{
// 参数检查
if (argc < 3) {
uart_send_string("Usage: write <addr> <value> (hex, e.g. write 0x20000000 0x12345678)\r\n");
return -1;
}
// 解析地址
uint32_t addr;
if (parse_hex(argv[1], &addr) != 0) {
uart_send_string("Error: Invalid hex address format\r\n");
return -1;
}
// 解析写入值
uint32_t value;
if (parse_hex(argv[2], &value) != 0) {
uart_send_string("Error: Invalid hex value format\r\n");
return -1;
}
// 地址合法性检查
if (addr < ADDR_VALID_MIN || addr > ADDR_VALID_MAX) {
uart_send_string("Error: Address out of range\r\n");
return -1;
}
// 写入内存(volatile确保写入实际内存)
volatile uint32_t* ptr = (volatile uint32_t*)addr;
*ptr = value;
// 验证写入结果
if (*ptr == value) {
char buffer[64];
snprintf(buffer, sizeof(buffer), "Write OK: 0x%08X = 0x%08X\r\n", addr, value);
uart_send_string(buffer);
return 0;
} else {
uart_send_string("Error: Write failed (verify error)\r\n");
return -1;
}
}
/*************************************************************************
* 命令: tasklist
* 功能: 显示FreeRTOS任务列表、状态、栈使用情况
* 参数: 无
* 返回: 0=成功,-1=内存分配失败
*************************************************************************/
static int cmd_tasklist(int argc, char* argv[])
{
uart_send_string("\r\n===== FreeRTOS Task List =====\r\n");
uart_send_string("Name\t\tState\t\tStack High Water\r\n");
uart_send_string("----------------------------------------\r\n");
// 获取系统任务数量
UBaseType_t num_tasks = uxTaskGetNumberOfTasks();
// 分配内存存储任务状态(FreeRTOS动态内存)
TaskStatus_t* task_status = (TaskStatus_t*)pvPortMalloc(num_tasks * sizeof(TaskStatus_t));
if (task_status == NULL) {
uart_send_string("Error: Memory allocation failed\r\n");
return -1;
}
// 获取所有任务状态
num_tasks = uxTaskGetSystemState(task_status, num_tasks, NULL);
// 遍历任务列表,格式化输出
for (UBaseType_t i = 0; i < num_tasks; i++) {
char buffer[128];
const char* state_str;
// 转换任务状态为字符串
switch (task_status[i].eCurrentState) {
case eRunning: state_str = "Running"; break;
case eReady: state_str = "Ready"; break;
case eBlocked: state_str = "Blocked"; break;
case eSuspended: state_str = "Suspended"; break;
case eDeleted: state_str = "Deleted"; break;
default: state_str = "Unknown"; break;
}
// 栈高水位:剩余栈空间(越小越危险)
uint32_t stack_hw = task_status[i].usStackHighWaterMark;
// 格式化输出(任务名占12字符,状态占12字符)
snprintf(buffer, sizeof(buffer),
"%-12s\t%-12s\t%lu bytes\r\n",
task_status[i].pcTaskName,
state_str,
stack_hw);
uart_send_string(buffer);
}
// 释放内存
vPortFree(task_status);
uart_send_string("==============================\r\n");
return 0;
}
/*************************************************************************
* 命令: info
* 功能: 显示系统基本信息(运行时间、任务数、堆内存)
* 参数: 无
* 返回: 0=成功
*************************************************************************/
static int cmd_info(int argc, char* argv[])
{
uart_send_string("\r\n===== System Information =====\r\n");
char buffer[64];
// 系统运行时间(ms)
uint32_t uptime = xTaskGetTickCount() * portTICK_PERIOD_MS;
snprintf(buffer, sizeof(buffer), "Uptime: %lu ms (%lu s)\r\n", uptime, uptime/1000);
uart_send_string(buffer);
// 任务数量
UBaseType_t num_tasks = uxTaskGetNumberOfTasks();
snprintf(buffer, sizeof(buffer), "Total Tasks: %lu\r\n", num_tasks);
uart_send_string(buffer);
// 空闲任务栈剩余
UBaseType_t idle_stack = uxTaskGetStackHighWaterMark(NULL);
snprintf(buffer, sizeof(buffer), "Idle Task Stack Left: %lu bytes\r\n", idle_stack);
uart_send_string(buffer);
// 当前空闲堆内存
size_t free_heap = xPortGetFreeHeapSize();
snprintf(buffer, sizeof(buffer), "Free Heap: %lu bytes\r\n", free_heap);
uart_send_string(buffer);
// 历史最小空闲堆(反映内存峰值使用)
size_t min_free_heap = xPortGetMinimumEverFreeHeapSize();
snprintf(buffer, sizeof(buffer), "Min Free Heap: %lu bytes\r\n", min_free_heap);
uart_send_string(buffer);
uart_send_string("==============================\r\n");
return 0;
}
#endif
/* ========================== 6. 核心解析逻辑 ========================== */
#ifdef ENABLE_CLI_DEBUG
/*************************************************************************
* 函数名: parse_command
* 功能: 将命令行字符串分割为参数数组(argc/argv)
* 参数: cmd_line - 输入的命令行字符串(如"read 0x20000000")
* 返回: 无
*************************************************************************/
static void parse_command(char* cmd_line)
{
argc = 0;
char* token = cmd_line;
// 跳过前导空格/制表符
while (*token == ' ' || *token == '\t') {
token++;
}
// 分割字符串为参数
while (*token != '\0' && argc < MAX_ARGC) {
argv[argc++] = token; // 记录参数起始地址
// 查找参数结束位置(空格/制表符)
while (*token != ' ' && *token != '\t' && *token != '\0') {
token++;
}
// 替换分隔符为字符串结束符
if (*token != '\0') {
*token = '\0';
token++;
// 跳过连续分隔符
while (*token == ' ' || *token == '\t') {
token++;
}
}
}
}
/*************************************************************************
* 函数名: find_command
* 功能: 根据命令名查找命令表中的对应项
* 参数: cmd_name - 要查找的命令名(如"read")
* 返回: 命令表项指针(NULL=未找到)
*************************************************************************/
static const cmd_entry_t* find_command(const char* cmd_name)
{
extern const cmd_entry_t cmd_table[];
for (int i = 0; cmd_table[i].cmd_name != NULL; i++) {
if (strcmp(cmd_table[i].cmd_name, cmd_name) == 0) {
return &cmd_table[i];
}
}
return NULL;
}
/*************************************************************************
* 函数名: process_command
* 功能: 命令行主处理逻辑:解析→查找→执行
* 参数: cmd_line - 输入的命令行字符串
* 返回: 无
*************************************************************************/
static void process_command(char* cmd_line)
{
// 解析命令行为argc/argv
parse_command(cmd_line);
// 空命令直接返回
if (argc == 0) {
return;
}
// 查找命令
const cmd_entry_t* cmd = find_command(argv[0]);
if (cmd == NULL) {
uart_send_string("Error: Unknown command - ");
uart_send_string(argv[0]);
uart_send_string("\r\n");
return;
}
// 执行命令处理函数
int result = cmd->handler(argc, argv);
if (result != 0) {
uart_send_string("Command execution failed\r\n");
}
}
#endif
/* ========================== 7. UART字符处理 ========================== */
#ifdef ENABLE_CLI_DEBUG
/*************************************************************************
* 函数名: cli_debug_process_char
* 功能: 处理单个UART接收字符:行缓冲、回显、退格、密码认证、超时检查
* 参数: ch - 接收到的字符
* 返回: 无
*************************************************************************/
void cli_debug_process_char(uint8_t ch)
{
// 1. 超时检查:已认证且超时则登出
uint32_t current_time = xTaskGetTickCount();
if (cli_authenticated && (current_time - last_activity_time) > (CLI_TIMEOUT_MS / portTICK_PERIOD_MS)) {
cli_authenticated = false;
uart_send_string("\r\n[Timeout] Session closed\r\nPassword: ");
password_index = 0;
return;
}
last_activity_time = current_time; // 更新最后操作时间
// 2. 未认证:进入密码输入模式
if (!cli_authenticated) {
// 回车:验证密码
if (ch == '\r' || ch == '\n') {
password_buffer[password_index] = '\0';
if (strcmp(password_buffer, CLI_PASSWORD) == 0) {
cli_authenticated = true;
uart_send_string("\r\n[Success] Access granted\r\n");
uart_send_string(CMD_PROMPT); // 显示命令提示符
} else {
uart_send_string("\r\n[Failed] Wrong password\r\nPassword: ");
}
password_index = 0; // 重置密码缓冲区
return;
}
// 退格/删除:删除最后一个字符(回显为"← 空格 ←")
if (ch == '\b' || ch == 0x7F) {
if (password_index > 0) {
password_index--;
uart_send_string("\b \b"); // 视觉上删除字符
}
return;
}
// 普通字符:存入密码缓冲区,回显为*(隐藏明文)
if (password_index < sizeof(password_buffer) - 1) {
password_buffer[password_index++] = ch;
uart_send_char('*'); // 不显示明文密码
}
return;
}
// 3. 已认证:正常命令行处理
// 回车:执行命令
if (ch == '\r' || ch == '\n') {
uart_send_string("\r\n"); // 换行
cmd_buffer[cmd_index] = '\0';
process_command(cmd_buffer); // 处理命令
cmd_index = 0; // 重置命令缓冲区
uart_send_string(CMD_PROMPT); // 显示提示符
return;
}
// 退格/删除:删除最后一个字符
if (ch == '\b' || ch == 0x7F) {
if (cmd_index > 0) {
cmd_index--;
uart_send_string("\b \b"); // 视觉删除
}
return;
}
// 普通字符:存入缓冲区并回显
if (cmd_index < MAX_CMD_LENGTH - 1) {
cmd_buffer[cmd_index++] = ch;
uart_send_char(ch); // 回显字符
}
}
#endif
/* ========================== 8. 命令表定义 ========================== */
#ifdef ENABLE_CLI_DEBUG
// 命令表:按"命令名-帮助信息-处理函数"关联(最后一项为NULL结束)
const cmd_entry_t cmd_table[] = {
{"help", "Show all commands and usage", cmd_help},
{"read", "Read 32-bit value from memory: read <addr>", cmd_read},
{"write", "Write 32-bit value to memory: write <addr> <val>", cmd_write},
{"tasklist", "Show FreeRTOS task list and stack info", cmd_tasklist},
{"info", "Show system information (uptime/heap/tasks)", cmd_info},
{NULL, NULL, NULL} // 结束标记
};
#endif
/* ========================== 9. 初始化与任务 ========================== */
/*************************************************************************
* 函数名: uart_init
* 功能: UART硬件初始化(需用户适配实际硬件)
* 参数: 无
* 返回: 无
*************************************************************************/
void uart_init(void)
{
#ifdef ENABLE_CLI_DEBUG
// 【用户适配】添加UART初始化代码:
// 1. 配置引脚复用(TX/RX)
// 2. 设置波特率(UART_BAUDRATE)
// 3. 启用接收中断(或DMA)
// 4. 启用UART收发
// 初始化完成后提示密码输入
uart_send_string("\r\n===== Embedded CLI Debug Tool =====\r\n");
uart_send_string("Please enter password: ");
last_activity_time = xTaskGetTickCount(); // 初始化超时时间
#endif
}
/*************************************************************************
* 函数名: cli_debug_task
* 功能: FreeRTOS CLI任务(可选,若UART用中断接收则无需此任务)
* 参数: pvParameters - 任务参数(未使用)
* 返回: 无
*************************************************************************/
#ifdef ENABLE_CLI_DEBUG
void cli_debug_task(void* pvParameters)
{
(void)pvParameters;
uint8_t ch;
// 任务主循环
while (1) {
// 【用户适配】读取UART接收字符(非阻塞)
// 示例:if (uart_rx_available()) { ch = uart_read_char(); cli_debug_process_char(ch); }
vTaskDelay(pdMS_TO_TICKS(10)); // 10ms延时,降低CPU占用
}
}
#endif
/*************************************************************************
* 函数名: cli_debug_init
* 功能: CLI调试工具初始化:UART初始化 + 创建FreeRTOS任务
* 参数: 无
* 返回: 无
*************************************************************************/
void cli_debug_init(void)
{
#ifdef ENABLE_CLI_DEBUG
// 初始化UART
uart_init();
// 创建CLI处理任务(优先级低于业务任务,栈大小按需调整)
xTaskCreate(cli_debug_task, "CLI_Task", 1024, NULL, 1, NULL);
#else
// 生产版本:无操作
#endif
}
/* ========================== 10. 生产版本兼容宏 ========================== */
#ifndef ENABLE_CLI_DEBUG
// 生产版本:空宏定义,避免编译错误
#define cli_debug_init() ((void)0)
#define cli_debug_process_char(ch) ((void)0)
#define cli_debug_task(pv) ((void)0)
#endif
三、代码使用说明
1. 硬件适配
- UART收发函数:
uart_send_char/uart_init需要用户根据实际MCU(如STM32、NXP)的HAL/LL库适配; - UART接收方式:推荐用「中断接收」(字符到达后调用
cli_debug_process_char),也可使用FreeRTOS任务轮询(cli_debug_task); - 内存地址范围:修改
ADDR_VALID_MIN/ADDR_VALID_MAX适配目标MCU的SRAM/Flash地址。
2. 编译控制
- 开发版本:保留
#define ENABLE_CLI_DEBUG 1,编译时包含调试工具; - 生产版本:注释/删除
ENABLE_CLI_DEBUG,所有CLI相关函数会被空宏替换,无代码冗余。
3. 安全保护
- 密码认证:默认密码
EmbedDebug@2025,建议根据项目修改; - 超时登出:5分钟无操作自动登出,可修改
CLI_TIMEOUT_MS调整; - 内存保护:
read/write命令仅允许访问合法地址范围,防止非法内存操作。
4. 扩展命令
添加新命令只需两步:
- 实现命令处理函数(如
cmd_xxx); - 在
cmd_table中添加一行:{"xxx", "Help info", cmd_xxx}。
四、典型使用流程
- 设备上电后,串口终端显示
Please enter password:; - 输入正确密码(回显为
*),回车后显示提示符>; - 输入命令调试:
help:查看所有命令;read 0x20000000:读取0x20000000地址的32位值;write 0x20000000 0x12345678:写入值到指定地址;tasklist:查看FreeRTOS任务状态;info:查看系统运行信息;
- 5分钟无操作自动登出,需重新输入密码。
五、关键注意事项
- 中断安全:若在UART中断中调用
cli_debug_process_char,需确保FreeRTOS API(如xTaskGetTickCount)在中断中可用(启用configUSE_TIMERS); - 栈大小:
cli_debug_task的栈大小(1024)需根据实际命令复杂度调整; - 内存保护:
write命令禁止写入程序Flash/寄存器地址,避免系统崩溃; - 字符编码:仅支持ASCII字符,不支持中文;
- 兼容性:代码基于FreeRTOS,若使用其他RTOS,需替换
xTaskGetTickCount/uxTaskGetSystemState等API。
更多推荐
所有评论(0)