之前使用一个定时器作为多个串口的超时计数器,那么如果有好几种协议,能不能都可以解析出来,并且方便扩展呢,参考AI工具进行了相应改动
使用解析器模式
解析器.h文件
c
#ifndef PARSER_MANAGER_H
#define PARSER_MANAGER_H
#include "parser.h"
/* parser_manager: 管理已注册 parser 的集合,并将收到的字节分发 (dispatch)
* 给各个 parser。此模块尽量保持简单轻量,具体策略可根据项目需求增强:
* - 是否按 priority 排序 dispatch
* - 是否在 parser 返回 IN_PROGRESS 时把后续字节专属投递给该 parser(独占模式)
*
* 注意:本示例的 manager 以注册顺序分发字节;当某 parser 返回 PARSER_DONE 时
* 表示该 parser 已完整消费该帧并触发回调(manager 会停止对当前字节的进一步分发)。
*/
/* 最大可注册解析器数量:对资源受限的嵌入式系统设置一个上界 */
#define PARSER_MANAGER_MAX 8
/* 初始化管理器(清空已注册的解析器列表) */
void parser_manager_init(void);
/* 注册 / 注销解析器
* - parser_manager_register: 将 parser 加入到 manager;会调用 parser->init
* - parser_manager_unregister: 将 parser 从列表移除
* 返回值:0 表示成功,负值表示失败
*/
int parser_manager_register(parser_t *p);
int parser_manager_unregister(parser_t *p);
/* 单字节/多字节入口
* - parser_manager_feed: 把一个字节分派给所有已注册解析器(直到被解析完成)
* - parser_manager_feed_buf: 便利的块输入函数,会按字节调用 feed
* 返回:当前实现总是返回 0(可扩展为错误/未处理字节统计)
*/
int parser_manager_feed(uint8_t b);
int parser_manager_feed_buf(const uint8_t *buf, size_t len);
/* Notify all registered parsers that a silence/timeout event occurred.
* Useful for protocols like Modbus RTU which use an inter-frame silence (T3.5)
* to indicate end of frame.
*/
int parser_manager_on_timeout(const uint8_t *buf, size_t len);
#endif /* PARSER_MANAGER_H */
.c文件
c
#include "parser_manager.h"
#include "custom_parser.h"
#include <string.h>
#include <stdio.h>
#include "custom_parser.h"
/* 内部解析器列表与计数器
* g_parsers: 存储已注册解析器的指针数组;固定上限 PARSER_MANAGER_MAX
* g_count: 当前已注册解析器数量
*
* 线程安全/并发:当前简单实现没有做互斥保护(适用于单线程或由外部保证同步)。
* 在多任务/并发环境下,请在注册/注销/喂字节时加锁或在单一任务中处理分发。
*/
static parser_t *g_parsers[PARSER_MANAGER_MAX];
static int g_count = 0;
/**
* 初始化 parser manager
* 清空内部列表,并将计数器归零。
*/
void parser_manager_init(void)
{
memset(g_parsers, 0, sizeof(g_parsers));
g_count = 0;
}
/**
* 注册一个 parser 到 manager
* - 参数: p - 要注册的 parser 对象(应已分配并初始化 on_message 等回调)
* - 返回: 0 成功;负值表示错误(如参数为 NULL 或已达最大注册数量)
*/
int parser_manager_register(parser_t *p)
{
if (!p)
return -1;
if (g_count >= PARSER_MANAGER_MAX)
return -2;
g_parsers[g_count++] = p;
/* 如果 parser 提供 init 函数,立即调用,便于 parser 完成其内部初始化 */
if (p->init)
p->init(p);
return 0;
}
/**
* 注销一个 parser
* - 遍历已注册的 parser 列表,找到目标则删除并紧凑数组
* - 返回 0 成功,-1 表示未找到
*/
int parser_manager_unregister(parser_t *p)
{
for (int i = 0; i < g_count; ++i)
{
if (g_parsers[i] == p)
{
for (int j = i; j < g_count - 1; ++j)
g_parsers[j] = g_parsers[j + 1];
g_parsers[--g_count] = NULL;
return 0;
}
}
return -1;
}
/**
* 将一个字节分派给所有已注册解析器
* 策略说明(当前实现):
* - 依次按注册顺序调用每个 parser->feed(p,b)
* - 如果某个 parser 返回 PARSER_DONE,表示该 parser 已处理完一个完整帧并回调,manager 停止对当前字节进一步分发
* - PARSER_IN_PROGRESS 说明 parser 已进入接收状态,仍会接收后续字节(但当前实现并不会把后续字节独占给某个 parser)
* - PARSER_NOT_MATCH / PARSER_ERROR 会继续尝试下一个 parser
*
* 为了提高匹配准确性,可考虑:
* - 根据 priority 排序 parser 列表,优先匹配优先级高的协议
* - 当 parser 返回 IN_PROGRESS 时,为其开启独占模式直到 DONE/ERROR
*/
int parser_manager_feed(uint8_t b)
{
for (int i = 0; i < g_count; ++i)
{
parser_t *p = g_parsers[i];
if (!p || !p->feed)
continue;
parser_status_t st = p->feed(p, b);
if (st == PARSER_DONE)
{
/* 当解析器表明解析完成,通常它会在内部调用 on_message。
* 返回后表示该字节属于一个已处理的帧,停止分发
*/
return 0; /* handled */
}
if (st == PARSER_IN_PROGRESS)
{
/* 当前 parser 正在组帧中。当前 manager 仍然给予其他 parser 机会
* 去识别同一字节(这对某些协议可能合适,也可能存在冲突)
*/
continue;
}
/* PARSER_NOT_MATCH 或 PARSER_ERROR: 尝试下一个解析器 */
}
return 0;
}
/**
* 批量输入字节到 manager
* - 便捷函数:按字节地调用 parser_manager_feed
*/
int parser_manager_feed_buf(const uint8_t *buf, size_t len)
{
for (size_t i = 0; i < len; ++i)
parser_manager_feed(buf[i]);
return 0;
}
/* parser_manager_on_timeout 在下方已用中文注释实现,见文件末尾的实现体 */
/**
* 通知所有注册的 parser 超时/静默事件(例如 Modbus T3.5)
*
* 场景:
* - 当 DMA+IDLE 或定时器检测到接收一段静默时间(或上层决定本次会话超时)时,
* 调用本函数用于通知各 parser 做超时处理。
*
* 行为:遍历已注册解析器并调用其 on_timeout 回调(如果实现了),回调内部可
* 判断当前缓冲是否包含一个完整帧并最终触发 on_message 或执行 reset。
*/
int parser_manager_on_timeout(const uint8_t *buf, size_t len)
{
parser_status_t st;
/* 通知每个 parser 执行其超时处理(如 Modbus parser 的 T3.5 处理流程) */
for (int i = 0; i < g_count; ++i)
{
parser_t *p = g_parsers[i];
if (!p) continue;
if (p->on_timeout)
{
st = p->on_timeout(p,buf,len);
if (st == PARSER_DONE)
{
return 0; //正确 停止分发
}
else
{
continue;
}
}
}
return 0;
}
自定义协议
.h文件
c
#ifndef CUSTOM_PARSER_H
#define CUSTOM_PARSER_H
#include "parser.h"
#include <stddef.h>
parser_t *custom_parser_create(void (*on_message)(parser_t*, const uint8_t*, size_t, void*), void *ctx);
static parser_status_t custom_feed(parser_t *p, uint8_t b);
#endif /* CUSTOM_PARSER_H */
.c文件
c
include "crc16.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
/* 自定义协议(示例):
* 帧格式(简单演示):
* 0x7E | LEN (1) | PAYLOAD (LEN 字节) | CHK (1)
* 其中 CHK = (payload 的所有字节之和) & 0xFF
*
* 解析策略:逐字节读取
* - 首字节 0x7E 表示帧开始
* - 第二字节为 payload 长度 N
* - 随后 N 字节为 payload,最后 1 字节为校验
* - 当收到 N 字节 payload + 1 字节校验后验证 CHK,匹配则视为一帧
*/
typedef struct
{
parser_t base; /* 公共 parser 字段 */
parser_buffer_t buf; /* 内部缓冲区 */
int waiting_payload; /* 标记已看到起始字 */
size_t expected; /* payload 长度 */
} custom_parser_obj_t;
/**
* 初始化 custom parser 的内部状态
* 在注册 parser 或创建时会调用(由 parser_manager_register 触发 init)
*/
static void custom_init(parser_t *p)
{
custom_parser_obj_t *c = (custom_parser_obj_t *)p->ctx;
c->buf.pos = 0;
c->waiting_payload = 0;
c->expected = 0;
}
/**
* 重置 custom parser 状态
* 当解析出错或外部需要重置解析器(例如超时/串口错误)时调用
*/
static void custom_reset(parser_t *p)
{
/* 重置内部状态,丢弃已收数据 */
custom_parser_obj_t *c = (custom_parser_obj_t *)p->ctx;
c->buf.pos = 0;
c->waiting_payload = 0;
c->expected = 0;
}
/**
* custom parser 的逐字节喂入接口
* - 参数 p: parser 对象
* - 参数 b: 新到达的字节
* 返回值: parser_status_t,表示当前的匹配/解析状态
*/
static parser_status_t custom_feed(parser_t *p, uint8_t b)
{
custom_parser_obj_t *c = (custom_parser_obj_t *)p->ctx;
/* 状态机:
* pos==0 -> 等待起始字 0x7E
* pos==1 -> 长度字(expected)
* pos>=2 -> payload + checksum
*/
if (c->buf.pos == 0)
{
if (b != 0x7E)
return PARSER_NOT_MATCH; /* 非起始字则不匹配 */
c->buf.buf[c->buf.pos++] = b;
c->waiting_payload = 1;
return PARSER_IN_PROGRESS;
}
if (c->buf.pos == 1)
{
/* 第二字节为长度 */
c->expected = b;
c->buf.buf[c->buf.pos++] = b;
if (c->expected == 0)
return PARSER_ERROR; /* 不允许空 payload(示例) */
return PARSER_IN_PROGRESS;
}
/* 追加 payload 或 checksum */
c->buf.buf[c->buf.pos++] = b;
/* 判断是否已收到完整帧(2 header + payload + 1 checksum) */
if (c->buf.pos >= 2 + c->expected + 1)
{
/* payload 位置 */
size_t payload_len = c->expected;
const uint8_t *payload = &c->buf.buf[2];
uint8_t chk = c->buf.buf[2 + payload_len];
uint8_t sum = 0;
for (size_t i = 0; i < payload_len; ++i)
sum += payload[i];
if (sum == chk)
{
/* 校验通过,回调并重置内部缓冲 */
if (p->on_message)
p->on_message(p, c->buf.buf, c->buf.pos, p->ctx);
c->buf.pos = 0;
return PARSER_DONE;
}
/* 校验失败:丢弃当前帧并返回错误,这里可以选择更复杂的重同步逻辑 */
c->buf.pos = 0;
return PARSER_ERROR;
}
return PARSER_IN_PROGRESS;
}
static int custom_on_timeout(parser_t *p,const uint8_t *buf, size_t len)
{
// custom_parser_obj_t *c = (custom_parser_obj_t *)p->ctx;
parser_status_t st;
for (size_t i = 0; i < len; ++i)
{
st = custom_feed(p,buf[i]);
}
return st;
}
/**
* 创建 custom parser(分配内存并绑定函数)
* - on_message: 当成功解析到一帧时的回调
* 返回值:parser_t* 指针,失败返回 NULL
*/
parser_t *custom_parser_create(void (*on_message)(parser_t*, const uint8_t*, size_t, void*), void *ctx)
{
custom_parser_obj_t *c = malloc(sizeof(custom_parser_obj_t));
if (!c)
return NULL;
memset(c, 0, sizeof(*c));
c->base.name = "custom_7E_len_chk";
c->base.ctx = c;
c->base.init = custom_init;
c->base.reset = custom_reset;
c->base.feed = custom_feed;
c->base.on_message = on_message;
c->base.on_timeout = custom_on_timeout;
c->base.priority = 1; /* 示例中给出较高优先级 */
return &c->base;
}
/* 当 custom parser 解析完成一个帧:
* 这里打印出来并可在真实项目中把解析后的 payload 推入业务队列
*/
void custom_msg_cb(parser_t *p, const uint8_t *buf, size_t len, void *ctx)
{
(void)ctx; (void)p;
printf("[CUSTOM] got frame len=%zu: ", len);
for (size_t i = 0; i < len; ++i) printf("%02X ", buf[i]);
printf("\n");
}
modbus协议
.h文件
c
#ifndef MODBUS_PARSER_H
#define MODBUS_PARSER_H
#include "parser.h"
#include <stddef.h>
parser_t *modbus_parser_create(void (*on_message)(parser_t*, const uint8_t*, size_t, void*), void *ctx);
static void modbus_msg_cb(parser_t *p, const uint8_t *buf, size_t len, void *ctx);
static int modbus_on_timeout(parser_t *p,const uint8_t *buf, size_t len);
#endif /* MODBUS_PARSER_H */
.c文件
c
#include "modbus_parser.h"
#include "crc16.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
/* Modbus RTU 简单解析器实现说明
* - 不实现完整的 Modbus 协议栈,仅用于示例说明如何把 CRC 与帧边界判定嵌入 parser。
* - Modbus RTU 的帧结束常常依赖于线上的静默间隔(T3.5);在本示例我们采用最简单
* 的办法:逐字节累计直至 CRC 校验通过(适合示例流测试,但在真实串口环境上
* 可能误判或需要基于超时的帧边界检测)。
*
* 约定:当接收到足够字节且 CRC 匹配,调用 on_message 回调并返回 PARSER_DONE。
*/
typedef struct
{
parser_t base; /* 公共 parser 接口 */
parser_buffer_t buf; /* 内部缓冲,用于临时收集字节 */
} modbus_parser_obj_t;
static void modbus_init(parser_t *p)
{
/* 初始化内部缓冲位置信息 */
modbus_parser_obj_t *m = (modbus_parser_obj_t *)p->ctx;
m->buf.pos = 0;
m->buf.expected_len = 0;
}
static void modbus_reset(parser_t *p)
{
/* 清除状态;可在解析错误或超时时调用 */
modbus_parser_obj_t *m = (modbus_parser_obj_t *)p->ctx;
m->buf.pos = 0;
m->buf.expected_len = 0;
}
static parser_status_t modbus_feed(parser_t *p, uint8_t b)
{
modbus_parser_obj_t *m = (modbus_parser_obj_t *)p->ctx;
/* 检查是否超过缓冲区上限,避免内存越界 */
if (m->buf.pos >= PARSER_MAX_FRAME)
{
m->buf.pos = 0; /* 重置以丢弃过长帧 */
return PARSER_ERROR;
}
/* 将新字节追加到缓冲中 */
m->buf.buf[m->buf.pos++] = b;
/* Modbus RTU 简单判定:
* - 最小帧长度: addr(1) + func(1) + CRC(2) = 4 bytes
* - 当缓冲字节>=4 时计算 CRC:如果校验成功则视为一帧完成
* 注意:更健壮的实现应结合帧间超时 (T3.5) 来判断帧边界避免误判
*/
if (m->buf.pos >= 6)
{
size_t datalen = m->buf.pos - 2; /* 不包含 CRC 字节 */
/* Modbus 在帧末尾按低字节先传输 CRC,因此低字节在 m->buf.buf[pos-2] */
uint16_t got_crc = (uint16_t)m->buf.buf[m->buf.pos - 2] | ((uint16_t)m->buf.buf[m->buf.pos - 1] << 8);
uint16_t calc = crc16_modbus(m->buf.buf, datalen);
if (calc == got_crc)
{
/* 成功解析到一帧 -> 调用回调并清空缓冲 */
if (p->on_message)
p->on_message(p, m->buf.buf, m->buf.pos, p->ctx);
m->buf.pos = 0;
return PARSER_DONE;
}
/* CRC 不匹配,继续等待更多字节(或等待超时逻辑处理) */
}
return PARSER_IN_PROGRESS;
}
/* 当外部检测到静默/超时(例如 Modbus T3.5 到期或 UART IDLE)时调用。
* 作用:通知解析器当前接收已进入静默区间;解析器在此处可以最终判定是否
* 缄包结束并交付帧(如果检测到的缓冲已构成完整帧并通过 CRC 校验)。
* 行为:若缓冲里有足够字节(>=4)且 CRC 校验通过,则触发 on_message;否则
* 丢弃缓冲并 reset,避免残留数据影响后续解析。
*/
static int modbus_on_timeout(parser_t *p,const uint8_t *buf, size_t len)
{
modbus_parser_obj_t *m = (modbus_parser_obj_t *)p->ctx;
memset(&m->buf, 0, PARSER_MAX_FRAME); //清空缓存区
memcpy(&m->buf, buf, len); //赋值给缓存区
m->buf.pos = len;
if (m->buf.pos >= 6)
{
size_t datalen = m->buf.pos - 2; /* 不包含 CRC 字节 */
uint16_t got_crc = (uint16_t)m->buf.buf[m->buf.pos - 2] | ((uint16_t)m->buf.buf[m->buf.pos - 1] << 8);
uint16_t calc = crc16_modbus(m->buf.buf, datalen);
if (calc == got_crc)
{
if (p->on_message)
p->on_message(p, m->buf.buf, m->buf.pos, p->ctx);
m->buf.pos = 0;
return PARSER_DONE;
}
/* CRC failed --- drop buffer and reset */
}
return PARSER_ERROR;
}
/**
* 创建并分配一个 modbus_parser 对象
*
* 参数:
* - on_message: 当成功解析到一个 Modbus 帧时的回调函数
* - ctx: 用户自定义上下文,会以 p->ctx 方式传入回调(当前示例未使用)
* 返回:指向 parser_t 的指针(调用方负责释放内存,如果需要)
*
* 说明:该解析器为示例实现,兼容基于 CRC 校验的判帧方式,并支持 on_timeout
* (当上层 IDLE 或定时器通知超时时,parser 会在 on_timeout 中再次尝试把缓冲
* 作为一个完整帧处理)。
*/
parser_t *modbus_parser_create(void (*on_message)(parser_t *, const uint8_t *, size_t, void *), void *ctx)
{
modbus_parser_obj_t *m = malloc(sizeof(modbus_parser_obj_t));
if (!m)
return NULL;
memset(m, 0, sizeof(*m));
m->base.name = "modbus";
/* 注意:base.ctx 指向整个 modbus_parser_obj_t 便于在 callback 中访问 */
m->base.ctx = m;
m->base.init = modbus_init;
m->base.reset = modbus_reset;
m->base.feed = modbus_feed;
m->base.on_message = on_message;
m->base.on_timeout = modbus_on_timeout;
m->base.priority = 0; /* 默认优先级 */
m->buf.pos = 0;
m->buf.expected_len = 0;
return &m->base;
}
/* 当 modbus parser 成功解析到一帧,该回调打印帧数据。
* 注意:在嵌入式应用中应尽量避免在回调中进行耗时工作,应把解析结果放入队列交给业务线程处理。
*/
static void modbus_msg_cb(parser_t *p, const uint8_t *buf, size_t len, void *ctx)
{
(void)ctx; (void)p;
printf("[MODBUS] got frame len=%zu: ", len);
for (size_t i = 0; i < len; ++i) printf("%02X ", buf[i]);
printf("\n");
}
测试
c
/* 当 custom parser 解析完成一个帧:
* 这里打印出来并可在真实项目中把解析后的 payload 推入业务队列
*/
static void custom_msg_cb(parser_t *p, const uint8_t *buf, size_t len, void *ctx)
{
(void)ctx; (void)p;
printf("[CUSTOM] got frame len=%zu: ", len);
for (size_t i = 0; i < len; ++i) printf("%02X ", buf[i]);
printf("\n");
}
/* 当 modbus parser 成功解析到一帧,该回调打印帧数据。
* 注意:在嵌入式应用中应尽量避免在回调中进行耗时工作,应把解析结果放入队列交给业务线程处理。
*/
static void modbus_msg_cb(parser_t *p, const uint8_t *buf, size_t len, void *ctx)
{
(void)ctx; (void)p;
printf("[MODBUS] got frame len=%zu: ", len);
for (size_t i = 0; i < len; ++i) printf("%02X ", buf[i]);
printf("\n");
}
/* USER CODE END 0 */
// 用户定义的数据处理回调函数
void User_UART_Data_Handler(uint8_t uart_id, uint8_t *data, uint16_t len)
{
SendBefore();
switch(uart_id) {
case 0:
printf("UART1 Received %d bytes: ", len);
for (int i = 0; i < len; i++) {
printf("%02X ", data[i]);
}
printf("\r\n");
break;
case 1:
// parser_manager_feed_buf(data, len);
parser_manager_on_timeout(data, len);
break;
case 2:
printf("UART3 Received %d bytes: ", len);
for (int i = 0; i < len; i++) {
printf("%02X ", data[i]);
}
printf("\r\n");
break;
}
SendOver();
// 这里可以添加协议解析、数据存储等操作
}
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C2_Init();
MX_USART2_UART_Init();
MX_TIM7_Init();
/* USER CODE BEGIN 2 */
SendOver();
UART_Timeout_Manager_Init();
UART_Register_Timeout(&huart2, 1, User_UART_Data_Handler);
parser_manager_init();
parser_t *cp = custom_parser_create(custom_msg_cb, NULL);
parser_t *mp = modbus_parser_create(modbus_msg_cb, NULL);
parser_manager_register(cp);
parser_manager_register(mp);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}

测试可以完美解析不同协议格式