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

elink_parse.c

源文件路径: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)staticAT 应答解析状态机,从环形缓冲按 \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);

要点:

4.2 接收路径 e_linker_recv_flag() / e_linker_recv() / e_linker_clear_buf()

底层环形缓冲由 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_DATA0x00e_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 单字符判断。

超时/异常出口

返回值类型为 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;

时序要点:

  1. 发前清场:e_linker_clear_buf() 已经 OSSemSet(...,0,...),紧接着又调一次 OSSemSet,属于冗余但无害的"双保险"(防止清缓冲期间中断又 Post 了一次信号量导致 recv_wifi_log 立即返回空数据)。
  2. 发送长度用 strlen(cmd),因此所有 AT 指令必须是 C 字符串并以 \r\n 结尾(elink_parse.h 中的宏都已带 \r\n,而 AT_SET_* 系列不带,由调用者 sprintf("%s%s\r\n", ...) 拼接)。
  3. 判定优先级:先查 pdata 里有没有 "Login Error",再查 "OK"。"Login Error" 中含子串 "Error" 但不含 "OK";然而若某条应答同时包含两者(例如回显指令本身),会优先判失败。
  4. strstr 依赖 pdata 是 NUL 结尾的 C 字符串。本函数自身不写 \0,靠调用者把缓冲区初始化为 0、以及 recv_wifi_log 按 \r 截断来保证。

4.5 与 uCOS-II 的配合

机制使用方式
信号量 g_t_uart5_rcv_semUART5 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.cusart5_send_data、usart5_read_data_async、usart5_is_recv_async_data、usart5_clear_buffer
user/main.cg_t_uart5_rcv_sem(信号量句柄)
uCOS-IIOSSemPend、OSSemSet、OSTimeGet、OSTimeDlyHMSM
libcstrcmp、strstr、strlen、abs(stdlib.h / math.h 经 chip.h 间接引入)、printf

6. 依赖关系

依赖(include / 使用)

被依赖

elink_parse.h 只被上述三个文件 include(已用 grep 全工程确认)。

7. 备注与风险点

  1. 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};。只要通讯棒返回一条超长行(噪声、错波特率、固件回显整条指令),就会写越界。建议按传入长度加护栏。
  1. E_LINKER_4G_COMM_DEBUG 在全工程没有定义。 elink_parse.c:230 的 #if (E_LINKER_4G_COMM_DEBUG == 1) 中,未定义标识符在预处理期被当作 0,条件恒假,这段"打印指令与应答"的调试代码永远不会编译进来。e_linker_wifi.c:153/593 有同样问题。若需现场调试 4G 指令流,需先补一个宏定义。
  1. 函数头注释与实现不一致。 elink_parse.c:212~219 的注释写 @Return :0 OK / 1 Error,但实现是 TRUE(1) 表示成功、FALSE(0) 表示失败,语义正好相反。e_linker_send() 的注释同样写 1 成功 / 0 超时(这个与实现一致,但 @Parameter 把 u32 len 写成了 u8 len`)。
  1. 超时路径不清缓冲。 idex != 0 时函数直接返回 AT_RECV_ERROE 而不调 e_linker_clear_buf()(elink_parse.c:207~209),半行残数据留在缓冲区。下一次 at_execute_command() 开头会清掉,但走 wireless_recv()` 透传路径时这些残字节会被当成 TCP 数据交给上层协议解析。
  1. DATA_LF_MSG 的关键字比较依赖缓冲区已清零。 strcmp((char *)buf, "ERROR") / strcmp((char *)buf, "OK") 要求 buf 在 idex 处有 NUL;本函数没有写终止符,正确性完全交给调用方"缓冲初始化为 0 + 按 \r 截断"两条隐含约定。若调用方忘了初始化(如把未清空的静态缓冲传进来),这里会读到脏数据。
  1. at_execute_command() 的 strstr 匹配过宽。 只要应答任意位置出现 "OK" 子串即判成功,例如某个 SSID / SN 内容含 "OK" 也会被误判;同时 "Login Error" 检查优先于 "OK",回显类应答容易互斥判错。
  1. 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)。
  1. 发送节流是"进程内"的,不跨任务同步。 s_u32_send_tick 是普通 static u32,task_meter 与 task_ethernet_monitor 都可能调用;读-改-写非原子,理论上会出现两次发送间隔小于 100ms 的情况。
  1. u8 / u16 混用的返回语义。 recv_wifi_log() 返回 u16 但只产出 0/1;at_execute_command() 把结果压成 u8 的 TRUE/FALSE,AT_TIMOUT、AT_RECV_Y 两个枚举值在这条链路上从未被产生,属于从旧 WM6232PU 驱动继承下来的冗余定义。