ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32 串口发送接收与数据包学习笔记

STM32 串口发送接收与数据包学习笔记 数据包理论一、为什么需要数据包一帧只能传 8 位串口物理层一帧 起始位 8 数据位 停止位一次只能传 1 个字节。可实际通信往往要传一组有含义的信息打包给数据加上固定的边界标记让接收方一看就知道这包从哪开始、到哪结束┌────┬─────────────┬────┐ │包头 │ 数据区 │包尾 │ └────┴─────────────┴────┘ 固定值 内容 固定值二、数据包的基本格式一个完整的数据包通常包含部分作用说明包头Head标记一包从这里开始1~2 个固定字节如0xFF或数据区Data真正要传的内容长度固定或可变包尾Tail标记这包到此结束固定字节如0xFC或\r\n校验Check可选检测传输是否出错和校验/CRC 等发送与接收两端必须约定同一套格式通信协议否则无法互相理解。三、两种数据包类型1. HEX 数据包十六进制/原始数据包包头 数据区 包尾 0xFF 0xFE │ 0x01 0x02 0x03 │ 0xFC特点说明边界用不可见控制字节0xFF 等当包头包尾数据直接传二进制数值原始数据优点传输高效、紧凑缺点不可读、需程序解析、数据区不能出现和包头相同的值要转义适用程序间通信、传感器数据、指令下发2. 文本数据包ASCII/字符数据包包头 数据区 包尾 │ 123 456 │ \r\n特点说明边界用可见字符、\r\n回车换行当包头包尾数据以 ASCII 文本传输如 “123” 是 3 个 ASCII 字符优点人眼可读、上位机解析简单、调试方便缺点效率低数字要转字符串、传输量大适用调试输出、人机交互、简单上位机协议3. 对比表对比项HEX 数据包文本数据包包头示例0xFF 0xFE包尾示例0xFC\r\n数据形态二进制原始值ASCII 字符人眼可读性✗ 不可读✓ 可读传输效率高低解析难度需处理转义简单直观常见场景指令/传感器调试/上位机四、固定长度可变长度数据区的长度有两种设计方式1. 固定长度数据包数据个数固定接收方数够指定个数即为一包甚至不需要包尾。包头(0xFF) 数据1 数据2 数据3固定3个 ← 数到3个即收完2. 可变长度数据包数据个数不定必须靠包尾来判定一包结束。包头(0xFF) 数据...(数量不定)... 包尾(0xFC) ← 看到包尾才收完长度固定与否决定了拆包逻辑——固定数个数可变看包尾。五、打包与拆包思想1. 发送端打包按协议格式把数据组装再逐个发送填包头 → 填数据 → 填包尾 → 依次 Serial_SendByte 发出2. 接收端拆包接收是边收边判断的用状态机描述┌─────────────────────────────────────────┐ ▼ │ 状态0等包头 ──收到包头──► 状态1收数据 ──收到包尾──► 状态2处理数据 ▲ │ └────────────── 处理完回到状态0等下一包 ◄──────────────┘状态干什么状态0等包头收到包头 → 转状态1否则继续等状态1收数据数据存入缓冲区收到包尾 → 转状态2状态2处理校验/解析缓冲区内容 → 回状态0状态机的好处不阻塞。每来一个字节喂给它它自动流转主程序不用死等整包。3. 数据区出现包头值HEX 包用0xFF当包头若数据里恰好也有0xFF接收方会误判为新包头转义数据中的包头值改成两个字节的转义序列如0xFF→0xFD 0x01或用连续两个字节做包头0xFF 0xFE降低误判概率。六、数据包可能遇到的问题问题现象应对错位一包被拦腰截断包头定位重新同步粘包多包连成一片包头包尾界定边界丢帧中间丢字节靠包头重新同步状态机回到等包头数据区含包头误判为新包双字节包头 / 转义只要状态机设计得当上述问题都能在等包头环节自动恢复同步。七、接收方案总体设计串口中断只负责收字节存起来拆包在专门函数里做状态机串口中断收到字节 → Serial_RxData → 置标志 主循环把 Serial_RxData 喂给 Packet_Receive() 状态机拆包 收完一包 → PacketFlag 置位 → 主循环解析 RxPacket[]状态机三大要素要素代码体现当前状态uint8_t RxState0等包头1收数据2处理数据缓冲RxPacket[PACKET_LEN]存这一包的数据完成标志RxPacketFlag收完一包置 1主循环处理八、方案①HEX 数据包收发协议约定包头1(0xFF) 包头2(0xFE) 数据1~3 包尾(0xFC)/* HEXPacket.c */ #include stm32f10x.h // 包含 STM32 标准库头文件 #include Serial.h // 串口模块见 9-3 笔记 /* 协议定义 */ #define HEAD1 0xFF // 包头1 #define HEAD2 0xFE // 包头2 #define TAIL 0xFC // 包尾 #define PACKET_LEN 3 // 固定数据个数示例 uint8_t RxPacket[PACKET_LEN]; // 接收缓冲本包数据 uint8_t RxPacketFlag 0; // 收包完成标志 uint8_t RxState 0; // 状态机当前状态 /** * brief 发送 HEX 数据包 * param Data 数据指针Length 数据个数 */ void HEXPacket_Send(uint8_t *Data, uint8_t Length) { uint8_t i; Serial_SendByte(HEAD1); // 包头1 Serial_SendByte(HEAD2); // 包头2 for (i 0; i Length; i) { Serial_SendByte(Data[i]); // 数据 } Serial_SendByte(TAIL); // 包尾 } /** * brief 接收状态机每收到一字节调用一次 * param Byte 收到的字节 */ void HEXPacket_Receive(uint8_t Byte) { static uint8_t DataIndex 0; switch (RxState) { case 0: // 等包头1 if (Byte HEAD1) { RxState 1; } break; case 1: // 等包头2 if (Byte HEAD2) { RxState 2; // 包头完整开收数据 DataIndex 0; } else if (Byte ! HEAD1) { RxState 0; } // 非包头重新等 break; case 2: // 收数据 if (Byte TAIL) // 包尾到了 { RxState 3; } else if (DataIndex PACKET_LEN) { RxPacket[DataIndex] Byte; // 存入缓冲 DataIndex; } break; case 3: // 收包完成转主循环处理 RxPacketFlag 1; RxState 0; break; } } /** * brief 查询收包标志 * retval 1收到完整一包 */ uint8_t HEXPacket_GetFlag(void) { if (RxPacketFlag 1) { RxPacketFlag 0; return 1; } return 0; }/* HEXPacket.h */ #ifndef __HEXPACKET_H // 防止头文件重复包含 #define __HEXPACKET_H #include stm32f10x.h extern uint8_t RxPacket[]; // 接收到的数据 extern uint8_t RxPacketFlag; // 收包标志 void HEXPacket_Send(uint8_t *Data, uint8_t Length); // 打包发送 void HEXPacket_Receive(uint8_t Byte); // 状态机收包 uint8_t HEXPacket_GetFlag(void); // 查收包标志 #endif九、方案②文本数据包收发协议约定 数据(ASCII文本不定长) \r \n与 HEX 包区别HEX 包文本包包头0xFF 0xFE包尾0xFC\r\n两个字节数据二进制ASCII收完要加\0变字符串可读性不可读可直接 printf / 显示/* TextPacket.h */ #ifndef __TEXTPACKET_H // 防止头文件重复包含 #define __TEXTPACKET_H #include stm32f10x.h extern uint8_t RxPacket[]; // 接收到的文本含 \0 extern uint8_t RxPacketFlag; // 收包标志 void TextPacket_Send(char *Data); // 打包发送 void TextPacket_Receive(uint8_t Byte); // 状态机收包 uint8_t TextPacket_GetFlag(void); // 查收包标志 #endif十、主函数中断收字节 → 喂状态机/* main.c */ #include stm32f10x.h // 包含 STM32 标准库头文件 #include Delay.h // 延时模块 #include OLED.h // OLED 模块 #include Serial.h // 串口模块9-3含中断接收 #include HEXPacket.h // HEX 包 #include TextPacket.h // 文本包 uint8_t TestData[] {0x01, 0x02, 0x03}; // 测试数据 /** * brief 主函数串口中断收字节 → 主循环喂状态机拆包 */ int main(void) { OLED_Init(); Serial_Init(); // 串口 中断接收 while (1) { // 每 1 秒发一个 HEX 包 HEXPacket_Send(TestData, 3); Delay(1000000); // 中断收到字节 → 喂给两个状态机分别拆 HEX/文本包 if (Serial_GetRxFlag() 1) { HEXPacket_Receive(Serial_RxData); // HEX 包状态机 TextPacket_Receive(Serial_RxData); // 文本包状态机 } // 收到完整 HEX 包显示内容 if (HEXPacket_GetFlag() 1) { OLED_ShowHexNum(1, 1, RxPacket[0], 2); OLED_ShowHexNum(1, 5, RxPacket[1], 2); OLED_ShowHexNum(1, 9, RxPacket[2], 2); } // 收到完整文本包当字符串显示 if (TextPacket_GetFlag() 1) { OLED_ShowString(3, 1, (char *)RxPacket); } } }
返回列表