十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32F429移植FreeRTOS-Plus-CLI:命令行调试实战指南

STM32F429移植FreeRTOS-Plus-CLI:命令行调试实战指南 如果你的 STM32F429 板子上已经跑起了 FreeRTOS但每次想确认一个任务当前是阻塞、就绪还是被卡死都要重新编译下载、到处加打印还要靠逻辑分析仪慢慢猜这个感觉我太熟悉了。后来我在工程里集成了 FreeRTOS-Plus-CLI用串口敲几条命令就能看任务状态、运行时间统计甚至直接改业务参数整个调试节奏完全不一样。这篇文章就是我多次在 STM32F429 上移植 FreeRTOS-Plus-CLI 的完整记录从源码集成、串口收发、命令注册到运行时统计和各类高频问题排查都会讲到。适合有一定 FreeRTOS 基础、想给嵌入式系统加一个可交互调试入口的工程师照着做基本能少踩一半的坑。1. 为什么要在 STM32F429 上给 FreeRTOS 加一个命令行1.1 调试裸机和简单 RTOS 工程的老大难问题在只跑裸机或者简单定时器轮询的项目里调试通常靠 LED 闪烁、串口 printf、以及断点。断点这招在逻辑复杂的状态机里其实很难用因为打断的位置不对现场早就不一样了。而走到多任务阶段后问题更明显任务优先级配错、栈溢出、信号量拿不到这些都不是靠 printf 插桩能快速定位的尤其当问题只在跑了几分钟甚至几小时之后才出现你根本没法定点断下来。我印象最深的一次是有个任务周期性采集传感器数据跑一会儿就停在那里。我怀疑是某个优先级更高的任务占用 CPU 太久但又没办法实时看到每个任务的运行时间。当时手里只有一个串口调试助手还是只能用 printf 打印日志。我打了整整两天的日志最后才怀疑到调度配置上。后来我想明白如果系统里有一个命令行能随时查看任务状态和运行时间统计这类问题五分钟就能确认。1.2 FreeRTOS-Plus-CLI 到底能做什么FreeRTOS-Plus-CLI 是 FreeRTOS 官方提供的一个命令行解析组件它本身不依赖特定板卡只需要一个能输入字符、能输出字符的通道最常见的载体就是串口。它可以让你在运行中的嵌入式系统里像 SSH 登录服务器一样敲命令然后立刻看结果。它的核心能力可以归为三类查看类命令任务运行状态、运行时统计、系统版本、运行时长、内存使用情况。操作类命令改变全局参数、触发某个自检流程、复位某个外设、开关注册表里的开关。测试类命令模拟传感器数据、注入异常、批量跑压力测试。这也是为什么很多人把它叫做“嵌入式系统的控制台”。在 FreeRTOS 官方的演示工程里CLI 组件通常是和 TCP 服务器配套的通过网络远程敲命令。但我们做 STM32 这类 MCU 项目完全可以直接挂到 UART 上实现成本更低调试效率提升却很直接。1.3 自己写命令解析器还是直接上现成组件你可能会想一个命令解析器而已我自己写也就一百多行为什么非要用 FreeRTOS-Plus-CLI确实最简单的那种“收到一个字符串用 strcmp 比较一下再执行”在命令数量少的时候完全够用。但一旦命令多起来问题就来了参数怎么分割带引号的字符串怎么处理帮助信息怎么统一维护输出太长怎么分页这些看着不起眼的小设计自己写起来非常琐碎还容易出边界 bug。FreeRTOS-Plus-CLI 的好处是命令注册、参数提取、输出续传这些机制都已经做好了代码量不大但结构完整你只需要实现每个命令的回调函数。从资源占用上看这个组件也很适合 MCU。它本身不动态申请内存运行时的核心数据结构都来自用户提供的缓冲区代码体积大概几 KBRAM 消耗主要看你的命令输出缓冲区大小。在 STM32F429 这种有 256KB SRAM、主频 180MHz 的芯片上跑它一点也不吃力。2. 移植前的资源盘点与关键概念2.1 硬件、工具链和源码准备在开始写代码之前最好先把手里的东西理清楚。硬件方面STM32F429 本身有多个串口选一个不用和调试器冲突的 UART 就行。比如常用 USART1 或者 UART4注意确认引脚是否被其他外设占用。硬件上只需要三根线TXD、RXD、GND接一个 USB-TTL 转换器到电脑再用串口助手或 MobaXterm 之类的工具连接即可。工具链方面Keil MDK 和 IAR 都可以你只要保证代码按 C99 标准编译就行。FreeRTOS 内核版本建议 10.x 以上现在绝大多数新工程也都是这个版本。FreeRTOS-Plus-CLI 可以从官方仓库下载也可以直接从某个官方示例工程里把 FreeRTOS_CLI.c 和 FreeRTOS_CLI.h 两个文件拷出来这两个文件就是组件的全部源码没有其他花哨依赖。需要注意一点这个组件通过 FreeRTOS 内核的 API 工作所以它必须放在已经能正常调度、能创建任务、能使用队列或信号量的工程里。如果你当前连一个最简单的 LED 任务都还没跑起来先不要着急移植 CLI否则出了问题很难判断是内核问题还是组件问题。2.2 FreeRTOS 内核要满足的前提条件FreeRTOS-Plus-CLI 对内核的依赖不算多但有几个前提最好提前检查。第一堆内存要够用。如果你用动态创建任务的方式CLI 任务需要一块 TCB 和任务栈如果你用静态创建也需要用户自己提供这些内存。官方组件本身不会额外申请内存主要是任务本身的开销。第二要使能任务通知。CLI 任务推荐用任务通知来唤醒configUSE_TASK_NOTIFICATIONS要保持默认的 1。第三如果你打算用系统自带的vTaskList和vTaskGetRunTimeStats必须在 FreeRTOSConfig.h 里打开两个宏configUSE_TRACE_FACILITY和configUSE_STATS_FORMATTING_FUNCTIONS。后面被格式化输出函数依赖漏了这些宏编译直接报错或者生成空输出。第四关注中断优先级。FreeRTOS 在 Cortex-M 系列上对中断优先级很敏感。串口中断里如果调用了vTaskNotifyGiveFromISR中断优先级数值必须在configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY允许的范围内否则会出现中断里调内核 API 导致系统崩溃的问题。对 STM32F429 来说一般把中断优先级设置成 5 或更高数值即可。2.3 FreeRTOS-Plus-CLI 的核心工作方式理解了这个组件的运行机制后面写代码会顺利很多。它本质上是一个“注册 分发 输出”的结构。命令先注册到一个内部命令表里。每个命令用一个结构体描述typedef struct CLI_Command_Definition_t { const char *pcCommand; const char *pcHelpString; BaseType_t xCommandHasParameters; pdCommandInterpreter_t pxCommandInterpreter; } CLI_Command_Definition_t;pcCommand是命令名称比如 task-stats。pcHelpString是帮助文本会被 help 命令拿来展示。xCommandHasParameters表示该命令是否需要接收参数。pxCommandInterpreter是真正的回调函数也就是命令执行函数。收到一行命令字符串后调用FreeRTOS_CLIProcessCommand来解析。这个函数会把输入的命令和命令表里的条目匹配匹配成功就调用对应的回调。回调的固定格式是static BaseType_t prvCommandHandler(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString);回调把自己要显示的内容写进pcWriteBuffer返回值pdFALSE表示输出完毕返回pdTRUE表示还有更多内容要输出CLI 会继续调用回调来填充剩余内容。这个“分页输出”机制是 CLI 组件一个非常容易踩坑的地方后面章节我会单独讲。3. 在 STM32F429 上一步步完成移植3.1 把 CLI 源码加进工程这一步没有什么技术含量但决定了后面排查问题的难度。我习惯在工程里单独建一个目录比如Middlewares/Third_Party/FreeRTOS-Plus/CLI把FreeRTOS_CLI.c和FreeRTOS_CLI.h放进去而不是跟其他 BSP 驱动文件混在一起。在 Keil 里把FreeRTOS_CLI.c添加到某个分组下然后在 C/C 编译器选项的 Include Paths 里加上这个目录。IAR 的操作类似。这里有个容易忽略的小地方FreeRTOS_CLI.c会包含FreeRTOS.h所以 include 路径里必须能找到 FreeRTOS 内核的头文件目录否则编译直接失败。测试编译前建议在FreeRTOS_CLI.h同级目录里再放一个FreeRTOS_CLI_IO.h之类的头文件用来声明你自己实现的字符收发接口吗其实不一定需要。最简单的做法是直接在 CLI 相关代码里调用你已有的串口发送函数只要在适当地方extern声明一下即可。我自己更倾向封装一层cli_puts(const char *s)里面调用串口发送这样后面想改成 DMA 或者加缓存都可以只改一个函数。3.2 串口收发层的设计与实现CLI 组件不帮你做串口收发这部分要自己实现。我的建议是发送用简单轮询接收用中断 环形缓冲。这样做的好处是中断里只做拷贝和唤醒不解析、不格式化逻辑简单也不容易出错。信号量、消息队列在 CLI 场景尽管可用但会让代码绕一圈性价比不高。任务通知就够用了。先定义一个环形缓冲接收和处理之间靠它解耦#define RX_RING_SIZE 512 static volatile uint8_t rx_ring[RX_RING_SIZE]; static volatile uint16_t rx_head 0; static volatile uint16_t rx_tail 0;串口中断里只管把新到的字节放进 ring同时在遇到\r或\n时通知 CLI 任务。这里用任务通知比每次发信号量更轻量void UART4_IRQHandler(void) { BaseType_t xHigherPriorityTaskWoken pdFALSE; uint32_t sr UART4-SR; if (sr USART_SR_RXNE) { uint8_t c (uint8_t)(UART4-DR 0xFF); uint16_t next (rx_head 1) % RX_RING_SIZE; if (next ! rx_tail) { rx_ring[rx_head] c; rx_head next; } if ((c \r) || (c \n)) { vTaskNotifyGiveFromISR(xCLITaskHandle, xHigherPriorityTaskWoken); } } portYIELD_FROM_ISR(xHigherPriorityTaskWoken); }这里有几个关键点rx_ring、rx_head、rx_tail加上 volatile是为了防止编译器优化掉对共享变量的重复读取。判断next ! rx_tail是为了防止环形缓冲溢出覆盖还没处理的数据。数据满了就丢弃新字节对终端输入来说能接受。遇到回车或换行就给任务发一次通知CLI 任务醒了之后再统一从 ring 里取数据这样即便一次传了多行也不怕丢。发送函数我建议用一个简单的字符循环void cli_puts(const char *s) { while (*s) { while (!(UART4-SR USART_SR_TXE)); UART4-DR (uint8_t)(*s); } }如果你对波特率有要求或者担心发送阻塞会影响其他任务可以改成 DMA 发送但 CLI 的输出量通常不大轮询发送在 115200 波特率下完全够用。3.3 CLI 任务创建、命令注册和输出分页在 main 函数创建一个 CLI 任务。推荐用静态创建方式因为 CLI 任务栈大小可以预先规划而且不依赖堆碎片#define CLI_TASK_STACK_SIZE 512 static StackType_t uxCLITaskStack[CLI_TASK_STACK_SIZE]; static StaticTask_t xCLITaskTCB; TaskHandle_t xCLITaskHandle;创建任务的代码xCLITaskHandle xTaskCreateStatic(vCLITask, CLI, CLI_TASK_STACK_SIZE, NULL, tskIDLE_PRIORITY 2, uxCLITaskStack, xCLITaskTCB);优先级我不建议设太高。CLI 是人在终端前手动敲命令实时性要求并不高。设成tskIDLE_PRIORITY 2的较低优先级就不会抢占真正的控制逻辑。任务主体负责三件事等待通知、从 ring 里取出一整行、调用解析器并输出结果static void vCLITask(void *pvParameters) { static char cLine[CLI_INPUT_LEN]; static char cOut[CLI_OUTPUT_LEN]; uint16_t usLineLen 0; (void)pvParameters; vRegisterCLICommands(); for (;;) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); while (rx_tail ! rx_head) { uint8_t c rx_ring[rx_tail]; rx_tail (rx_tail 1) % RX_RING_SIZE; if ((c \r) || (c \n)) { if (usLineLen 0) { cLine[usLineLen] \0; vCliExecute(cLine); usLineLen 0; } } else if ((c \b) || (c 0x7F)) { if (usLineLen 0) usLineLen--; } else if (usLineLen CLI_INPUT_LEN - 1) { cLine[usLineLen] c; } } } }这里没有把FreeRTOS_CLIProcessCommand直接放在等待循环里是因为中断里可能一次性唤醒多次我们把 ring 里的所有字节都处理完这样不容易漏命令。真正执行命令的函数要处理 CLI 输出“续传”机制。第一次调用传入命令字符串如果返回pdTRUE说明输出还没完后面要传NULL继续拿后续输出static void vCliExecute(char *pcCmd) { static char cOut[CLI_OUTPUT_LEN]; BaseType_t bMore pdTRUE; bMore FreeRTOS_CLIProcessCommand(pcCmd, cOut, sizeof(cOut)); cli_puts(cOut); while (bMore pdTRUE) { memset(cOut, 0, sizeof(cOut)); bMore FreeRTOS_CLIProcessCommand(NULL, cOut, sizeof(cOut)); cli_puts(cOut); } }cOut数组要放在这个函数内部用 static 申请避免在中断或任务栈上大量占用栈空间。CLI_OUTPUT_LEN我一般设成 512因为vTaskList这类系统命令打印一个表头加多个任务行256 字节往往不够。命令注册函数长这样static void vRegisterCLICommands(void) { FreeRTOS_CLIRegisterCommand(xCmdHelp); FreeRTOS_CLIRegisterCommand(xCmdTaskStats); FreeRTOS_CLIRegisterCommand(xCmdRunTimeStats); FreeRTOS_CLIRegisterCommand(xCmdUptime); }3.4 常用系统命令的实现命令定义都是类似的。拿查看任务状态的命令举例static BaseType_t prvCmdTaskStats(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) { (void)xWriteBufferLen; vTaskList(pcWriteBuffer); return pdFALSE; } static const CLI_Command_Definition_t xCmdTaskStats { task-stats, task-stats: show task state/list\n, 0, prvCmdTaskStats };这就能用task-stats命令打印出 FreeRTOS 所有任务的状态表了。再做一个uptime命令显示系统跑了多少 tick 和多少秒。FreeRTOS 内核里有一个全局变量xTickCount但更规范的做法是用xTaskGetTickCount()static BaseType_t prvCmdUptime(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) { TickType_t now xTaskGetTickCount(); snprintf(pcWriteBuffer, xWriteBufferLen, tick%lu, seconds%lu\n, (unsigned long)now, (unsigned long)(now / configTICK_RATE_HZ)); return pdFALSE; }help命令如果想列出所有命令最直接的方式是遍历自己的命令表。不过这里有个小麻烦CLI 组件内部会保存一份注册表但它没有提供遍历全部命令的公开接口。我一般会在自己的代码里维护一个命令指针数组然后在 help 里一个个打印static const CLI_Command_Definition_t *pxAllCommands[] { xCmdHelp, xCmdTaskStats, xCmdRunTimeStats, xCmdUptime, }; static BaseType_t prvCmdHelp(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) { size_t used 0; for (uint32_t i 0; i (sizeof(pxAllCommands) / sizeof(pxAllCommands[0])); i) { used snprintf(pcWriteBuffer used, xWriteBufferLen - used, %s, pxAllCommands[i]-pcHelpString); if (used xWriteBufferLen) break; } return pdFALSE; }这种写法虽然维护两处但胜在可控。命令多了之后help 内容对不对一目了然。3.5 打开运行时统计的配置与实现想看每个任务占用 CPU 的百分比就要用到vTaskGetRunTimeStats。这个函数依赖一个高分辨率时间基准推荐用 STM32F429 内核里的 DWT 周期计数器因为它不需要额外占用定时器代码也简单。在 FreeRTOSConfig.h 中增加或修改这些宏#define configGENERATE_RUN_TIME_STATS 1 #define configUSE_TRACE_FACILITY 1 #define configUSE_STATS_FORMATTING_FUNCTIONS 1 #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() prvConfigureRunTimeTimer() #define portGET_RUN_TIME_COUNTER_VALUE() prvGetRunTimeCounterValue()然后在某个 C 文件里实现这两个函数static void prvConfigureRunTimeTimer(void) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; } static uint32_t prvGetRunTimeCounterValue(void) { return DWT-CYCCNT; }DWT 计数器会跟着 CPU 时钟一直增长频率是 180MHz大约 23 秒翻转一次。对查看任务占用率这种场景来说足够了因为vTaskGetRunTimeStats内部会计算百分比它关心的是两次调用之间的计数值差只有两次差值超过 4294967295 才会有问题这在正常情况下不会发生。配置好之后新增命令static const CLI_Command_Definition_t xCmdRunTimeStats { run-time, run-time: show task CPU usage\n, 0, prvCmdRunTimeStats }; static BaseType_t prvCmdRunTimeStats(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) { (void)xWriteBufferLen; vTaskGetRunTimeStats(pcWriteBuffer); return pdFALSE; }到这里一个包含 help、task-stats、run-time、uptime 的基础命令行已经能用了。4. 用起来之后才发现的调试技巧4.1 任务状态表的正确读法task-stats命令输出的第一行是字段名第二行开始就是每个任务的信息。我最常看的是最后一列也就是栈剩余空间。很多莫名的死机其实就是某个任务栈溢出后把邻近内存踩坏了但如果一直没注意栈余量排查方向很容易跑到别处去。FreeRTOS 里每个状态用简写表示R 表示 Running当前正在运行。B 表示 Blocked阻塞中比如等信号量、等延时。S 表示 Suspended被挂起。D 表示 Deleted任务被删除但内核还没释放资源。调试卡死问题时重点看你想运行的那个任务是不是成了 S。如果任务莫名其妙停在 Suspended多半是有人对它调用了vTaskSuspend或者任务自己把自己挂起了。要注意vTaskList为了统计数据会短暂挂起调度器所以不要把这条命令做得太高频。人工调试时敲一下完全没问题。4.2 自定义业务命令时的高效姿势系统命令只是开胃菜真正好用的是把业务参数暴露给命令行。比如你的系统里有一个电机目标速度变量g_u16MotorSpeed想临时修改它来测试不同速度下的表现就可以做一个命令static BaseType_t prvCmdSetSpeed(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) { const char *pcParam; uint32_t xParamLen; unsigned long speed 0; pcParam FreeRTOS_CLIGetParameter(pcCommandString, 1, xParamLen); if (pcParam NULL) { snprintf(pcWriteBuffer, xWriteBufferLen, usage: set-speed 0-3000\n); return pdFALSE; } speed strtoul(pcParam, NULL, 10); if (speed 3000) { snprintf(pcWriteBuffer, xWriteBufferLen, invalid speed, max 3000\n); return pdFALSE; } g_u16MotorSpeed (uint16_t)speed; snprintf(pcWriteBuffer, xWriteBufferLen, speed set to %lu\n, speed); return pdFALSE; }这里有两个重要的坑。第一FreeRTOS_CLIGetParameter返回的指针是指向命令字符串内部的不是独立缓冲区。它只适合拿来读千万不要往里写。如果你要把参数保存起来尽量先把内容复制到自己的局部数组里。第二strtoul不是 FreeRTOS 专用函数但标准 C 库在 Keil 和 IAR 里都能正常使用只是会增加一点代码体积。如果你很在意编译尺寸也可以自己写个简单的十进制字符串转数值函数。业务命令修改的全局变量如果同时被其他任务访问必须有保护。最简单的方式是加临界区taskENTER_CRITICAL(); g_u16MotorSpeed (uint16_t)speed; taskEXIT_CRITICAL();如果数据更复杂建议用互斥信号量保护但临界区在修改单变量时开销最小不会导致调度延迟够用。4.3 怎么把命令行变成安全的调试后门命令行功能是越用越方便越方便就越容易失控。我见过有人把所有内部寄存器都暴露出去结果生产环境误操作把设备搞得不可运行。所以我的习惯是给命令分等级。调试代码里可以注册所有命令到了发布版本通过预编译宏把危险命令排除掉#if (CLI_ENABLE_WRITE_COMMANDS 1) FreeRTOS_CLIRegisterCommand(xCmdSetSpeed); #endif甚至更极端一点生产固件里连 CLI 任务都不创建。这个思路本质上就是“表面一样底层分环境编译”对维护不同版本非常友好。如果你必须在生产环境保留命令行建议至少做一层简单的口令校验。比如在 CLI 任务启动后要求输入特定字符串才开放写命令入口否则只允许 help、task-stats 这类只读命令。口令校验别做得太复杂不要引入加密库一个简单的字符串比较配合编译宏常量就够了因为它的作用是防止误操作不是防黑客。5. 高频问题与排查经验5.1 问题速查表下面这些是我在移植和使用 FreeRTOS-Plus-CLI 时遇到过的真实问题整理成表方便你对照排查。症状可能原因处理办法敲回车后完全没有输出CLI 任务没创建或串口发送路径不对先确认xCLITaskHandle不为 NULL再在 CLI 任务里加一条启动打印输入命令后提示 unknown command命令还没注册或注册表是在任务启动前已经初始化但没有调用注册函数在 CLI 任务开始处调用vRegisterCLICommands()并检查命令字符串大小写输出乱码波特率不匹配或串口时钟配置错误用逻辑分析仪/示波器看波形或先写一个纯串口回环测试排除问题命令能被识别但任务状态列表内容截断输出缓冲区太小把CLI_OUTPUT_LEN调到 512 或更大再试一次一执行 task-stats 就进 HardFault输出缓冲区不够或任务栈太小加大 CLI 任务栈到 512 字以上并调大cOut缓冲区运行时间统计输出全为 0portGET_RUN_TIME_COUNTER_VALUE没有正确替换检查宏名是否拼写正确确认portGET_RUN_TIME_COUNTER_VALUE()能拿到非零计数器值CLI 任务优先级太高导致业务任务抖动优先级设置不合理把 CLI 任务优先级降到tskIDLE_PRIORITY 2左右串口中断里调用通知后系统随机死机中断优先级超出 FreeRTOS 允许范围将串口中断优先级设置为大于等于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY的数值5.2 实际踩坑记录分页输出和栈资源第一次集成的时候我以为调用一次FreeRTOS_CLIProcessCommand就能输出所有内容结果执行run-time命令时只看到前面部分任务行后面的任务统计不见了。翻源码才发现 CLI 使用“调用一次、返回 pdTRUE 表示还有内容”的续传机制。如果没有 while 循环继续传 NULL长输出就会被截断。这个坑不看实现很难发现。第二个坑是栈资源。刚开始 CLI 任务栈我按常规任务给了 128 字跑 help 和简单命令都没问题但一执行task-stats就死机。定位过程很典型先看到系统进入 HardFault检查所有数组越界都没问题后来把 CLI 任务栈加到 512 字问题消失。原因就是vTaskList内部调用格式化函数对栈的需求超出普通任务。后来我把所有系统相关命令task-stats、run-time的pcWriteBuffer输出缓冲放到函数内部的 static 区任务栈压力进一步降低这样才稳妥。第三个坑是串口中断优先级。我最初把 UART4 中断优先级设置得比较高比如 2结果跑着跑着一进串口发送日志就死机而且现象不固定。后来想到 FreeRTOS 的configMAX_SYSCALL_INTERRUPT_PRIORITY限制把串口优先级降到了 5问题再没出现过。这一点在 Cortex-M4 上尤其重要因为中断优先级数字越小优先级越高而 FreeRTOS 只允许在特定优先级之上调用带 FromISR 后缀的 API。5.3 几个可以少走弯路的配置建议如果你现在正准备在自己的 STM32 工程里移植这个组件我建议一开始就把下面几件事做对能省很多事。串口接收 ring 缓冲区至少 256 字节。你永远不知道用户在终端里粘贴了一段多长的命令。CLI 任务栈不要小于 512 字。如果你后面还要加网络、文件系统相关命令栈要相应再加大。每次执行完命令把cOut缓冲区 memset 一下防止上次残留内容混入。帮助信息里统一带命令说明和用法不要只写命令名。不要在中断里注册命令。命令注册必须在普通任务上下文里完成。6. 最后分享一点我的配置倾向如果你问我个人项目里最习惯怎么搭这套东西我的答案是CLI 任务用低优先级串口发送用 DMA接收用中断加环形缓冲业务参数的写命令全部用互斥保护并且出厂时默认关闭写命令。这套组合在 STM32F429 上已经稳定跑了几个项目给我省下大量重复烧录的时间。选型时如果资源允许不要吝啬给 CLI 分配输出缓冲一个 1KB 的 static 缓冲区对 F429 来说根本不是事却能避免很多诡异的截断问题。后续如果你想扩展可以往命令行里加 flash 读写命令、日志导出命令、自定义协议触发命令把嵌入式设备的调试体验真正做成服务端开发那样顺手。
返回列表