You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
198 lines
7.5 KiB
198 lines
7.5 KiB
|
3 days ago
|
/******************************************************************************
|
||
|
|
|
||
|
|
版权所有 (C), 2018-2099, Radkil
|
||
|
|
|
||
|
|
******************************************************************************
|
||
|
|
文 件 名 : ATParser.c
|
||
|
|
版 本 号 : 初稿
|
||
|
|
作 者 : Radkil
|
||
|
|
生成日期 : 2026年3月14日星期六
|
||
|
|
最近修改 :
|
||
|
|
功能描述 : AT命令解释器
|
||
|
|
|
||
|
|
修改历史 :
|
||
|
|
1.日 期 : 2026年3月14日星期六
|
||
|
|
作 者 : Radkil
|
||
|
|
修改内容 : 创建文件
|
||
|
|
|
||
|
|
******************************************************************************/
|
||
|
|
|
||
|
|
/*----------------------------------------------*
|
||
|
|
* 外部变量说明 *
|
||
|
|
*----------------------------------------------*/
|
||
|
|
|
||
|
|
/*----------------------------------------------*
|
||
|
|
* 外部函数原型说明 *
|
||
|
|
*----------------------------------------------*/
|
||
|
|
|
||
|
|
/*----------------------------------------------*
|
||
|
|
* 内部函数原型说明 *
|
||
|
|
*----------------------------------------------*/
|
||
|
|
|
||
|
|
/*----------------------------------------------*
|
||
|
|
* 全局变量 *
|
||
|
|
*----------------------------------------------*/
|
||
|
|
|
||
|
|
/*----------------------------------------------*
|
||
|
|
* 模块级变量 *
|
||
|
|
*----------------------------------------------*/
|
||
|
|
|
||
|
|
/*----------------------------------------------*
|
||
|
|
* 常量定义 *
|
||
|
|
*----------------------------------------------*/
|
||
|
|
|
||
|
|
/*----------------------------------------------*
|
||
|
|
* 宏定义 *
|
||
|
|
*----------------------------------------------*/
|
||
|
|
#include <stdio.h>
|
||
|
|
#include "ATParser.h"
|
||
|
|
|
||
|
|
// 内部辅助函数:检查字符串是否以某前缀开头
|
||
|
|
static bool starts_with(const char *str, const char *prefix) {
|
||
|
|
if (!str || !prefix) return false;
|
||
|
|
return strncmp(str, prefix, strlen(prefix)) == 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
void at_parser_init(at_parser_t *parser,
|
||
|
|
void (*send_cb)(const uint8_t*, uint16_t),
|
||
|
|
uint32_t (*tick_cb)(void)) {
|
||
|
|
memset(parser, 0, sizeof(at_parser_t));
|
||
|
|
parser->hw_send = send_cb;
|
||
|
|
parser->hw_get_tick = tick_cb;
|
||
|
|
parser->response_count = 0;
|
||
|
|
parser->last_status = AT_UNKNOWN;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool at_parser_register_response(at_parser_t *parser, const char *prefix, void (*cb)(const char*, int)) {
|
||
|
|
if (parser->response_count >= AT_MAX_RESPONSES) return false;
|
||
|
|
parser->responses[parser->response_count].prefix = prefix;
|
||
|
|
parser->responses[parser->response_count].callback = cb;
|
||
|
|
parser->response_count++;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void at_parser_feed(at_parser_t *p, uint8_t data) {
|
||
|
|
// 存入环形缓冲区
|
||
|
|
uint16_t next_head = (p->head + 1) % AT_RX_BUFFER_SIZE;
|
||
|
|
if (next_head != p->tail) { // 缓冲区未满
|
||
|
|
p->rx_buffer[p->head] = data;
|
||
|
|
p->head = next_head;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 简单的状态机解析行结束符 (\n)
|
||
|
|
// 注意:AT 命令通常以 \r\n 结尾,这里简化处理,遇到 \n 即认为一行结束
|
||
|
|
// 更严谨的做法是检测 \r\n 序列
|
||
|
|
if (data == '\n') {
|
||
|
|
if (p->cmd_len > 0 && p->cmd_len < AT_CMD_MAX_LEN) {
|
||
|
|
p->cmd_buffer[p->cmd_len] = '\0'; // 封口
|
||
|
|
|
||
|
|
// 1. 检查是否是标准响应 (OK / ERROR)
|
||
|
|
if (strcmp(p->cmd_buffer, "OK") == 0 || strcmp(p->cmd_buffer, "\rOK") == 0) {
|
||
|
|
if (p->is_waiting_response) {
|
||
|
|
p->last_status = AT_OK;
|
||
|
|
p->is_waiting_response = false;
|
||
|
|
}
|
||
|
|
} else if (strcmp(p->cmd_buffer, "ERROR") == 0 || strcmp(p->cmd_buffer, "\rERROR") == 0) {
|
||
|
|
if (p->is_waiting_response) {
|
||
|
|
p->last_status = AT_ERROR;
|
||
|
|
p->is_waiting_response = false;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// 2. 检查注册的自定义前缀 (URC 或 带参数响应)
|
||
|
|
for (int i = 0; i < p->response_count; i++) {
|
||
|
|
if (starts_with(p->cmd_buffer, p->responses[i].prefix)) {
|
||
|
|
if (p->responses[i].callback) {
|
||
|
|
// 去掉前缀部分,只传递参数部分给回调 (可选优化)
|
||
|
|
// 这里传递整行,用户回调内自行解析
|
||
|
|
p->responses[i].callback(p->cmd_buffer, strlen(p->cmd_buffer));
|
||
|
|
}
|
||
|
|
// 注意:如果是 URC (异步消息),不应停止等待标志,除非它是命令的直接响应
|
||
|
|
// 这里简化逻辑:如果是等待响应期间收到的非 OK/ERROR,通常视为中间数据
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 如果正在等待响应,且收到了非 OK/ERROR 的未知行,通常忽略或视为中间数据
|
||
|
|
// 某些模块会在 OK 之前返回多行数据
|
||
|
|
}
|
||
|
|
|
||
|
|
// 重置行缓冲
|
||
|
|
p->cmd_len = 0;
|
||
|
|
} else {
|
||
|
|
p->cmd_len = 0;
|
||
|
|
}
|
||
|
|
} else if (data != '\r') {
|
||
|
|
// 忽略 \r,只存有效字符,防止缓冲溢出
|
||
|
|
if (p->cmd_len < AT_CMD_MAX_LEN - 1) {
|
||
|
|
p->cmd_buffer[p->cmd_len++] = data;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
at_status_t at_parser_send_cmd(at_parser_t *p, const char *cmd, uint32_t timeout_ms) {
|
||
|
|
if (!p->hw_send) return AT_ERROR;
|
||
|
|
|
||
|
|
// 1. 发送命令 + \r\n
|
||
|
|
uint8_t tx_buf[AT_CMD_MAX_LEN + 4];
|
||
|
|
uint16_t len = strlen(cmd);
|
||
|
|
memcpy(tx_buf, cmd, len);
|
||
|
|
tx_buf[len++] = '\r';
|
||
|
|
tx_buf[len++] = '\n';
|
||
|
|
|
||
|
|
p->hw_send(tx_buf, len);
|
||
|
|
|
||
|
|
// 2. 设置等待状态
|
||
|
|
p->is_waiting_response = true;
|
||
|
|
p->last_status = AT_UNKNOWN;
|
||
|
|
p->timeout_ms = timeout_ms;
|
||
|
|
p->wait_start_time = p->hw_get_tick();
|
||
|
|
|
||
|
|
// 3. 阻塞等待 (或在外部轮询 process)
|
||
|
|
// 为了兼容性,这里使用简单的轮询等待,实际嵌入式系统中建议配合 RTOS 信号量
|
||
|
|
while (p->is_waiting_response) {
|
||
|
|
// 在等待期间,必须不断调用 feed 或让中断去 feed
|
||
|
|
// 这里假设 feed 是在中断中调用的,所以这里只需要检查超时
|
||
|
|
|
||
|
|
uint32_t current_tick = p->hw_get_tick();
|
||
|
|
// 处理时间回绕
|
||
|
|
uint32_t elapsed = (current_tick >= p->wait_start_time) ?
|
||
|
|
(current_tick - p->wait_start_time) :
|
||
|
|
(0xFFFFFFFF - p->wait_start_time + current_tick);
|
||
|
|
|
||
|
|
if (elapsed > timeout_ms) {
|
||
|
|
p->is_waiting_response = false;
|
||
|
|
p->last_status = AT_TIMEOUT;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 短暂延时,避免死循环占用 CPU (根据平台调整)
|
||
|
|
// 如果是 RTOS,这里应该是 osDelay(1);
|
||
|
|
// 如果是裸机,确保中断能打断此处
|
||
|
|
}
|
||
|
|
|
||
|
|
return p->last_status;
|
||
|
|
}
|
||
|
|
|
||
|
|
void at_parser_send_raw(at_parser_t *p, const uint8_t *data, uint16_t len) {
|
||
|
|
if (p->hw_send) {
|
||
|
|
p->hw_send(data, len);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void at_parser_process(at_parser_t *p) {
|
||
|
|
// 主要用于处理超时逻辑的非阻塞版本
|
||
|
|
// 如果使用了上面的阻塞式 send_cmd,此函数主要用于看门狗或额外逻辑
|
||
|
|
if (p->is_waiting_response) {
|
||
|
|
uint32_t current_tick = p->hw_get_tick();
|
||
|
|
uint32_t elapsed = (current_tick >= p->wait_start_time) ?
|
||
|
|
(current_tick - p->wait_start_time) :
|
||
|
|
(0xFFFFFFFF - p->wait_start_time + current_tick);
|
||
|
|
|
||
|
|
if (elapsed > p->timeout_ms) {
|
||
|
|
p->is_waiting_response = false;
|
||
|
|
p->last_status = AT_TIMEOUT;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|