串口接收协议,建立一个结构体,把接收到的帧头,设备编号,功能码,起始信息,数据,校验,存入到结构体中,使用指针核对帧头,设备编号,功能码,起始信息,校验,是否正确,C语言代码
时间: 2025-07-01 22:46:15 浏览: 6
<think>根据用户的要求,我们需要实现一个串口通信协议的解析。协议包含以下字段:帧头、设备编号、功能码、起始信息、数据、校验字段。我们将定义一个结构体来存储这些字段,并通过指针逐一验证这些字段的正确性。步骤:1.定义协议结构体。2.从串口接收到的数据缓冲区中提取数据。3.校验帧头是否正确。4.根据协议结构解析数据。5.计算校验和(或校验码)并与接收到的校验字段比较。假设协议格式如下(具体协议字段长度和位置根据实际情况调整):-帧头:2字节,固定为0xAA55-设备编号:1字节-功能码:1字节-起始信息:2字节-数据:N字节(长度由协议规定,或通过固定长度,或通过长度字段,这里假设为固定长度,例如10字节)-校验:1字节(假设为前面所有字节的异或校验)注意:实际协议中可能有长度字段,但根据问题描述,协议字段中没有提到长度字段,所以这里假设为固定长度的数据字段(10字节)。协议结构:[帧头2][设备编号1][功能码1][起始信息2][数据10][校验1]总长度=2+1+1+2+10+1=17字节。定义结构体:```ctypedefstruct{uint16_theader;//帧头uint8_tdevice_id;//设备编号uint8_tfunction_code;//功能码uint16_tstart_info;//起始信息uint8_tdata[10];//数据uint8_tchecksum;//校验}UART_Protocol;```解析步骤:1.接收数据到缓冲区(假设已经接收到一个完整的数据包,长度17字节)。2.将缓冲区数据映射到结构体(注意内存对齐和字节序问题)。3.验证帧头是否为0xAA55。4.计算校验和:从帧头开始到数据结束(不包括校验和字段)的所有字节进行异或,然后与结构体中的checksum比较。由于串口接收的数据是字节流,我们需要将字节流复制到结构体中。这里要注意结构体的内存布局和字节序(小端模式)。注意:帧头是uint16_t类型,在x86和ARM上通常是小端模式。如果我们的设备是小端,而协议帧头字节序是大端,则需要转换。这里假设帧头在协议中是大端(高字节在前),而我们的系统是小端,所以需要转换。因此,我们有两种方式:1.直接使用指针按字节位置逐一赋值给结构体成员,同时转换字节序(如果需要)。2.或者定义一个联合体,方便按字节访问。这里选择方法1:逐字节复制并转换多字节字段的字节序。另一种方法:使用memcpy,但是要注意字节序转换。我们可以在复制到结构体后,对多字节字段(header,start_info)进行字节序转换。这里假设协议帧头是大端,起始信息也是大端,而我们的系统是小端。因此,在复制这些字段后,需要进行网络字节序到主机字节序的转换(ntohs)。所以,解析过程:1.将接收到的字节流复制到一个足够大的缓冲区。2.定义一个临时结构体变量。3.使用指针将缓冲区中每个字段按协议顺序复制到结构体成员,并对多字节字段进行字节序转换。为了简化,我们可以直接使用指针偏移来读取各字段,并转换需要转换的字段。代码实现:```c#include<stdint.h>#include<stdbool.h>#include<string.h>//memcpy//假设接收到的数据存放在recv_buf中,长度为sizeof(UART_Protocol)#definePROTOCOL_SIZE17//定义一个结构体存储解析后的协议数据typedefstruct{uint16_theader;uint8_tdevice_id;uint8_tfunction_code;uint16_tstart_info;uint8_tdata[10];uint8_tchecksum;}UART_Protocol;//字节序转换函数uint16_tswap16(uint16_tvalue){return(value<<8)|(value>>8);}//解析数据包,并验证帧头和校验和//输入:recv_buf接收缓冲区,长度至少为PROTOCOL_SIZE//输出:将解析后的数据存到protocol结构体中//返回值:true表示解析成功,false表示失败boolparse_protocol(constuint8_t*recv_buf,UART_Protocol*protocol){//1.复制整个缓冲区到结构体(不能直接memcpy,因为存在字节序问题)//我们按字段复制//复制帧头(2字节,大端转小端)//将两个字节组合为uint16_t,并转换字节序protocol->header=(recv_buf[0]<<8)|recv_buf[1];//如果系统是小端,并且协议规定帧头是大端,那么这样组合后就是小端值(因为左移8位后高字节在前,然后或低字节)//实际上,这样组合后得到的是主机序(如果主机是小端,那么存储时就是小端存储,但数值是正确的)//验证帧头if(protocol->header!=0xAA55){returnfalse;}//设备编号(1字节)protocol->device_id=recv_buf[2];//功能码(1字节)protocol->function_code=recv_buf[3];//起始信息(2字节,大端)protocol->start_info=(recv_buf[4]<<8)|recv_buf[5];//数据(10字节)memcpy(protocol->data,&recv_buf[6],10);//校验和(1字节)protocol->checksum=recv_buf[16];//计算校验和:从帧头到数据结束(即前16字节)的异或校验和uint8_tcalc_checksum=0;for(inti=0;i<16;i++){//0~15共16个字节calc_checksum^=recv_buf[i];}//比较校验和if(calc_checksum!=protocol->checksum){returnfalse;}returntrue;}//如果协议中的帧头和起始信息都是小端,那么可以简化,直接使用memcpy复制整个结构体,但要注意结构体对齐问题。//但是通常串口协议使用大端(网络字节序),所以上述转换方式更为通用。//另外,为了避免结构体对齐问题(结构体可能存在填充),我们可以在定义结构体时指定按1字节对齐(使用编译器扩展)//例如使用#pragmapack(1)#pragmapack(push,1)typedefstruct{uint16_theader;uint8_tdevice_id;uint8_tfunction_code;uint16_tstart_info;uint8_tdata[10];uint8_tchecksum;}UART_Protocol2;#pragmapack(pop)//使用这个UART_Protocol2结构体,我们可以直接使用memcpy,前提是协议中的字段顺序和结构体完全一致,且协议中的多字节字段也是小端模式(与主机一致)。//但是,如果协议是多字节字段使用大端,而我们主机是小端,那么复制后仍然需要转换每个多字节字段的字节序。//因此,我们推荐使用第一种方法,按字段复制并手动转换字节序(针对大端协议)。```上述代码中,我们定义了一个解析函数`parse_protocol`,它接收一个字节缓冲区和指向UART_Protocol的指针,解析并验证数据包。如果帧头正确且校验和匹配,则返回true,否则返回false。使用示例:```cintmain(){uint8_trecv_buf[PROTOCOL_SIZE];//接收缓冲区//这里假设已经通过串口接收到一个完整数据包,存到recv_buf中UART_Protocolprotocol;if(parse_protocol(recv_buf,&protocol)){//解析成功,处理协议数据}else{//解析失败}return0;}```注意:在实际串口通信中,可能还需要处理数据包的接收(包括组包、处理粘包、断包等),这超出了本问题的范围。相关问题:§§相关问题§§1.如何解决串口通信中的粘包断包问题?2.如果协议字段的长度不固定(例如数据字段长度可变),应如何设计解析函数?3.除了异或校验,通信协议还有哪些常见的校验方式?4.在结构体解析时,如何避免因为内存对齐导致的问题?</think>
### C语言串口通信协议解析实现
以下是根据您的需求实现的串口通信协议解析代码。协议字段包括:帧头、设备编号、功能码、起始信息、数据和校验字段。假设协议格式如下:
- 帧头:2字节(固定值 `0xAA 0x55`)
- 设备编号:1字节
- 功能码:1字节
- 起始信息:2字节
- 数据:长度根据功能码确定
- 校验:1字节(使用XOR校验)
```c
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
// 通信协议结构体定义
typedef struct {
uint8_t header[2]; // 帧头(0xAA, 0x55)
uint8_t device_id; // 设备编号
uint8_t function_code; // 功能码
uint16_t start_info; // 起始信息
uint8_t *data; // 数据指针
uint8_t checksum; // 校验字段
} SerialProtocol;
// 协议解析函数
bool parse_serial_protocol(
const uint8_t *buffer,
size_t length,
SerialProtocol *result
) {
// 1. 校验最小长度
if (length < 7) {
return false; // 帧头(2) + 设备ID(1) + 功能码(1) + 起始信息(2) + 校验(1) = 7字节
}
// 2. 验证帧头
if (buffer[0] != 0xAA || buffer[1] != 0x55) {
return false;
}
// 3. 计算数据长度
size_t data_length = length - 7;
result->data = malloc(data_length);
if (!result->data) {
return false; // 内存分配失败
}
// 4. 解析固定长度字段
result->header[0] = buffer[0];
result->header[1] = buffer[1];
result->device_id = buffer[2];
result->function_code = buffer[3];
// 5. 组合起始信息(大端序)
result->start_info = (buffer[4] << 8) | buffer[5];
// 6. 提取数据字段
memcpy(result->data, buffer + 6, data_length);
result->checksum = buffer[6 + data_length]; // 最后一个字节是校验
// 7. 校验计算(验证数据完整性)
uint8_t calculated_checksum = 0;
for (size_t i = 0; i < length - 1; i++) {
calculated_checksum ^= buffer[i];
}
// 8. 返回解析结果
return (calculated_checksum == result->checksum);
}
// 示例使用
int main() {
// 模拟接收的串口数据(包含完整协议帧)
uint8_t rx_buffer[] = {
0xAA, 0x55, // 帧头
0x01, // 设备编号
0xA0, // 功能码
0x12, 0x34, // 起始信息
0x11, 0x22, 0x33, // 数据字段
0x44 // XOR校验(前面所有字节异或)
};
const size_t rx_length = sizeof(rx_buffer);
SerialProtocol parsed;
if (parse_serial_protocol(rx_buffer, rx_length, &parsed)) {
printf("设备ID: 0x%02X\n", parsed.device_id);
printf("功能码: 0x%02X\n", parsed.function_code);
printf("起始信息: 0x%04X\n", parsed.start_info);
// 释放动态分配的内存
free(parsed.data);
return 0;
} else {
printf("协议解析失败\n");
return 1;
}
}
```
### 关键功能说明
1. **帧头验证**:
```c
if (buffer[0] != 0xAA || buffer[1] != 0x55)
```
确保数据包以正确帧头开始,作为有效协议帧的标识[^1]。
2. **动态数据长度处理**:
```c
size_t data_length = length - 7;
result->data = malloc(data_length);
```
根据接收数据总长度动态计算数据段长度,适应不同长度的数据内容。
3. **起始信息组合**:
```c
result->start_info = (buffer[4] << 8) | buffer[5];
```
将两个独立字节组合为16位值,使用大端序存储(高位在前)。
4. **XOR校验计算**:
```c
for (size_t i = 0; i < length - 1; i++) {
calculated_checksum ^= buffer[i];
}
```
实现简单的异或校验机制,验证数据完整性[^2]。
5. **内存管理**:
```c
free(parsed.data); // 使用后释放内存
```
动态分配内存存储可变长度数据,避免静态数组大小限制。
### 使用说明
1. 接收串口数据到`buffer`缓冲区
2. 调用`parse_serial_protocol()`解析数据
3. 校验返回值判断解析是否成功
4. 使用解析出的结构体字段值
5. **必须**在使用后调用`free()`释放数据内存
该实现适用于大多数嵌入式场景,并支持:
- 动态数据长度
- 协议完整性校验
- 字段级错误检测
- 大小端转换[^3]
阅读全文
相关推荐


















