单片机的命令模式

记录下单片机的命令模式

通过命令模式,我们将通信协议解析与命令执行逻辑分离,使得添加新命令只需增加表项和实现函数,而无需修改解析流程。代码简洁、可维护性强。根据实际需求,还可以进一步扩展,如支持参数校验、权限管理、命令返回值等。

在之前的策略模式中改动

.h文件

c 复制代码
#ifndef CUSTOM_PARSER_H
#define CUSTOM_PARSER_H

#include "parser.h"
#include <stddef.h>



/* 命令码枚举 */
typedef enum {
    CMD_LED_ON   = 0xAA,
    CMD_LED_OFF  = 0xAB,
    CMD_READ_TEMP = 0xAC,
    // ... 其他命令
} CmdCode_t;

/* 命令处理函数类型:接收参数指针和长度,返回执行状态 */
typedef uint8_t (*CmdHandler)(uint8_t *data, uint8_t len);

/* 命令表项结构 */
typedef struct 
{
    CmdCode_t  cmdCode;      // 命令码
    CmdHandler handler;      // 处理函数
    uint8_t    minDataLen;   // 最小所需数据长度(用于参数校验)
} CmdTableItem_t;


#define ERR_INVALID_PARAM 0xF1
#define ERR_UNKNOWN_CMD   0xF2




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);
void custom_msg_cb(parser_t *p, const uint8_t *buf, size_t len, void *ctx);

static uint8_t ledOnHandler(uint8_t *data, uint8_t len);
static uint8_t ledOffHandler(uint8_t *data, uint8_t len);
static uint8_t readTempHandler(uint8_t *data, uint8_t len);
#endif /* CUSTOM_PARSER_H */

.c文件

c 复制代码
#include "custom_parser.h"
#include "crc16.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>



/* 命令表 */
const CmdTableItem_t cmdTable[] = {
    {CMD_LED_ON,   ledOnHandler,   1},
    {CMD_LED_OFF,  ledOffHandler,  1},
    {CMD_READ_TEMP, readTempHandler, 0},
    // ... 更多命令
};



/* 自定义协议(示例):
 * 帧格式(简单演示):
 *   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;
}


/* LED 开 */
static uint8_t ledOnHandler(uint8_t *data, uint8_t len)
	{
    if (len < 1) return 0xFF; // 参数错误
    uint8_t ledId = data[0];
    // 实际硬件操作:根据 ledId 点亮 LED
//    HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
		 printf("CMD is led On \n");
    return 0x00; // 成功
}

/* LED 关 */
static uint8_t ledOffHandler(uint8_t *data, uint8_t len)
	{
    if (len < 1) return 0xFF;
    uint8_t ledId = data[0];
//    HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
		 printf("CMD is led Off \n");
    return 0x00;
}


/* 读取温度 */
static uint8_t readTempHandler(uint8_t *data, uint8_t len) 
{
    // 假设无参数,直接读取温度并发送回上位机
//    uint16_t temp = read_temperature();
//    // 将温度值通过 UART 返回
//    uint8_t resp[] = {0xAA, 0x03, 2, (temp>>8)&0xFF, temp&0xFF, 0};
//    resp[5] = xor_checksum(&resp[1], 4); // 计算校验
//    HAL_UART_Transmit(&huart1, resp, sizeof(resp), 100);
			 printf("CMD is read temperature \n");
    return 0x00;
}



void SendErrorResponse(uint8_t cmd, uint8_t errCode)
	{
//    uint8_t resp[] = {0xAA, cmd, 1, errCode, 0};
//    resp[4] = xor_checksum(&resp[1], 3);
//    HAL_UART_Transmit(&huart1, resp, sizeof(resp), 100);
		 printf("Send Error Response \n");
}




/* 当 custom parser 解析完成一个帧:
 * 这里打印出来并可在真实项目中把解析后的 payload 推入业务队列
 */
void custom_msg_cb(parser_t *p, const uint8_t *buf, size_t len, void *ctx)
{
    (void)ctx; (void)p;
	 uint8_t cmd = buf[2];
	 uint8_t max_length = 0;
    printf("[CUSTOM] got frame len=%zu: ", len);
    for (size_t i = 0; i < len; ++i)
		{
			printf("%02X ", buf[i]);
		}
    printf("\n");
		
		
		max_length = sizeof(cmdTable) / sizeof(CmdTableItem_t);
		    // 在命令表中查找命令码
    for (uint8_t i = 0; i < max_length; i++) {
        if (cmdTable[i].cmdCode == cmd) 
					{
            // 检查数据长度是否足够
            if (len < cmdTable[i].minDataLen) {
                // 参数不足,发送错误应答
                SendErrorResponse(cmd, ERR_INVALID_PARAM);
                return;
            }
            // 执行命令处理函数
            uint8_t status = cmdTable[i].handler(&cmd, len);
            // 如果需要应答,可在此处统一构建应答帧
            if (status != 0x00) 
						{
                SendErrorResponse(cmd, status);
            } else 
						{
                // 可能有的命令已自行发送应答,此处不再重复
            }
            return;
        }
    }
    // 命令码未找到
    SendErrorResponse(cmd, ERR_UNKNOWN_CMD);
}

测试

测试可以发现对正确命令做出了响应

以后需要增加命令时,只需要在命令表增加命令和相应处理函数即可

相关推荐
集芯微电科技有限公司2 小时前
700V/1.6A单通道GaN FET增强型驱动器具有零反向恢复损耗
人工智能·单片机·嵌入式硬件·深度学习·神经网络·机器学习·生成对抗网络
承前智2 小时前
Arduino1.8.19与stm32+ESP32的geek卸载及环境安装
stm32·单片机·嵌入式硬件
全栈游侠2 小时前
STM32F103XX 05-时钟配置分析与自举程序
stm32·单片机·嵌入式硬件
学嵌入式的小杨同学2 小时前
STM32 入门封神之路(四):GPIO 实战 + 寄存器深度拆解 ——LED 控制 + 按键检测全流程(含位操作 + 面试题)
stm32·单片机·嵌入式硬件·硬件架构·硬件工程·智能硬件·嵌入式实时数据库
撩妹小狗3 小时前
定时器PWM输出功能的使用
单片机·嵌入式硬件
学嵌入式的小杨同学4 小时前
STM32 进阶封神之路(七):中断核心原理 + NVIC 深度解析 —— 从概念到寄存器配置(面试重点)
stm32·单片机·嵌入式硬件·mcu·硬件架构·pcb·嵌入式实时数据库
不吃鱼的羊4 小时前
CodeMeter Runtime Server was not found on this computter问题解决
单片机
蒙塔基的钢蛋儿5 小时前
使用STM32CUEBEIDE/S32DS 开发时,生成compile_commands.json 方便VSCODE智能提示
vscode·stm32·单片机·json
qq_402995755 小时前
RS485通信设计
stm32·单片机·mcu