PSM:Protocol State Machine,协议状态机。
一款用于流式传输的数据协议解析组件,可有效解决沾包、断帧问题。
PSM以回调函数的形式将完整的协议数据包抛到应用软件层,确保应用软件层收到的数据是一包完整的、有效的数据帧。
代码以纯C编写,可无缝在windows、linux、嵌入式单片机、嵌入式RTOS下应用。
代码下载:https://gitee.com/jhembedded/psm
协议的制定也是比较有规律的,一包完整的协议数据包的结构包含:包头、数据字段的长度、包尾等信息,或者只有协议包头后跟数据的形式,基于此特性,PSM由此而生。
PSM由psm_t这个结构体进行管理,该结构如下:
typedef struct psm psm_t;
struct psm
{
uint8_t* buf; // 指向数据缓存
uint32_t bufsize; // 数据缓存大小
uint32_t wpos; // 写指针
uint32_t rpos; // 读指针
uint32_t match_start; // 数据匹配索引
psm_state_t state; // 当前协议状态机的状态
int (*recv_callback)(psm_t* psm, const void* buf, uint32_t len); // 数据回调函数
uint8_t delimiter[4]; // 协议尾分隔符
uint32_t delimiter_bytes; // 协议尾分隔符长度
uint32_t delimiter_index; // 协议尾分隔符匹配索引
uint8_t identifier[4]; // 协议头标识符
uint32_t identifier_bytes; // 协议头标识符长度
uint32_t identifier_index; // 协议头标识符匹配索引
uint32_t body_offset; // 主体数据偏移地址
uint32_t body_length; // 主体数据长度
uint32_t length_field_offset; // 长度字段偏移地址
uint32_t length_field_bytes; // 长度字段所占字节数
psm_unpack_coding_t length_field_coding; // 长度字节编码方式
void* arg; // 可用于携带附加参数
};
PSM的移植也很简单,API如下:
int psm_recv(psm_t* psm, const void* buf, uint32_t len);
int psm_recv_byte(psm_t* psm, uint8_t c);
void psm_reset(psm_t* psm);
int psm_unpack(psm_t* psm);
移植时将psm_recv或psm_recv_byte放置于实际的数据接收位置进行数据接收,使用psm_unpack对收到的数据进行解析,psm_unpack解析成功后自动调用回调函数将完整的协议包抛到上层。
以单片机串口为例,将psm_recv_byte或psm_recv放置到串口接收中断中进行数据接收,在main函数中的死循环中轮训psm_unpack函数即可。
示例:
psm_t psm_usart =
{
...
...
...
...
};
void UART1_IRQHandler(void)
{
static uint8_t RxData;
...
psm_recv_byte(&psm_usart, RxData); // 接收一个字节
...
}
int main()
{
...
...
...
while (1)
{
psm_unpack(&psm_usart); // 解析
}
}
同时,也可将psm_unpack直接放置在串口接收中断中,由于此时数据是在串口中断中进行的数据解析,解析成功后会调用回调函数,所以在在回调函数中不可存在耗时操作。
示例:
void UART1_IRQHandler(void)
{
static uint8_t RxData;
...
psm_recv_byte(&psm_usart, RxData); // 接收一个字节
psm_unpack(&psm_usart); // 解析
...
}
int recv_callback(psm_t * psm, const void* buf, uint32_t len)
{
// 在此函数中对完整的数据包进行使用
return 0;
}
uint8_t buffer[512];
psm_t psm =
{
.buf = buffer,
.bufsize = sizeof(buffer),
.recv_callback = recv_callback,
.delimiter[0] = '\r', // 结束符第一个字节
.delimiter[1] = '\n', // 结束符第二个字节
.delimiter_bytes = 2, // 结束符大小
};
该协议结构一般为:
包头 | 命令码 | 数据长度 | CRC | N字节数据 |
---|
其中:包头 +命令码 +数据长度 +CRC组合成协议头,后跟实际的数据。
以包头、命令码、数据长度、CRC都为unsigned short类型,且包头为0XA1B0,PSM设置如下:
psm_t psm =
{
.buf = buffer,
.bufsize = sizeof(buffer),
.recv_callback = recv_callback,
.identifier[0] = 0XB0, // 包头低字节
.identifier[1] = 0XA1, // 包头高字节
.identifier_bytes = 2, // 包头的大小
.length_field_bytes = 2, // 协议头中数据长度字段的大小
.length_field_offset = 4, // 协议头中数据长度字段的位置偏移
.length_field_coding = PSM_UNPACK_ENCODE_BY_LITTEL_ENDIAN, // 编码方式:小端
.body_offset = 8, // 主体数据的位置偏移
};
注:不支持相同字节的包头包尾,例如包头为0XAA、0XAA,包尾为0x55、0x55。
该协议结构一般为:
包头 | 数据 | 包尾 |
---|
假设包头、包尾为unsigned short类型,且包头为0XAA、0X55、包尾为0X55、0XAA,PSM设置如下:
psm_t psm =
{
.buf = buffer,
.bufsize = sizeof(buffer),
.recv_callback = recv_callback,
.identifier[0] = 0XAA, // 包头低字节
.identifier[1] = 0X55, // 包头高字节
.identifier_bytes = 2, // 包头的大小
.delimiter[0] = 0X55, // 包尾低字节
.delimiter[1] = 0XAA, // 包尾高字节
.delimiter_bytes = 2, // 包尾的大小
};
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。