记录下单片机的命令模式
通过命令模式,我们将通信协议解析与命令执行逻辑分离,使得添加新命令只需增加表项和实现函数,而无需修改解析流程。代码简洁、可维护性强。根据实际需求,还可以进一步扩展,如支持参数校验、权限管理、命令返回值等。
在之前的策略模式中改动
.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);
}
测试

测试可以发现对正确命令做出了响应
以后需要增加命令时,只需要在命令表增加命令和相应处理函数即可