elink_parse.c
e_linker_send() 与帧间节流4.2 接收路径 e_linker_recv_flag() / e_linker_recv() / e_linker_clear_buf()4.3 应答解析状态机 recv_wifi_log()4.4 指令执行 at_execute_command()4.5 与 uCOS-II 的配合5. 对外接口6. 依赖关系7. 备注与风险点源文件路径:
midware/ethernt/wireless/elink_parse.c所属模块:midware/ethernt/wireless(无线通讯棒驱动层 / AT 指令解析) 代码行数:251
1. 文件职责
本文件是无线通讯棒 AT 指令通道的"收发 + 应答解析"层,把 hardware/usart5 的裸字节流(环形缓冲 + DMA 发送)包装成两个语义清晰的接口:发送用 e_linker_send(),接收用 e_linker_recv()(透传式)与 recv_wifi_log()(应答式状态机)。
它处在"UART5 物理层"与"易联/自研通讯棒协议层(wifi/e_linker_wifi.c)"之间,是后者所有 AT 交互(登录、读 SN/HW/SW/信号、设置 AP/密码、读 P1 数据)的唯一执行通道。
对外最主要的接口是 at_execute_command():发一条 AT 指令,阻塞等待应答,用 strstr 判断 "OK" / "Login Error",返回 TRUE/FALSE。调用者据此决定重试或放弃。
本文件不含任何业务状态,也不直接操作无线运行状态表;无线侧的型号、信号、P1 数据等语义全部由上层 wireless_driver.c / e_linker_wifi.c 解释。
2. 关键数据结构与宏
| 名称 | 类型 | 说明 |
|---|---|---|
AT_STATE_E | 枚举 | 应答解析状态机状态:AT_RECV_HEAD(0)、AT_SHAKE_HANDS、READ_DATE、DATA_LF_MSG、MSG_DATA(定义在 elink_parse.h,AT_SHAKE_HANDS 本文件未使用) |
AT_COMM_E | 枚举 | 解析结果码:AT_RECV_OK(0)、AT_RECV_ERROE、AT_RECV_Y、AT_TIMOUT(本文件只用到前两个) |
SEND_AT_CODE_TIMEOUT | 宏 | 100 —— 发 AT 指令时的 DMA 完成超时,单位 ms(值定义同注释 elink_parse.h:26) |
RECV_AT_CODE_TIMEOUT | 宏 | 1500 —— 状态机内单次接收的最长等待,单位 ms |
AT_OK | 宏 | "OK",成功应答关键字 |
AT_ERR | 宏 | "Login Error",失败应答关键字 |
E_LINKER_COMM_DEBUG | 宏 | e_Linker_wifi.h:8,默认 0;本文件第 117 行用它门控超时打印 |
E_LINKER_4G_COMM_DEBUG | 宏 | 本文件第 230 行使用,但全工程无定义(见第 7 节) |
g_t_uart5_rcv_sem | 全局信号量指针 | 定义在 user/main.c:58、OSTimeGet 同族的 OS_EVENT *;由 UART5 空闲中断 OSSemPost,本文件 OSSemPend |
USART5_BUFFER_LEN | 宏 | 3072(hardware/usart5/usart5.h),UART5 接收环形缓冲区大小 |
本文件无自定义结构体/联合体,所有数据结构均由 hardware/usart5 与上层持有。
3. 函数清单
| 函数名 | 作用域 | 说明(一句话) |
|---|---|---|
u8 e_linker_send(u8 *buf, u32 len, u16 timeout) | 全局 | 带 100ms 帧间节流的发送,实际调用 usart5_send_data(),1 成功 / 0 超时 |
u8 e_linker_recv_flag(void) | 全局 | 查询 UART5 环形缓冲是否有待读数据 |
u32 e_linker_recv(u8 *buf, u32 len) | 全局 | 从环形缓冲最多取 len 字节,返回实际取出长度 |
void e_linker_clear_buf(void) | 全局 | 清 UART5 环形缓冲并把接收信号量归零 |
static u16 recv_wifi_log(u8 *buf, u16 timeout) | static | AT 应答解析状态机,从环形缓冲按 \n/\r 分帧并识别 OK/ERROR |
u8 at_execute_command(u8 *cmd, u8 *pdata, u16 timeout) | 全局 | 发送一条 AT 指令并阻塞取回应答,判定 OK / 错误 |
4. 核心逻辑解析
4.1 发送路径 e_linker_send() 与帧间节流
static u32 s_u32_send_tick = 0;
if (abs(OSTimeGet() - s_u32_send_tick) < 100)
OSTimeDlyHMSM(0, 0, 0, 100 - (abs(OSTimeGet() - s_u32_send_tick)));
s_u32_send_tick = OSTimeGet();
return usart5_send_data(buf, len, timeout);
要点:
- 用
static变量记录"上次发送时刻",强制两条 AT 指令之间 ≥100ms。OSTimeDlyHMSM(0,0,0,N)只延时剩余毫秒数,把任务挂起让出 CPU(OSTimeGet在OS_TICKS_PER_SEC = 1000下为毫秒计数)。 - 该节流只覆盖"主动发 AT 指令"的路径;
wireless_driver.c走 TCP 透传的wireless_send()也经过这里,所以透传分包同样受 100ms 节奏约束(wireless_driver.h:75的注释即说明这一点:"WIFI 串口每个发送帧间隔至少 30ms,以确保通讯棒能把数据分开"——实际实现是 100ms)。 usart5_send_data()内部为 DMA1_CH7 搬运 +while(dma_flag_get(...) == RESET)忙等,超时返回 0。
4.2 接收路径 e_linker_recv_flag() / e_linker_recv() / e_linker_clear_buf()
e_linker_recv_flag()直接返回usart5_is_recv_async_data(),即判断环形缓冲读写指针是否不等。e_linker_recv()是非阻塞拉取:循环len次,每次若usart5_is_recv_async_data()为真则usart5_read_data_async()取一字节,否则break,返回实际取到的字节数。用于 TCP 透传数据的搬运(net/server.c:6353/6381)。e_linker_clear_buf()调usart5_clear_buffer(),它会OSSemSet(g_t_uart5_rcv_sem, 0, &err)并把环形缓冲读写指针清零、缓冲区memset清 0。
底层环形缓冲由 hardware/usart5/usart5.c 维护:中断里 usart5_add_data() 写、usart5_read_data_async() 读,USART5_IRQHandler 在检测到 IDLE 空闲中断时 OSSemPost(g_t_uart5_rcv_sem)(usart5.c:99),即"一帧接收结束"是信号量的触发条件。
4.3 应答解析状态机 recv_wifi_log()
这是本文件的核心。入口先阻塞等待应答信号量:
OSSemPend(g_t_uart5_rcv_sem, timeout, &err);
if (err == OS_ERR_NONE) { ... }
return AT_RECV_ERROE;
timeout 由 at_execute_command() 的调用方给出;e_linker_wifi.c 绝大多数传 RECV_AT_CODE_TIMEOUT(1500),信号强度查询传 SENCOND_3S(3000)。
进入循环后有两重时间保护:
tick_start_temp = OSTimeGet();
while (1) {
if ((OSTimeGet() - tick_start_temp) >= RECV_AT_CODE_TIMEOUT) break; // 1500ms 内层上限
if (!usart5_is_recv_async_data()) { OSTimeDlyHMSM(0,0,0,50); continue; } // 无数据延时 50ms 再查
byte = usart5_read_data_async();
switch (state) { ... }
}
因此单次 recv_wifi_log() 的最坏耗时约为 timeout(信号量等待)+ 1500ms(内层循环),且内层无数据时以 50ms 为粒度让出 CPU。
状态迁移表
| 当前状态 | 收到字节 | 动作 | 迁移到 |
|---|---|---|---|
AT_RECV_HEAD | '\n' | — | READ_DATE |
AT_RECV_HEAD | 其他 | — | 不变 |
READ_DATE | '\r' | — | DATA_LF_MSG |
READ_DATE | 其他 | buf[idex++] = byte | 不变 |
DATA_LF_MSG | '\n' | 先迁移;随后比较 buf 与 "ERROR" / "OK" | MSG_DATA(或直接返回) |
DATA_LF_MSG | 其他 | — | 不变 |
MSG_DATA | 'O' | e_linker_clear_buf() | 返回 AT_RECV_OK |
MSG_DATA | 'E' | e_linker_clear_buf() | 返回 AT_RECV_ERROE |
MSG_DATA | 0x00 | e_linker_clear_buf() | 返回 AT_RECV_OK |
也就是说,解析器按行分帧:\n 进入内容行,\r 结束内容行,紧跟的 \n 触发关键字比对。这与易联棒的 AT 应答格式一致(例如 AT+SN?\r\n → \r\n<SN>\r\nOK\r\n)。
DATA_LF_MSG 分支里的两处提前返回是本状态机的"简写路径":如果整行内容恰好就是 "OK" 或 "ERROR",直接判定结果,不再依赖后面 MSG_DATA 单字符判断。
超时/异常出口
- 内层 1500ms 超时跳出
while(1)后:if (idex == 0)才清缓冲并返回AT_RECV_ERROE(elink_parse.c:201~206`)。 - 若
idex != 0(收到半行就超时),函数不清缓冲,直接落到函数末尾的return AT_RECV_ERROE(elink_parse.c:209),残留数据留给下一次交互的e_linker_clear_buf()或下一个字节处理。 - 若
OSSemPend超时(err != OS_ERR_NONE),跳过整个循环,同样从函数末尾返回AT_RECV_ERROE。
返回值类型为 u16,但实际只返回枚举值 0/1。
4.4 指令执行 at_execute_command()
e_linker_clear_buf(); // 清环形缓冲 + 信号量清零
OSSemSet(g_t_uart5_rcv_sem, 0, &err); // 再显式清一次信号量计数
e_linker_send(cmd, strlen(cmd), SEND_AT_CODE_TIMEOUT); // 100ms 发超时
state = recv_wifi_log(pdata, timeout); // 阻塞等应答
// 结果判定(以字符串内容优先,状态码兜底)
if (strstr(pdata, AT_ERR)) return FALSE; // 含 "Login Error"
else if (strstr(pdata, AT_OK)) return TRUE; // 含 "OK"
else if (state == AT_RECV_OK) return TRUE;
else return FALSE;
时序要点:
- 发前清场:
e_linker_clear_buf()已经OSSemSet(...,0,...),紧接着又调一次OSSemSet,属于冗余但无害的"双保险"(防止清缓冲期间中断又 Post 了一次信号量导致recv_wifi_log立即返回空数据)。 - 发送长度用
strlen(cmd),因此所有 AT 指令必须是 C 字符串并以\r\n结尾(elink_parse.h中的宏都已带\r\n,而AT_SET_*系列不带,由调用者sprintf("%s%s\r\n", ...)拼接)。 - 判定优先级:先查
pdata里有没有"Login Error",再查"OK"。"Login Error" 中含子串 "Error" 但不含 "OK";然而若某条应答同时包含两者(例如回显指令本身),会优先判失败。 strstr依赖pdata是 NUL 结尾的 C 字符串。本函数自身不写\0,靠调用者把缓冲区初始化为 0、以及recv_wifi_log按\r截断来保证。
4.5 与 uCOS-II 的配合
| 机制 | 使用方式 |
|---|---|
信号量 g_t_uart5_rcv_sem | UART5 IDLE 中断 OSSemPost,本文件 OSSemPend(recv_wifi_log)与 OSSemSet(...,0,...)(at_execute_command、间接触发于 usart5_clear_buffer) |
任务延时 OSTimeDlyHMSM | 发送节流的 100ms、状态机空转的 50ms |
| 阻塞时长 | 单个 AT 指令最坏占用 timeout + 1500ms;e_linker_wifi_init() 内部对同一指令重试 3 次,最坏可达数秒 |
| 调用任务 | task_meter(P1 抄表)、task_ethernet_monitor(状态巡检)、task_ethernet(透传发送);均非中断上下文 |
由于 recv_wifi_log() 内部用 OSTimeDlyHMSM 主动让出 CPU,等待期间不影响其它任务;但整条 AT 交互期间 UART5 被调用方以 u8_wireless_uart_flag 软锁保护(见 wireless_driver.c)。
5. 对外接口
本文件导出(供外部调用)
| 名称 | 调用方 |
|---|---|
e_linker_send() | wireless_driver.c:70(wireless_send) |
e_linker_recv_flag() | wireless_driver.c:81(wireless_recv_flag) |
e_linker_recv() | wireless_driver.c:93(wireless_recv) |
e_linker_clear_buf() | wireless_driver.c(经 wireless_recv/wireless_send 链路)、wifi/e_linker_wifi.c:194/491/499、间接触发于 wireless_driver.c 各状态判定 |
at_execute_command() | wifi/e_linker_wifi.c(共 24 处调用,覆盖登录、SN/HW/SW、AP SSID、目标 IP/端口、密码、协议支持位、P1 数据、信号强度等) |
本文件不导出全局变量(无任何非 static 全局定义)。
本文件调用的外部函数/变量
| 来源模块 | 具体项 |
|---|---|
hardware/usart5/usart5.c | usart5_send_data、usart5_read_data_async、usart5_is_recv_async_data、usart5_clear_buffer |
user/main.c | g_t_uart5_rcv_sem(信号量句柄) |
| uCOS-II | OSSemPend、OSSemSet、OSTimeGet、OSTimeDlyHMSM |
| libc | strcmp、strstr、strlen、abs(stdlib.h / math.h 经 chip.h 间接引入)、printf |
6. 依赖关系
依赖(include / 使用)
- 本文件 include:
elink_parse.h、usart5.h、main.h、<string.h> - 间接依赖:
hardware/usart5(环形缓冲、DMA、IDLE 中断信号量)、uCOS-II 内核
被依赖
midware/ethernt/wireless/wireless_driver.c—— 无线收发与 P1 抄表的底层通道midware/ethernt/wireless/wifi/e_linker_wifi.c—— 全部 AT 指令交互- 间接被
midware/ethernt/ethernet.c、net/server.c依赖(经wireless_*包装层)
elink_parse.h 只被上述三个文件 include(已用 grep 全工程确认)。
7. 备注与风险点
recv_wifi_log()对buf无长度保护(栈溢出风险)。READ_DATE分支的(*(buf+idex)) = byte; idex++;没有任何上限判断,idex为u16。实测最长调用场景是wireless_driver.c的l_u8_pdata[170](注释标称有效长度 162,余量 8 字节),而e_linker_wifi.c:477的e_linker_wifi_login()传入的缓冲区更小——e_linker_wifi.c:479只有u8 pdata[20] = {0};。只要通讯棒返回一条超长行(噪声、错波特率、固件回显整条指令),就会写越界。建议按传入长度加护栏。
E_LINKER_4G_COMM_DEBUG在全工程没有定义。elink_parse.c:230的#if (E_LINKER_4G_COMM_DEBUG == 1)中,未定义标识符在预处理期被当作 0,条件恒假,这段"打印指令与应答"的调试代码永远不会编译进来。e_linker_wifi.c:153/593有同样问题。若需现场调试 4G 指令流,需先补一个宏定义。
- 函数头注释与实现不一致。
elink_parse.c:212~219的注释写@Return :0 OK / 1 Error,但实现是TRUE(1) 表示成功、FALSE(0) 表示失败,语义正好相反。e_linker_send()的注释同样写1 成功 / 0 超时(这个与实现一致,但@Parameter把u32 len写成了u8 len`)。
- 超时路径不清缓冲。
idex != 0时函数直接返回AT_RECV_ERROE而不调e_linker_clear_buf()(elink_parse.c:207~209),半行残数据留在缓冲区。下一次at_execute_command()开头会清掉,但走wireless_recv()` 透传路径时这些残字节会被当成 TCP 数据交给上层协议解析。
DATA_LF_MSG的关键字比较依赖缓冲区已清零。strcmp((char *)buf, "ERROR")/strcmp((char *)buf, "OK")要求buf在idex处有 NUL;本函数没有写终止符,正确性完全交给调用方"缓冲初始化为 0 + 按\r截断"两条隐含约定。若调用方忘了初始化(如把未清空的静态缓冲传进来),这里会读到脏数据。
at_execute_command()的strstr匹配过宽。 只要应答任意位置出现"OK"子串即判成功,例如某个 SSID / SN 内容含 "OK" 也会被误判;同时"Login Error"检查优先于"OK",回显类应答容易互斥判错。
e_linker_send()的节流计时用int abs()处理无符号差值。OSTimeGet()返回INT32U,OSTimeGet() - s_u32_send_tick为无符号运算后再隐式转int。正常情况下(两次发送间隔小于 24.8 天)结果落在正区间,行为正确;但该写法在 tick 回绕或s_u32_send_tick被异常放大时会得到"负数转 int"的中间值,进而算出错误延时。同类写法在usart5_send_data()中也有(abs((int)(OSTimeGet() - l_32_systick_in)) > timeout)。
- 发送节流是"进程内"的,不跨任务同步。
s_u32_send_tick是普通static u32,task_meter与task_ethernet_monitor都可能调用;读-改-写非原子,理论上会出现两次发送间隔小于 100ms 的情况。
u8/u16混用的返回语义。recv_wifi_log()返回u16但只产出 0/1;at_execute_command()把结果压成u8的 TRUE/FALSE,AT_TIMOUT、AT_RECV_Y两个枚举值在这条链路上从未被产生,属于从旧WM6232PU驱动继承下来的冗余定义。