状态机代码
#以下代码的功能是解析数据包,按照一定的协议格式寻找帧头(0xFF)、标识符(0x01)、控制字节(0x7D)、数据部分(最多 129 字节),以及帧尾(0xFE)。当成功接收到完整数据帧后,会将 frame_done
置为 1(但 frame_done
没有进一步处理)。
VOID CsiParsePacket (INT8U *pData, DWORD dwLen)
{
INT16 i;
INT8U curr_char;
BOOL result;
static UINT8 buffer[131];
static UINT8 buf_index = 0;
static UINT8 step = 0;
UINT8 frame_done = 0;
for (i = 0; i < dwLen; i++) {
switch (step) {
case 0: { // find 0xff
if (pData[i] == 0xff) {
buf_index = 0;
memset(buffer, 0, 131);
buffer[buf_index] = pData[i];
buf_index++;
step = 1;
} else {
step = 0;
buf_index = 0;
}
} break;
case 1: { // find 0x01
if (pData[i] == 0x01) {
buffer[buf_index] = pData[i];
buf_index++;
step = 2;
} else {
step = 0;
buf_index = 0;
}
} break;
case 2: { // find 0x7d
if (pData[i] == 0x7d) {
buffer[buf_index] = pData[i];
buf_index++;
step = 3;
} else {
step = 0;
buf_index = 0;
}
} break;
case 3: { // load data
if (buf_index < 129) {
buffer[buf_index] = pData[i];
buf_index++;
} else {
step = 4;
}
} break;
case 4: { // find 0xfe
if (pData[i] == 0xfe) {
buffer[buf_index] = pData[i];
buf_index++;
frame_done = 1;
}
buf_index = 0;
step = 0;
} break;
}
}
代码解析
1. 函数参数
VOID CsiParsePacket (INT8U *pData, DWORD dwLen)
pData
:指向待解析的数据流(字节数组)。dwLen
:数据流的长度。
2. 变量定义
INT16 i; // 遍历数据流的索引
INT8U curr_char; // 当前处理的字节(但未使用)
BOOL result; // 可能用于后续处理(未使用)
static UINT8 buffer[131]; // 存储解析出的数据包(最大 131 字节)
static UINT8 buf_index = 0; // 当前数据存储索引
static UINT8 step = 0; // 解析状态机的状态
UINT8 frame_done = 0; // 标记数据帧是否解析完成(未进一步处理)
buffer
用于存储解析出的数据包。step
作为状态机的步骤控制变量,确保按照0xFF 0x01 0x7D ... 0xFE
格式解析数据。
3. 数据包解析状态机
Step 0: 查找帧头 0xFF
case 0: { // find 0xff
if (pData[i] == 0xff) {
buf_index = 0;
memset(buffer, 0, 131);
buffer[buf_index] = pData[i];
buf_index++;
step = 1; // 进入下一步查找 0x01
} else {
step = 0;
buf_index = 0;
}
} break;
- 如果当前字节是
0xFF
(帧头):- 重置
buf_index
和buffer
,清除旧数据。 - 记录
0xFF
并进入step = 1
(寻找0x01
)。
- 重置
- 否则,继续等待
0xFF
。
Step 1: 查找 0x01
case 1: { // find 0x01
if (pData[i] == 0x01) {
buffer[buf_index] = pData[i];
buf_index++;
step = 2; // 进入下一步查找 0x7D
} else {
step = 0;
buf_index = 0;
}
} break;
- 期待下一个字节是
0x01
。 - 如果找到
0x01
,记录并进入step = 2
(寻找0x7D
)。 - 否则,解析失败,回到
step = 0
重新查找0xFF
。
Step 2: 查找 0x7D
case 2: { // find 0x7d
if (pData[i] == 0x7d) {
buffer[buf_index] = pData[i];
buf_index++;
step = 3; // 进入数据部分
} else {
step = 0;
buf_index = 0;
}
} break;
- 期待
0x7D
作为控制字节。 - 如果找到
0x7D
,记录并进入step = 3
(开始接收数据)。 - 否则,解析失败,回到
step = 0
重新查找0xFF
。
Step 3: 接收数据
case 3: { // load data
if (buf_index < 129) {
buffer[buf_index] = pData[i];
buf_index++;
} else {
step = 4; // 数据超过 129 字节,进入查找帧尾
}
} break;
- 继续接收数据,最多 129 字节。
- 如果
buf_index
超过 129,自动进入step = 4
(查找帧尾0xFE
)。
Step 4: 查找帧尾 0xFE
case 4: { // find 0xfe
if (pData[i] == 0xfe) {
buffer[buf_index] = pData[i];
buf_index++;
frame_done = 1; // 数据包完整
}
buf_index = 0;
step = 0;
} break;
- 期待
0xFE
作为数据包的结束标志。 - 如果
pData[i] == 0xFE
,说明数据包完整,标记frame_done = 1
。 - 无论是否成功解析,重置
buf_index
和step
,准备解析下一个数据包。
总结
- 解析数据格式:
0xFF 0x01 0x7D [最大 129 字节数据] 0xFE
- 状态机解析流程:
- Step 0:寻找
0xFF
(帧头)。 - Step 1:寻找
0x01
(标识符)。 - Step 2:寻找
0x7D
(控制字节)。 - Step 3:接收数据(最多 129 字节)。
- Step 4:寻找
0xFE
(帧尾)。 - 成功解析后
frame_done = 1
(但代码中未使用)。
- Step 0:寻找
可能问题
-
没有处理
frame_done
- 解析完整数据帧后,
frame_done = 1
,但代码没有利用frame_done
进行后续处理,如回调函数或数据传输。
- 解析完整数据帧后,
-
数据丢失风险
- 如果数据包中有
0xFF
,但后续字节不符合格式,代码会丢弃整个包,而不是继续查找新的0xFF
。 - 可能导致部分有效数据丢失。
- 如果数据包中有
-
固定
129
字节数据限制- 如果协议允许不同长度的数据帧,当前代码会错误地忽略小于 129 字节的数据。
优化建议
✅ 处理 frame_done
if (frame_done) {
frame_done = 0; // 复位
ProcessFrame(buffer, buf_index); // 处理完整帧
}
✅ 优化数据丢失问题
如果检测到 0xFF
但没有完整数据包,可以不清空 buffer
,而是继续寻找 0xFF
:
if (step == 0 && pData[i] != 0xFF) {
continue; // 继续查找帧头
}
总结
这段代码实现了一个简单的状态机来解析固定格式的数据包(0xFF 0x01 0x7D ... 0xFE
)。但它有一些潜在问题,例如 frame_done
未使用、可能丢失数据、以及无法处理变长数据帧。优化后,它可以更可靠地解析数据并触发相应的回调处理完整帧。