当前位置: 首页 > news >正文

[STM32]Day9-Part2串口收发数据包

HEX数据包

数据包的作用是把多个字节数据封装起来,方便进行多字节的数据通信。

如何解决数据载荷与包头/包尾重复的问题?可能的方法有:

  • 对数据进行限幅,使数据范围不包括包头包尾,避免重复
  • 使用固定包长的数据包,根据当前包的长度判断是不是包头/包尾
  • 让包头包尾变的更复杂,减小重复概率

固定包长和可变包长的选择:如果可能出现数据载荷与包头包尾重复,需要使用固定包长

文本数据包

在HEX数据包中,数据都是以原始的字节数据呈现的;在文本数据包中,每个字节经过一层编码和译码,以文本形式呈现,但本质上还是字节数据

数据包接收

HEX数据包固定包长

每收到一个字节,程序都会进一遍中断,在中断函数中读取RDR得到数据,然后退出中断,所以每收到一个字节数据,都是一个独立的过程。在程序中,需要设计一个能记住不同状态的机制,在不同状态执行不同的操作,同时进行状态的合理转移,这种设计思维叫做状态机。使用状态机的方法接收数据包,状态转移图如上。

文本数据包可变包长

串口收发固定长度HEX数据包

// Serial.c#include"stm32f10x.h"// Device header#include<stdio.h>// 缓存区数组,存储发送和接收的数据包uint8_tSerial_TxPacket[4];uint8_tSerial_RxPacket[4];uint8_tSerial_RxFlag;voidSerial_Init(void){// 开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);// 配置GPIOGPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;// 上拉输入GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;GPIO_Init(GPIOA,&GPIO_InitStructure);// 配置USARTUSART_InitTypeDef USART_InitStructure;USART_StructInit(&USART_InitStructure);USART_InitStructure.USART_BaudRate=9600;USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;// 不使用流控USART_InitStructure.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;// 收发模式USART_InitStructure.USART_Parity=USART_Parity_No;// 校验位设置:无校验USART_InitStructure.USART_StopBits=USART_StopBits_1;USART_InitStructure.USART_WordLength=USART_WordLength_8b;USART_Init(USART1,&USART_InitStructure);// 配置中断USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);// 开启RXNE到NVIC的输出// 配置NVICNVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;NVIC_Init(&NVIC_InitStructure);// 开启USARTUSART_Cmd(USART1,ENABLE);}// 发送一个字节数据voidSerial_SendByte(uint8_tByte){// 将待发送数据写入TDRUSART_SendData(USART1,Byte);// 等待TDR中数据被转运到移位寄存器while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)!=SET);// 不需要手动清零}// 发送数组voidSerial_SendArray(uint8_t*Array,uint16_tLength){uint16_ti;for(i=0;i<Length;i++){Serial_SendByte(Array[i]);}}// 发送字符串voidSerial_SendString(char*String){uint8_ti;for(i=0;String[i]!='\0';i++){// '\0'为字符串结束字符Serial_SendByte(String[i]);}}// 参数X,Y,返回X的Y次方uint32_tSerial_Pow(uint32_tX,uint32_tY){uint32_tResult=1;uint32_ti;for(i=0;i<Y;i++){Result*=X;}returnResult;}// 发送数字,参数为12345时,接受方在文本模式下查看到12345voidSerial_SendNumber(uint32_tNumber,uint8_tLength){uint8_ti;for(i=0;i<Length;i++){Serial_SendByte(Number/Serial_Pow(10,Length-i-1)%10+'0');// 从高位到低位}}// 重定向fputc到串口,使得printf函数打印到串口intfputc(intch,FILE*f){Serial_SendByte(ch);returnch;}// 发送数据包函数voidSerial_SendPacket(void){// 发送包头Serial_SendByte(0xFF);// 发送数据载荷Serial_SendArray(Serial_TxPacket,4);// 发送包尾Serial_SendByte(0xFE);}uint8_tSerial_GetRxFlag(void){if(Serial_RxFlag==1){Serial_RxFlag=0;return1;}return0;}// 重写中断函数:接收数据包voidUSART1_IRQHandler(void){staticuint8_tRxState=0;// static变量函数第一次进入时初始化一次,退出后仍然有效,类似全局变量,但只能在本函数使用staticuint8_tpRxPacket=0;// 记录当前已经接收数据个数if(USART_GetFlagStatus(USART1,USART_IT_RXNE)==SET){// 读取接收到的数据uint8_tRxData=USART_ReceiveData(USART1);if(RxState==0){if(RxData==0xFF){RxState=1;pRxPacket=0;}}elseif(RxState==1){Serial_RxPacket[pRxPacket]=RxData;pRxPacket++;if(pRxPacket>=4){RxState=2;}}elseif(RxState==2){if(RxData==0xFE){Serial_RxFlag=1;RxState=0;}}USART_ClearITPendingBit(USART1,USART_IT_RXNE);}}// main.c#include"stm32f10x.h"// Device header#include"OLED_Hardware.h"#include"Serial.h"#include"Button.h"uint8_tButtonVal;intmain(void){OLED_Init_H();Serial_Init();Button_Init();OLED_ShowString_H(1,1,"TxPacket");OLED_ShowString_H(3,1,"RxPacket");Serial_TxPacket[0]=0x01;Serial_TxPacket[1]=0x02;Serial_TxPacket[2]=0x03;Serial_TxPacket[3]=0x04;while(1){ButtonVal=Button_Read(Pin_11);// 按键按下->松开,发送数据包if(ButtonVal==1){Serial_TxPacket[0]++;Serial_TxPacket[1]++;Serial_TxPacket[2]++;Serial_TxPacket[3]++;Serial_SendPacket();OLED_ShowHexNum_H(2,1,Serial_TxPacket[0],2);OLED_ShowHexNum_H(2,4,Serial_TxPacket[1],2);OLED_ShowHexNum_H(2,7,Serial_TxPacket[2],2);OLED_ShowHexNum_H(2,10,Serial_TxPacket[3],2);}// 如果收到数据包if(Serial_GetRxFlag()==1){OLED_ShowHexNum_H(4,1,Serial_RxPacket[0],2);OLED_ShowHexNum_H(4,4,Serial_RxPacket[1],2);OLED_ShowHexNum_H(4,7,Serial_RxPacket[2],2);OLED_ShowHexNum_H(4,10,Serial_RxPacket[3],2);}}}

串口收发可变长度文本数据包

// Serial.c#include"stm32f10x.h"// Device header#include<stdio.h>// 缓存区数组,存储接收的数据包charSerial_RxPacket[400];uint8_tSerial_RxFlag;voidSerial_Init(void){// 开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);// 配置GPIOGPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;// 上拉输入GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;GPIO_Init(GPIOA,&GPIO_InitStructure);// 配置USARTUSART_InitTypeDef USART_InitStructure;USART_StructInit(&USART_InitStructure);USART_InitStructure.USART_BaudRate=9600;USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;// 不使用流控USART_InitStructure.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;// 收发模式USART_InitStructure.USART_Parity=USART_Parity_No;// 校验位设置:无校验USART_InitStructure.USART_StopBits=USART_StopBits_1;USART_InitStructure.USART_WordLength=USART_WordLength_8b;USART_Init(USART1,&USART_InitStructure);// 配置中断USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);// 开启RXNE到NVIC的输出// 配置NVICNVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;NVIC_Init(&NVIC_InitStructure);// 开启USARTUSART_Cmd(USART1,ENABLE);}// 发送一个字节数据voidSerial_SendByte(uint8_tByte){// 将待发送数据写入TDRUSART_SendData(USART1,Byte);// 等待TDR中数据被转运到移位寄存器while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)!=SET);// 不需要手动清零}// 发送数组voidSerial_SendArray(uint8_t*Array,uint16_tLength){uint16_ti;for(i=0;i<Length;i++){Serial_SendByte(Array[i]);}}// 发送字符串voidSerial_SendString(char*String){uint8_ti;for(i=0;String[i]!='\0';i++){// '\0'为字符串结束字符Serial_SendByte(String[i]);}}// 参数X,Y,返回X的Y次方uint32_tSerial_Pow(uint32_tX,uint32_tY){uint32_tResult=1;uint32_ti;for(i=0;i<Y;i++){Result*=X;}returnResult;}// 发送数字,参数为12345时,接受方在文本模式下查看到12345voidSerial_SendNumber(uint32_tNumber,uint8_tLength){uint8_ti;for(i=0;i<Length;i++){Serial_SendByte(Number/Serial_Pow(10,Length-i-1)%10+'0');// 从高位到低位}}// 重定向fputc到串口,使得printf函数打印到串口intfputc(intch,FILE*f){Serial_SendByte(ch);returnch;}uint8_tSerial_GetRxFlag(void){if(Serial_RxFlag==1){Serial_RxFlag=0;return1;}return0;}// 重写中断函数:接收数据包voidUSART1_IRQHandler(void){staticuint8_tRxState=0;// static变量函数第一次进入时初始化一次,退出后仍然有效,类似全局变量,但只能在本函数使用staticuint8_tpRxPacket=0;// 记录当前已经接收数据个数if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET){// 读取接收到的数据uint8_tRxData=USART_ReceiveData(USART1);if(RxState==0){if(RxData=='@'){RxState=1;pRxPacket=0;}}elseif(RxState==1){// 如果遇到第一个包尾\r,转移到状态2if(RxData=='\r'){RxState=2;}else{Serial_RxPacket[pRxPacket]=RxData;pRxPacket++;}}elseif(RxState==2){if(RxData=='\n'){Serial_RxFlag=1;// 收到一个完整数据包Serial_RxPacket[pRxPacket]='\0';// 给字符串增加一个结尾RxState=0;// 转移到状态0,等待下一个包头}}USART_ClearITPendingBit(USART1,USART_IT_RXNE);}}// main.c#include"stm32f10x.h"// Device header#include"OLED_Hardware.h"#include"Serial.h"#include"LED.h"#include<string.h>// 调用字符串比较函数intmain(void){LED_Init();OLED_Init_H();Serial_Init();OLED_ShowString_H(1,1,"TxPacket");OLED_ShowString_H(3,1,"RxPacket");while(1){if(Serial_GetRxFlag()==1){// 先清除低4行,防止之前更长字符串残留OLED_ShowString_H(4,1," ");// 16个空格实现清空效果OLED_ShowString_H(4,1,Serial_RxPacket);if(strcmp(Serial_RxPacket,"LED_ON")==0){// 如果收到的字符串是LED_ON,点亮LEDLED_On(Pin_1);Serial_SendString("LED_ON_OK\r\n");OLED_ShowString_H(2,1," ");OLED_ShowString_H(2,1,"LED_ON_OK");}elseif(strcmp(Serial_RxPacket,"LED_OFF")==0){// 如果收到的字符串是LED_OFF,关闭LEDLED_Off(Pin_1);Serial_SendString("LED_OFF_OK\r\n");OLED_ShowString_H(2,1," ");OLED_ShowString_H(2,1,"LED_OFF_OK");}else{Serial_SendString("ERROR_COMMAND!\r\n");OLED_ShowString_H(2,1," ");OLED_ShowString_H(2,1,"ERROR_COMMAND!");}}}}

存在的问题:如果连续发送数据包,程序处理不及时,可能导致数据包错位。由于每个文本数据包是连续的,如果产生错位后果比较严重。可以做以下修改:

// USART1_IRQHandler(void)...if(RxState==0){if(RxData=='@'&&Serial_RxFlag==0){// 包头+RxState=1;pRxPacket=0;}}elseif......// 删除函数uint8_tSerial_GetRxFlag(void){if(Serial_RxFlag==1){Serial_RxFlag=0;return1;}return0;}// main.c...while(1){// 如果Serial_RxFlag为1,说明收到数据包,开始接收if(Serial_RxFlag==1){// 先清除低4行,防止之前更长字符串残留OLED_ShowString_H(4,1," ");// 16个空格实现清空效果OLED_ShowString_H(4,1,Serial_RxPacket);if(strcmp(Serial_RxPacket,"LED_ON")==0){// 如果收到的字符串是LED_ON,点亮LEDLED_On(Pin_1);Serial_SendString("LED_ON_OK\r\n");OLED_ShowString_H(2,1," ");OLED_ShowString_H(2,1,"LED_ON_OK");}elseif(strcmp(Serial_RxPacket,"LED_OFF")==0){// 如果收到的字符串是LED_OFF,关闭LEDLED_Off(Pin_1);Serial_SendString("LED_OFF_OK\r\n");OLED_ShowString_H(2,1," ");OLED_ShowString_H(2,1,"LED_OFF_OK");}else{Serial_SendString("ERROR_COMMAND!\r\n");OLED_ShowString_H(2,1," ");OLED_ShowString_H(2,1,"ERROR_COMMAND!");}// 所有接收完成后,Serial_RxFlag置为0Serial_RxFlag=0;}}
http://www.zskr.cn/news/1492031.html

相关文章:

  • 多维聚合本质:维度建模、粒度对齐与语义锚点
  • N皇后遗传算法实战:Python手写GA求解100皇后问题
  • 别再只接LCD了!解锁STM32 FMC的隐藏玩法:驱动AD7606、OLED等并行总线外设的完整指南
  • 终极指南:3步永久保存微信聊天记录的完整方法
  • 性价比高的绵阳酒店服务商哪个靠谱
  • AI技术写作规范:如何避免虚构与失实内容
  • [UEFI架构]必不可少的SecurityArch
  • Horizon UAG部署后连接服务器还是红叉?别慌,教你一步步排查(从日志分析到FQDN解析)
  • SolidWorks许可回收误杀率,对比三款横评
  • 2026长治市黄金回收铂金回收白银回收彩金回收机构实力:项链+戒指+手镯+吊坠专业鉴定上门服务及联系方式推荐 - 亦辰小黄鸭
  • 别再只用print了!Python格式化输出M和N运算结果的3种高级技巧
  • 生成式AI发展现状与中长期技术演进趋势分析
  • 《医院HIS药房模块实战避坑系列》之一:月中药品调价+跨价退药账务处理全解析
  • 跨境多店铺管理混乱,先排查浏览器环境边界
  • 别再为Aspose.Words水印发愁了!手把手教你用JD-GUI搞定19.1版本本地化部署
  • 从Mathtype到BibTeX:让你的IEEE LaTeX写作效率翻倍的几个隐藏技巧
  • PostgreSQL 技术日报 (6月8日)|索引预取迭代,AI 安全功能上新
  • 别再死记硬背了!用TensorFlow 2.x手把手复现Google的WideDeep推荐模型
  • C语言介绍——通用的计算机编程语言
  • 云尖信息亮相英特尔至强6+发布会暨数据中心创新日,以全栈能力构筑Agentic AI时代新算力底座
  • 从DH1到3DH5:一文读懂蓝牙射频测试中那些让人头疼的数据包与调制方式
  • 用C语言实战:最小公倍数在嵌入式编程和单片机开发中的一个具体应用案例
  • 告别均匀采样!用PER优先经验回放,让你的DQN在Atari游戏上快人一步
  • Python小说章节自动采集入库工具:含MySQL连接池、去重建表与配置化部署
  • 2026年6月岳阳楼区流量卡“闭眼入”指南:39元电信神卡杀疯了!
  • LLM多智能体语义传播监控与漂移治理方法
  • UniVidX——基于扩散先验的统一多模态视频生成框架
  • 手机拍证件照哪个好2026年专业证件照工具推荐
  • 告别迷茫!工业组态软件选型指南:从Qt、C#到Web,5分钟帮你找到最适合的技术栈
  • 基于STC89C52的智能洗衣机控制原型:三档面料适配+LCD实时显示+Proteus可运行仿真工程