EMS 知识库
首页 / knowledge/代码解析/midware/debug

shell_usmart.c

源文件路径: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_devstruct _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)

字段初值注释原文含义
funsusmart_nametab函数表指针
initusmart_init初始化函数
cmd_recusmart_cmd_rec识别/解析函数
exeusmart_exe执行函数
scanusmart_scan扫描函数
fnumsizeof(usmart_nametab)/sizeof(struct _m_usmart_nametab)函数数量,自动随条件编译变化
pnum0参数数量
id0函数 ID
sptype1参数显示类型:1 = 16 进制
parmtype0参数类型: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)。因此:

对使用者的意义:工程里保存着完整的在线调试 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

关键点:

4.3 双重扫描路径与节拍

这是本文件最需要注意的时序特征。USMART_ENTIMX_SCAN 被设为 1(common/usmart/usmart.h:22),同时工程又建立了一个专门的扫描任务,于是同一个 usmart_scan() 有两条驱动路径:

路径触发源周期代码位置
A:定时器中断TIMER3 更新中断 → TIMER3_IRQHandler → usmart_dev.scan()约 100 mscommon/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 环形缓冲,而没有任何互斥保护:

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.sptypeusmart_init() 运行时写(usmart.c:111);系统命令 hex/dec 也会改参数显示逻辑
usmart_dev.pnum/id/parmtype/plentbl/parmusmart_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.hget_e_linker_sn()(e_linker_wifi.h:49)、e_linker_protocol_support_state()(e_linker_wifi.h:50)
user/main.h任务上下文所需的公共声明
uCOS-IIOSTimeDlyHMSM(0,0,0,100)(延时 100 ms)
CMSISSystemCoreClock
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 编码,已转码阅读)。

被依赖

7. 备注与风险点

  1. 当前固件中本文件为"空文件":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 的开关一致性,否则会出现"任务创建了但库没编译"的链接错误。
  2. 双重扫描路径 + 无互斥: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,造成命令被错误解析或执行两次。
  3. 在中断上下文里执行任意业务函数:ISR 经 usmart_dev.exe() 直接调用注册函数(当前含 get_e_linker_sn),这些函数可能阻塞、访问被任务持有的资源,或经 printf 阻塞在 USART0 上,属于明确的设计风险。若启用本模块,建议把 USMART_ENTIMX_SCAN 改成 0 只保留任务路径。
  4. usmart_scan() 的局部缓冲 data[50] 无长度上限检查(common/usmart/usmart.c:415-431):一次输入超过 50 字节即栈溢出,而 task_usmart 的栈只有 512 项(app_cfg.h:100),溢出后果不可预测。
  5. usmart_init(SystemCoreClock) 实参被 u8 截断(shell_usmart.c:58):SystemCoreClock 是 u32,形参是 u8。虽然函数体未使用该参数(usmart.c:109-113),但接口类型不匹配属于隐患,编译器也可能给出精度丢失告警。
  6. 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。
  7. sptype 的中文注释与实际语义相反:common/usmart/usmart.c:110 附近及 usmart.h:56 的"0,10进制;1,16进制"描述没问题,但 usmart_init 内那一行注释写成"十进制显示进制"而赋的值是 1(16 进制),容易被后续维护者改错。
  8. 注册表原型字符串必须与真实签名一致(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)",需注意这类字符串一旦与实际签名不符,参数解析会静默出错而不是报错。
  9. parm[PARM_LEN] 只写了单个 0 初始化(shell_usmart.c:44-45,注释写着"需要 MAX_PARM 个 0 初始化"):C 语言部分初始化会自动补零,功能正确,但注释与写法不一致,说明是从上游例程整段复制而来。
  10. 原版注释指向已不存在的文件名:common/usmart/usmart.h:65-66 写着"在 usmart_config.c 中定义",实际定义在本文件 shell_usmart.c,容易误导查找。
  11. runtimeflag / runtime 从未被赋值,函数执行时间统计功能实际不可用(需 runtimeflag = 1 且配合 USMART_ENTIMX_SCAN = 1)。