解析器模式,解析不同协议

之前使用一个定时器作为多个串口的超时计数器,那么如果有好几种协议,能不能都可以解析出来,并且方便扩展呢,参考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 */
}

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

相关推荐
1379号监听员_2 小时前
PID定速/定位置控制电机
stm32·单片机
SystickInt4 小时前
32 LCD显示(FSMC应用)-寄存器
stm32·单片机·嵌入式硬件
d111111111d4 小时前
C语言中static修斯局部变量,全局变量和函数时分别由什么特性
c语言·javascript·笔记·stm32·单片机·嵌入式硬件·学习
逐步前行4 小时前
C51_74HC595串口转并口
单片机·嵌入式硬件
阿拉斯攀登6 小时前
STM32 架构概述
stm32·单片机·架构
物联网牛七七7 小时前
STM32 EXTI(外部中断)详解
stm32·单片机·嵌入式硬件·exti
发光小北7 小时前
SG-TCP232-110(单通道串口服务器)特点与功能介绍
服务器·网络·单片机
d111111111d7 小时前
STM32外设学习-读取芯片ID。(学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习
阿拉斯攀登7 小时前
STM32 简单入门
stm32·单片机·嵌入式硬件