shell_usmart.c
usmart_nametab[] 注册内容(shell_usmart.c:14-23)usmart_dev 的初始化值(shell_usmart.c:34-45)3. 函数清单4. 核心逻辑解析4.1 编译期总开关4.2 命令解析与执行流程4.3 双重扫描路径与节拍4.4 全局变量的读写4.5 usmart_init(SystemCoreClock) 的参数类型5. 对外接口本文件导出本文件调用的外部函数/变量6. 依赖关系依赖被依赖7. 备注与风险点源文件路径:
midware/debug/shell_usmart.c所属模块:midware/debug(设备中间件 - 板级调试/产线测试) 代码行数:65
1. 文件职责
本文件是开源调试 shell 组件 USMART 在 EMS 工程里的配置与任务外壳。USMART 的通用实现(命令接收 usmart_cmd_rec、函数查表执行 usmart_exe、扫描 usmart_scan、字符串工具)放在 common/usmart/ 下,属于可复用的公共库;本文件的职责只有两件事:①提供 USMART 要求的两个"配置表"——函数注册表 usmart_nametab[] 和设备结构体实体 usmart_dev;②提供一个 uCOS-II 任务 task_usmart,周期性调用 usmart_scan() 去解析从 USART0 进来的命令行。
它处在最外层的"人工调试入口"位置:开发人员通过调试串口(USART0)敲入函数名和参数,USMART 解析后直接调用固件里的任意已注册函数,免去重新编译下载。它不参与任何业务数据流,是纯粹的在线调试通道。整个文件体被 #if DEBUG_SUMART(shell_usmart.c:6)包住,而 DEBUG_SUMART 在 shell_usmart.h:13 定义为 0,因此在当前固件里本文件编译后为空——任务不会被创建(user/main.c:50 与 user/main.c:321-331 的 #if DEBUG_SUMART 同样为假),调试 shell 处于关闭状态。
2. 关键数据结构与宏
| 名称 | 类型 | 说明 |
|---|---|---|
DEBUG_SUMART | 宏(shell_usmart.h:13,值 0) | USMART 总开关。为 0 时本文件全部内容、common/usmart/usmart.c 与 usmart_str.c 的函数体、user/main.c 中的任务栈与 OSTaskCreateExt 一起被条件编译剔除 |
usmart_nametab[] | struct _m_usmart_nametab 数组(shell_usmart.c:14-23) | USMART 的函数注册表。表项是 {void* func; const u8* name;}(common/usmart/usmart.h:39-43),name 字段存的是带返回值与参数类型的完整函数原型字符串,如 "u8 get_e_linker_sn(u8 *pdata)",USMART 靠解析这段字符串来确定参数个数、类型和显示方式 |
usmart_dev | struct _m_usmart_dev 全局实体(shell_usmart.c:32-46) | USMART 的运行时上下文,保存函数表指针、四个函数指针(init/cmd_rec/exe/scan)、函数数量、参数暂存区等 |
struct _m_usmart_nametab | 结构体(common/usmart/usmart.h:39-43) | { void* func; const u8* name; },一个表项 = 一个可被 shell 调用的函数 |
struct _m_usmart_dev | 结构体(common/usmart/usmart.h:45-62) | 14 个成员:funs、init、cmd_rec、exe、scan、fnum、pnum、id、sptype、parmtype、plentbl[MAX_PARM]、parm[PARM_LEN]、runtimeflag、runtime |
MAX_FNAME_LEN | 宏(common/usmart/usmart.h:17,值 30) | 函数名最大长度 |
MAX_PARM | 宏(common/usmart/usmart.h:18,值 10) | 最多 10 个参数,同时决定 plentbl[] 的长度 |
PARM_LEN | 宏(common/usmart/usmart.h:19,值 200) | 所有参数总长的上限,决定 parm[] 的长度 |
USMART_ENTIMX_SCAN | 宏(common/usmart/usmart.h:22,值 1) | =1 表示"用定时器中断来扫描",会影响 runtime 统计功能 |
USMART_USE_WRFUNS | 宏(common/usmart/usmart.h:26,值 0) | 读写任意地址的黑客函数(read_addr/write_addr)开关;当前为 0,故注册表中这两项被条件编译剔除 |
SP_TYPE_HEX / SP_TYPE_DEC | 宏(common/usmart/usmart.h:34-35,值 1/0) | 参数显示进制 |
usmart_nametab[] 注册内容(shell_usmart.c:14-23)
| 位置 | 条件 | 注册函数 | 原型字符串 |
|---|---|---|---|
shell_usmart.c:17 | #if USMART_USE_WRFUNS == 1(当前为 0,不生效) | read_addr | "u32 read_addr(u32 addr)" |
shell_usmart.c:18 | 同上 | write_addr | "void write_addr(u32 addr,u32 val)" |
shell_usmart.c:20 | 无条件 | usmart_test | "void usmart_test(void)" |
shell_usmart.c:21 | 无条件 | get_e_linker_sn | "u8 get_e_linker_sn(u8 *pdata)" |
shell_usmart.c:22 | 无条件 | e_linker_protocol_support_state | "u8 e_linker_protocol_support_state(void)" |
usmart_dev 的初始化值(shell_usmart.c:34-45)
| 字段 | 初值 | 注释原文含义 |
|---|---|---|
funs | usmart_nametab | 函数表指针 |
init | usmart_init | 初始化函数 |
cmd_rec | usmart_cmd_rec | 识别/解析函数 |
exe | usmart_exe | 执行函数 |
scan | usmart_scan | 扫描函数 |
fnum | sizeof(usmart_nametab)/sizeof(struct _m_usmart_nametab) | 函数数量,自动随条件编译变化 |
pnum | 0 | 参数数量 |
id | 0 | 函数 ID |
sptype | 1 | 参数显示类型:1 = 16 进制 |
parmtype | 0 | 参数类型:0 = 数字,1 = 字符串 |
plentbl[MAX_PARM] | 0 | 每个参数长度暂存表 |
parm[PARM_LEN] | 0 | 参数缓冲区 |
runtimeflag | 未出现在初始化列表 | 隐式初始化为 0,即不统计函数执行时间 |
runtime | 未出现在初始化列表 | 隐式初始化为 0 |
3. 函数清单
| 函数名 | 作用域 | 说明 |
|---|---|---|
task_usmart | 全局(shell_usmart.c:54,声明在 shell_usmart.h:18) | USMART 通信任务:先 usmart_init(SystemCoreClock) 初始化定时器基准,然后死循环 usmart_scan() + OSTimeDlyHMSM(0,0,0,100) 延时 100 ms |
本文件只有这一个函数定义。usmart_nametab[] 与 usmart_dev 是数据定义,不是函数;usmart_init / usmart_cmd_rec / usmart_exe / usmart_scan / usmart_test 都定义在 common/usmart/usmart.c,本文件只取它们的地址填进配置表。
4. 核心逻辑解析
4.1 编译期总开关
shell_usmart.c:6 的 #if DEBUG_SUMART 一直作用到文件末尾的 #endif(shell_usmart.c:65),把注册表、设备实体、任务三部分全部包住。DEBUG_SUMART 的唯一定义在 shell_usmart.h:13(值 0)。因此:
- 本文件编译后不产生任何符号;
common/usmart/usmart.c:27与common/usmart/usmart_str.c:24的同名开关也为假,整个 USMART 库不产生代码;user/main.c:50的任务栈TASK_USMART_STK、user/main.c:321-331的OSTaskCreateExt(task_usmart, ...)与OSTaskNameSet(..., "task_usmart_parse", ...)也一并被剔除。
对使用者的意义:工程里保存着完整的在线调试 shell,但发行固件里它是关闭的。要启用需要把 shell_usmart.h:13 改为非 0 并重新编译。
4.2 命令解析与执行流程
本文件不实现解析,但它是解析链路的入口。当 task_usmart 调用 usmart_scan() 时(实现在 common/usmart/usmart.c:412),完整流程为:
usmart_scan()
├─ usart0_is_recv_async_data() ? ← 查 USART0 环形缓冲是否有新数据
│ 否 → return(本次无输入)
│ 是 ↓
├─ len = usart0_get_data_len(); len < 2 ? return; ← 太短的丢弃
├─ for (i < len) data[i] = usart0_read_data_async(); ← 取走本次全部字节到 data[50]
├─ sta = usmart_dev.cmd_rec(data); ← 解析:查表匹配函数名与参数
└─ sta == 0 ? usmart_dev.exe() ← 执行已注册函数
: usmart_sys_cmd_exe(data) ← 系统命令 help/list/id/hex/dec/runtime
关键点:
- 输入源固定为 USART0(
usmart.c:419/421/431),即debug.c里fputc()重定向的同一个口,因此"敲命令"和"看打印"共用一根串口。 - 匹配依据是
usmart_nametab[]的name字段(形如"u8 get_e_linker_sn(u8 *pdata)")。USMART 用字符串比较从 左括号之后 截出参数表,据此决定要读几个参数、按什么类型解析,所以注册表里的原型字符串必须与真实函数签名严格一致,否则参数个数和类型会解析错误。 data[50]是局部缓冲(usmart.c:415),usmart_scan()对len没有上限裁剪就直接for (i < len) data[i] = ...,一次超过 50 字节的输入会栈溢出。正常人工输入不会触发,但该缺陷客观存在。usmart_dev.sptype = 1(16 进制显示)在usmart_init()中被再次赋值(usmart.c:111)——common/usmart/usmart.c:110显示 "十进制显示进制" 的中文注释与实际赋值 1(十六进制)语义相反,注释是错的。
4.3 双重扫描路径与节拍
这是本文件最需要注意的时序特征。USMART_ENTIMX_SCAN 被设为 1(common/usmart/usmart.h:22),同时工程又建立了一个专门的扫描任务,于是同一个 usmart_scan() 有两条驱动路径:
| 路径 | 触发源 | 周期 | 代码位置 |
|---|---|---|---|
| A:定时器中断 | TIMER3 更新中断 → TIMER3_IRQHandler → usmart_dev.scan() | 约 100 ms | common/usmart/usmart.c:35-52(中断服务),定时器由 usmart.c:110 的 timer3_init(999, ...) 建立 |
| B:任务轮询 | task_usmart 死循环 → usmart_scan() | 100 ms(OSTimeDlyHMSM(0,0,0,100)) | shell_usmart.c:59-63 |
其中 A 路径的中断在 hardware/timer/timer3.c:30-48 的 timer3_init() 中通过 timer_interrupt_enable(TIMER3, TIMER_INT_UP) 使能,因此是真实生效的。两条路径共享同一份 usmart_dev 全局状态和同一个 USART0 环形缓冲,而没有任何互斥保护:
- ISR 里的
usmart_dev.scan()会调用usmart_dev.exe(),进而直接执行用户敲入的函数(例如get_e_linker_sn)。在中断上下文里执行任意业务函数是明确的风险点:这些函数可能阻塞、可能访问被任务持有的共享数据、可能调用printf(USART0 阻塞写)。 - 若任务侧正好在
usmart_scan()中途被 TIMER3 中断打断,ISR 再次进入usmart_scan(),会重复消费环形缓冲、覆盖usmart_dev.parm[]/plentbl[]/pnum/id等中间状态,导致本次命令被解析成错误的内容或被执行两次。
OSTaskCreateExt 侧的参数(user/main.c:321-331):优先级 TASK_USMART_PRIO = 22(UCOSII/uCOS-CONFIG/app_cfg.h:73,全工程最低优先级之一),栈 TASK_USMART_STK_SIZE = 512(app_cfg.h:100),选项 OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR | OS_TASK_OPT_SAVE_FP,任务名 "task_usmart_parse"。
4.4 全局变量的读写
| 变量 | 写者 | 读者 |
|---|---|---|
usmart_nametab[] | 编译期初始化,运行期只读 | usmart_cmd_rec()、usmart_exe()、系统命令 list |
usmart_dev.funs/fnum | 编译期初始化 | usmart_cmd_rec() 查表、usmart_exe() 按 id 取函数指针 |
usmart_dev.init/cmd_rec/exe/scan | 编译期初始化,运行期只读 | task_usmart 间接、TIMER3_IRQHandler(usmart.c:42 调 usmart_dev.scan()) |
usmart_dev.sptype | usmart_init() 运行时写(usmart.c:111);系统命令 hex/dec 也会改 | 参数显示逻辑 |
usmart_dev.pnum/id/parmtype/plentbl/parm | usmart_cmd_rec() 每次解析时写 | usmart_exe() 执行时读 |
usmart_dev.runtimeflag/runtime | 从未被赋值(保持 0) | usmart_get_runtime()(仅当开启 runtime 统计) |
runtimeflag/runtime 因为没有出现在 shell_usmart.c:34-45 的初始化列表里,由 C 语言的"部分初始化自动补零"规则置 0,功能上正确。
4.5 usmart_init(SystemCoreClock) 的参数类型
task_usmart 传入的是 CMSIS 的全局变量 SystemCoreClock(u32,GD32F407 上通常为 200000000),而 usmart_init 的形参是 u8 sysclk(common/usmart/usmart.h:69,实现 common/usmart/usmart.c:109),实参被静默截断为低 8 位。所幸 usmart_init 内部根本没用 sysclk,而是自己算分频:
void usmart_init(u8 sysclk) // usmart.c:109
{
timer3_init(999, (u32)rcu_clock_freq_get(CK_APB1)*4/10000-1); // usmart.c:110
usmart_dev.sptype = 1; // usmart.c:111
}
timer3_init(u16 arr, u16 psc)(hardware/timer/timer3.c:30)的两个形参也都是 u16:arr = 999,psc = APB1频率*4/10000-1。按注释(usmart.c:110)意图是 10 kHz 计数、100 ms 溢出一次,说明这里的乘除系数是与 RCU_TIMER_PSC_MUL4(timer3.c:35)配合的定点换算。但 psc 是 u16,最大 65535:若 APB1 频率较高使 freq*4/10000-1 > 65535,会被静默截断,定时周期随之取错;这是隐藏在类型里的定时基准风险。
5. 对外接口
本文件导出
| 名称 | 类型 | 供谁使用 |
|---|---|---|
task_usmart(void *p_arg) | 函数(声明 shell_usmart.h:18) | user/main.c:322 的 OSTaskCreateExt()(受 #if DEBUG_SUMART 保护) |
usmart_nametab[] | 全局数组(extern 声明在 common/usmart/usmart.h:65) | common/usmart/usmart.c 的 usmart_cmd_rec() / usmart_exe() |
usmart_dev | 全局结构体实体(extern 声明在 common/usmart/usmart.h:66) | common/usmart/usmart.c 全部函数、shell_usmart.c 自身初始化 |
注:
common/usmart/usmart.h:65-66的注释写的是"在 usmart_config.c 中定义",那是原版 STM32 例程的文件名;本工程把这两个实体的定义搬到了shell_usmart.c里。
本文件调用的外部函数/变量
| 来源模块 | 使用内容 |
|---|---|
common/usmart/usmart.h | 类型 struct _m_usmart_nametab / struct _m_usmart_dev,函数 usmart_init、usmart_cmd_rec、usmart_exe、usmart_scan、usmart_test |
midware/ethernt/wireless/wifi/e_linker_wifi.h | get_e_linker_sn()(e_linker_wifi.h:49)、e_linker_protocol_support_state()(e_linker_wifi.h:50) |
user/main.h | 任务上下文所需的公共声明 |
| uCOS-II | OSTimeDlyHMSM(0,0,0,100)(延时 100 ms) |
| CMSIS | SystemCoreClock |
common/usmart/usmart_str.h | 经 usmart.h 转包含,提供字符串处理原型 |
6. 依赖关系
依赖
usmart.h、shell_usmart.h、main.h、e_linker_wifi.h(shell_usmart.c:1-4,该文件为 GBK 编码,已转码阅读)。
被依赖
user/main.c:#if DEBUG_SUMART下创建task_usmart任务(user/main.c:321-331)common/usmart/usmart.c、common/usmart/usmart_str.c:通过extern引用usmart_nametab[]与usmart_devmidware/ethernt/wireless/wifi/e_linker_wifi.c:提供注册表中被调用的两个函数
7. 备注与风险点
- 当前固件中本文件为"空文件":
DEBUG_SUMART为 0,注册表、usmart_dev、task_usmart全部被条件编译剔除,任务不创建。修改shell_usmart.h:13时务必同步确认common/usmart/usmart.c:27、common/usmart/usmart_str.c:24、user/main.c:50/321的开关一致性,否则会出现"任务创建了但库没编译"的链接错误。 - 双重扫描路径 + 无互斥:
USMART_ENTIMX_SCAN = 1使TIMER3_IRQHandler(common/usmart/usmart.c:35-52)和task_usmart(shell_usmart.c:59-63)都以约 100 ms 的周期调用同一个usmart_scan(),共享usmart_dev全局状态与 USART0 环形缓冲,无信号量保护。任务侧扫描被定时器中断打断时,会重复消费串口数据、覆盖parm[]/plentbl[]/pnum/id,造成命令被错误解析或执行两次。 - 在中断上下文里执行任意业务函数:ISR 经
usmart_dev.exe()直接调用注册函数(当前含get_e_linker_sn),这些函数可能阻塞、访问被任务持有的资源,或经printf阻塞在 USART0 上,属于明确的设计风险。若启用本模块,建议把USMART_ENTIMX_SCAN改成 0 只保留任务路径。 usmart_scan()的局部缓冲data[50]无长度上限检查(common/usmart/usmart.c:415-431):一次输入超过 50 字节即栈溢出,而task_usmart的栈只有 512 项(app_cfg.h:100),溢出后果不可预测。usmart_init(SystemCoreClock)实参被 u8 截断(shell_usmart.c:58):SystemCoreClock是u32,形参是u8。虽然函数体未使用该参数(usmart.c:109-113),但接口类型不匹配属于隐患,编译器也可能给出精度丢失告警。timer3_init()的psc形参为u16而实参算式可超范围(common/usmart/usmart.c:110→hardware/timer/timer3.c:30):rcu_clock_freq_get(CK_APB1)*4/10000-1一旦超过 65535 会被静默截断,导致扫描中断周期偏离 100 ms。sptype的中文注释与实际语义相反:common/usmart/usmart.c:110附近及usmart.h:56的"0,10进制;1,16进制"描述没问题,但usmart_init内那一行注释写成"十进制显示进制"而赋的值是 1(16 进制),容易被后续维护者改错。- 注册表原型字符串必须与真实签名一致(
shell_usmart.c:20-22):get_e_linker_sn的字符串为"u8 get_e_linker_sn(u8 *pdata)",与e_linker_wifi.h:49的声明吻合;但usmart_test为"void usmart_test(void)",需注意这类字符串一旦与实际签名不符,参数解析会静默出错而不是报错。 parm[PARM_LEN]只写了单个0初始化(shell_usmart.c:44-45,注释写着"需要 MAX_PARM 个 0 初始化"):C 语言部分初始化会自动补零,功能正确,但注释与写法不一致,说明是从上游例程整段复制而来。- 原版注释指向已不存在的文件名:
common/usmart/usmart.h:65-66写着"在 usmart_config.c 中定义",实际定义在本文件shell_usmart.c,容易误导查找。 runtimeflag/runtime从未被赋值,函数执行时间统计功能实际不可用(需runtimeflag = 1且配合USMART_ENTIMX_SCAN = 1)。