usmart.h
源文件路径:
common/usmart/usmart.h所属模块:common / usmart(串口调试交互组件) 代码行数:80
1. 文件职责
本文件是 USMART 串口调试组件的公共接口头文件,定义了该组件的两个核心结构体(函数注册表项 _m_usmart_nametab、设备实例 _m_usmart_dev)、一整套行为开关宏(MAX_FNAME_LEN / MAX_PARM / PARM_LEN / USMART_ENTIMX_SCAN / USMART_USE_WRFUNS)、错误码与显示类型常量,以及 9 个对外函数的原型。它处在 EMS 固件调试基础设施的接口层:common/usmart/usmart.c、common/usmart/usmart_str.c、midware/debug/shell_usmart.c 三方都依赖它来共享同一份结构体布局,usmart_dev 与 usmart_nametab[] 这两个单例则由 midware/debug/shell_usmart.c 按本文件的布局实例化。文件本身没有可执行代码、不定义变量、不参与任务调度;它被 DEBUG_SUMART(定义在 midware/debug/shell_usmart.h:13,值为 0)间接决定是否真正参与编译。注意 struct 定义与宏常量的组合决定了「参数打包进 parm[]」这一 ABI,任何字段顺序或 MAX_PARM / PARM_LEN 的改动都会同时影响三个 .c 文件。
2. 关键数据结构与宏
| 名称 | 类型(结构体/枚举/宏/联合体) | 说明 |
|---|---|---|
__USMART_H | 宏(include guard) | 防重包含开关,usmart.h:9-10 |
MAX_FNAME_LEN | 宏 | usmart.h:17,值 30,函数名最大长度,注释要求「不小于最长函数名的长度」;用于 usmart.c 的 rfname / sfname / cmdname 栈数组 |
MAX_PARM | 宏 | usmart.h:18,值 10,单函数最大参数个数;注释明确「修改此参数,必须修改 usmart_exe 与之对应」——因为 usmart.c:350-390 是手写的 0~10 分支 switch |
PARM_LEN | 宏 | usmart.h:19,值 200,所有参数合并后的总字节上限;注释提醒串口接收部分要与之对应 |
USMART_ENTIMX_SCAN | 宏 | usmart.h:22,值 1,声明「使用 TIM 定时中断扫描 SCAN 函数」;注释强调 runtime 统计功能必须置 1 |
USMART_USE_WRFUNS | 宏 | usmart.h:26,值 0,读写任意地址功能开关;控制 usmart.c:461 是否编译 read_addr / write_addr,也控制 shell_usmart.c:15 是否把它们注册进函数表 |
USMART_OK | 宏 | usmart.h:29,值 0,无错误 |
USMART_FUNCERR | 宏 | usmart.h:30,值 1,函数错误(命令词无法识别 / 括号不配对) |
USMART_PARMERR | 宏 | usmart.h:31,值 2,参数错误(格式非法或实参比形参少) |
USMART_PARMOVER | 宏 | usmart.h:32,值 3,参数溢出(实参个数超过 MAX_PARM) |
USMART_NOFUNCFIND | 宏 | usmart.h:33,值 4,未在 usmart_nametab[] 中匹配到函数 |
SP_TYPE_DEC | 宏 | usmart.h:35,值 0,参数按十进制显示 |
SP_TYPE_HEX | 宏 | usmart.h:36,值 1,参数按十六进制显示 |
struct _m_usmart_nametab | 结构体 | usmart.h:39-43。字段:void* func(函数指针)、const u8* name(声明串,如 "void usmart_test(void)")。表项数组由 midware/debug/shell_usmart.c:14 定义 |
struct _m_usmart_dev | 结构体 | usmart.h:45-62。字段:funs(表指针)、init/cmd_rec/exe/scan 四个函数指针、fnum(函数数量)、pnum(本次调用的参数数量)、id(命中函数下标)、sptype(显示进制)、parmtype(u16 位图,bit i = 参数 i 是字符串)、plentbl[MAX_PARM](各参数长度前缀表)、parm[PARM_LEN](参数打包缓冲)、runtimeflag、runtime(0.1ms 为单位的耗时) |
usmart_nametab[] | 全局变量(extern 声明) | usmart.h:65,实际定义在 midware/debug/shell_usmart.c:14,本工程注册了 usmart_test、get_e_linker_sn、e_linker_protocol_support_state 三项 |
usmart_dev | 全局变量(extern 声明) | usmart.h:66,实际定义在 midware/debug/shell_usmart.c:32,USMART 单例 |
文件内无枚举、无联合体定义。
3. 函数清单
本文件是头文件,不含函数定义,仅含 9 个函数原型声明:
| 函数名 | 作用域(static/全局) | 说明(一句话) |
|---|---|---|
usmart_init | 全局(声明,usmart.h:69) | 初始化 USMART:配置 TIMER3 时基与默认显示进制,prototype 参数为系统时钟 |
usmart_cmd_rec | 全局(声明,usmart.h:70) | 识别串口字符串中的函数名与参数,写入 usmart_dev.id / pnum,返回 USMART_* 错误码 |
usmart_exe | 全局(声明,usmart.h:71) | 执行已识别的函数并回显返回值与执行耗时 |
usmart_scan | 全局(声明,usmart.h:72) | 从 UART0 取异步接收数据并驱动「识别 → 执行」流程 |
read_addr | 全局(声明,usmart.h:73) | 按绝对地址读 32 位值,默认因 USMART_USE_WRFUNS==0 不编译 |
write_addr | 全局(声明,usmart.h:74) | 按绝对地址写 32 位值,默认因 USMART_USE_WRFUNS==0 不编译 |
usmart_get_runtime | 全局(声明,usmart.h:75) | 返回 TIMER3 折算后的函数执行耗时(单位 0.1ms) |
usmart_reset_runtime | 全局(声明,usmart.h:76) | 把耗时计时归零并放宽计数周期 |
usmart_test | 全局(声明,usmart.h:77) | 调试自测函数,打印 TIMER3 的 CNT 与 ARR |
实现于 usmart.c 但本文件未声明的符号(头文件与实现不同步):
u8 usmart_sys_cmd_exe(u8 *str)(实现于common/usmart/usmart.c:137)——内置命令(help/list/id/hex/dec/runtime)的执行函数。u8 *sys_cmd_tab[](实现于common/usmart/usmart.c:121)——系统命令字符串表。void TIMER3_IRQHandler(void)(实现于common/usmart/usmart.c:35)——中断向量,由启动文件声明而非本头文件。
usmart_str.h 中的 11 个字符串工具函数由 usmart.h:15 的 #include "usmart_str.h" 间接引入。
4. 核心逻辑解析
本文件无过程代码,其「逻辑」体现在结构体布局与宏共同约定的参数打包 ABI上,这是理解 usmart.c / usmart_str.c 的前提。
4.1 参数打包 ABI
struct _m_usmart_dev 用两个平行数组描述一次函数调用的实参:
parmtype(u16位图,usmart.h:57):bit i 为 1 ⇒ 第 i 个参数是字符串,为 0 ⇒ 是数字。注意是u16而不是u32,因此最多只能安全覆盖 16 位;配合MAX_PARM = 10(usmart.h:18)尚有余量,但usmart_str.c:435的usmart_dev.parmtype &= ~(1<<n)与usmart_str.c:444的|= 1<<n一旦n > 15就会越界移位的语义。plentbl[MAX_PARM](usmart.h:58):plentbl[i]是第 i 个参数占用的字节数——数字固定 4(usmart_str.c:436),字符串为strlen+1(usmart_str.c:445)。由于变长,第 i 个参数的起始偏移必须用前缀和计算,由usmart_get_parmpos()(usmart_str.c:392,声明在usmart_str.h:19)完成。parm[PARM_LEN](usmart.h:59):所有参数按序紧凑存放的原始缓冲,总容量 200 字节。数字以小端u32写入(usmart_str.c:434的*(u32*)(usmart_dev.parm + pos) = res),字符串直接usmart_strcopy(usmart_str.c:443)。
这套布局有一个隐含约束:parmtype 的位宽(16)必须 ≥ MAX_PARM,且 plentbl 的元素数必须等于 MAX_PARM。usmart.h:18 的注释已经提醒「修改此参数必须修改 usmart_exe」,但没提 parmtype 的位宽限制。
4.2 状态变量的语义与生命周期
usmart_dev 是一份跨调用累积的状态,不是每帧重置:
fnum(usmart.h:54):只由shell_usmart.c:39的静态初始化设定一次,等于usmart_nametab[]的元素个数,全程只读。id(usmart.h:55):由usmart_cmd_rec在匹配成功时写入(usmart.c:298),被usmart_exe读取(usmart.c:324)并做id >= fnum的防御检查(usmart.c:325)。pnum(usmart.h:54下方,usmart.c:307写入):注意usmart_cmd_rec里解析出的用户实参个数被存进它,而usmart_exe用它做switch分派。因此「实参个数 < 形参个数」是合法的(usmart.c:296只拦spnum > rpnum),缺的参数读到的将是parm[]中的陈旧数据。runtimeflag/runtime(usmart.h:60-61):由runtime命令与usmart_exe协同维护,runtime的单位约定为 0.1ms,前提是 TIMER3 计数频率被usmart_init固定成 10kHz(usmart.c:111注释)。
4.3 宏开关的联动关系
| 宏 | 影响面 |
|---|---|
USMART_ENTIMX_SCAN = 1 | 声明「由 TIM 中断扫描」。在 usmart.c 里并没有引用该宏,中断入口是硬编码的 TIMER3_IRQHandler(usmart.c:35),所以置 0 也无法真正关掉中断扫描,只会关掉这份声明语义 |
USMART_USE_WRFUNS = 0 | 同时控制 usmart.c:461(read_addr/write_addr 的定义)与 shell_usmart.c:15(函数表注册)。两处都依赖同一个宏,开关一致 |
PARM_LEN = 200 | 决定 parm[] 容量,也决定 usmart_str.c:413 的 u8 tstr[PARM_LEN+1] 局部缓冲大小。而 usmart_scan 从 UART0 取数据用的却是固定 50 字节数组(usmart.c:415),两者不联动 |
MAX_FNAME_LEN = 30 | 决定 usmart.c:140/285/286/322 四处栈数组大小,也是 usmart_get_cmdname(..., MAX_FNAME_LEN) 的截断阈值 |
4.4 与任务/调度的配合
本文件本身不创建任何 uCOS-II 对象,但它声明了 4 个函数指针槽位(init / cmd_rec / exe / scan,usmart.h:49-52),使 usmart_dev 成为一个「接口表」。shell_usmart.c:32-52 用它把 usmart_init / usmart_cmd_rec / usmart_exe / usmart_scan 绑成虚函数表;实际执行流中只有 usmart_dev.scan() 是被间接调用的(usmart.c:43 与 shell_usmart.c:task_usmart),cmd_rec 与 exe 则分别在 usmart.c:431、usmart.c:432 通过函数指针调用。这层间接调用在 uCOS-II 任务上下文中增加了少量开销,但对调试组件而言可接受。
5. 对外接口
导出(供外部调用)
| 符号 | 类型 | 使用者 |
|---|---|---|
MAX_FNAME_LEN / MAX_PARM / PARM_LEN / USMART_ENTIMX_SCAN / USMART_USE_WRFUNS | 宏 | common/usmart/usmart.c、common/usmart/usmart_str.c、midware/debug/shell_usmart.c |
USMART_OK / USMART_FUNCERR / USMART_PARMERR / USMART_PARMOVER / USMART_NOFUNCFIND | 宏 | usmart.c(usmart.c:147,205,216,233,243,262,265,270,288,297,303,306,419,433,436,443-454 等)、usmart_str.c(usmart_str.c:259,269,419,433,438,448,451) |
SP_TYPE_DEC / SP_TYPE_HEX | 宏 | usmart.c(usmart.c:210,238,340,343,394,397) |
struct _m_usmart_nametab | 结构体类型 | midware/debug/shell_usmart.c:14 定义表数组 |
struct _m_usmart_dev | 结构体类型 | midware/debug/shell_usmart.c:32 定义 usmart_dev 实例 |
usmart_nametab[] | 全局变量(extern) | usmart.c 通过 usmart_dev.funs 间接访问;shell_usmart.c:14 为唯一定义处 |
usmart_dev | 全局变量(extern) | usmart.c(大量)、usmart_str.c(usmart_str.c:397,414,434-445) |
usmart_init / usmart_cmd_rec / usmart_exe / usmart_scan / read_addr / write_addr / usmart_get_runtime / usmart_reset_runtime / usmart_test | 函数原型 | common/usmart/usmart.c 实现;midware/debug/shell_usmart.c 绑定/注册;common/usmart/usmart_str.c 调用 usmart_get_runtime 之外无直接引用 |
usmart_str.h 中 11 个字符串工具 | 函数原型 | 经 usmart.h:15 的 #include 传递 |
被本文件调用(外部依赖)
本文件唯一的外部依赖是 usmart_str.h(usmart.h:15 的 #include)。结构体与原型使用的 u8 / u16 / u32 类型由调用方通过 usmart_str.h → gd32f4xx.h / chip.h(usmart_str.h:14-15)间接获得。
6. 依赖关系
- 依赖:
common/usmart/usmart_str.h(进而包含gd32f4xx.h、chip.h提供u8/u16/u32)。 - 被依赖:
common/usmart/usmart.c(实现 9 个原型中的大部分,操作usmart_dev);common/usmart/usmart_str.c(使用MAX_PARM、PARM_LEN、USMART_*错误码、usmart_dev);midware/debug/shell_usmart.c(按本文件布局定义usmart_nametab[]与usmart_dev,并把DEBUG_SUMART提供给usmart.c/usmart_str.c作为整体开关)。
7. 备注与风险点
- 注释与实际定义位置不符:
usmart.h:65-66的注释写「在 usmart_config.c 里面定义」,但本工程中这两个符号实际定义在midware/debug/shell_usmart.c:14/shell_usmart.c:32,没有usmart_config.c这个文件。移植时留下的错误注释。 usmart.h未声明usmart_sys_cmd_exe与sys_cmd_tab[]:二者在common/usmart/usmart.c:121/usmart.c:137定义且未加static,属「有实现无契约」的导出符号,存在与其他模块重名冲突的风险。parmtype位宽与MAX_PARM未做静态断言:parmtype是u16(usmart.h:57),代码用1<<n置位(usmart_str.c:444)。若将来把MAX_PARM提高到 16 以上,移位会溢出且无编译期告警。MAX_PARM的上限被usmart_exe硬编码:usmart.h:18的注释已承认这一点——usmart.c:350-390是手写的 0~10 分支switch,改MAX_PARM必须同步改usmart_exe,却没有任何机制强制两者一致;若只改宏,pnum == 10的调用仍可工作但 11 个参数会静默不执行(switch无default)。PARM_LEN与实际收包缓冲不联动:注释(usmart.h:19)要求「串口接收部分要与之对应(不小于 PARM_LEN)」,但收包侧usmart.c:415用的是固定u8 data[50],远小于 200,注释给出的约束并未被满足。USMART_ENTIMX_SCAN是失效开关:usmart.c里没有任何地方引用它,扫描入口被硬编码为TIMER3_IRQHandler(usmart.c:35)。把该宏置 0 不会关闭中断扫描,只会与注释产生矛盾。read_addr/write_addr的原型无条件暴露:usmart.h:73-74始终声明这两个危险函数,即便USMART_USE_WRFUNS == 0时不编译实现。某些编译器对「有声明无定义但无人引用」不会报错,但若被其他模块误引用会在链接期失败。该功能一旦开启即可任意改写寄存器/内存,属高危调试后门,务必保持关闭状态。- 结构体字段无填充对齐说明:
struct _m_usmart_dev是全局单例,按本文件字段顺序静态初始化(shell_usmart.c:33-52),字段顺序或新增字段会静默改变初始化值的对应关系,且不会有类型检查帮忙。 - 其余:暂无。