当前位置: 首页 > 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.jsqmd.com/news/980305/

相关文章:

  • Codex桌面版接入Deepseek api key教程
  • LLM生产系统合规落地:分层治理架构与工程实践
  • 多维聚合本质:维度建模、粒度对齐与语义锚点
  • 通义DeepResearch:面向产业研究的可追溯深度推理引擎
  • N皇后遗传算法实战:Python手写GA求解100皇后问题
  • 终极指南:3步永久保存微信聊天记录的完整方法
  • 性价比高的绵阳酒店服务商哪个靠谱
  • 2026长沙市黄金回收铂金回收白银回收彩金回收机构实力:项链+戒指+手镯+吊坠专业鉴定上门服务及联系方式推荐 - 亦辰小黄鸭
  • 5分钟掌握华硕笔记本性能调优神器:G-Helper完全解决方案
  • 别再只接LCD了!解锁STM32 FMC的隐藏玩法:驱动AD7606、OLED等并行总线外设的完整指南
  • 告别锚框!用CenterPoint搞定自动驾驶3D检测,Waymo/NuScenes双榜第一的保姆级解读
  • [UEFI架构]必不可少的SecurityArch
  • AI技术写作规范:如何避免虚构与失实内容
  • 如何轻松掌控AMD Ryzen处理器?这款免费调试工具让你成为硬件专家!
  • 【C++初阶】析构函数超详解(误区、语法、调用时机、析构顺序)
  • Horizon UAG部署后连接服务器还是红叉?别慌,教你一步步排查(从日志分析到FQDN解析)
  • 萤石 ERTC 如何一站式解决智能家居各类通话需求?
  • SolidWorks许可回收误杀率,对比三款横评
  • 计算机毕业设计之django基于Python的bs架构的进门审批管理系统设计与开发
  • 2026长治市黄金回收铂金回收白银回收彩金回收机构实力:项链+戒指+手镯+吊坠专业鉴定上门服务及联系方式推荐 - 亦辰小黄鸭
  • Web数据供应链:从爬虫到AI可信数据资产的四层架构
  • 每日一Go-76(架构篇)|多集群部署 / 容灾 / Failover / Backup / 热迁移
  • 别再只搜Star数了!用GitHub Topics和高级搜索,5分钟找到真正适合你的开源项目
  • 7.5元包邮的RC522读卡器,手把手教你用Arduino Uno复制小区门禁卡(附完整接线图与代码)
  • Python新手必看:用input()和eval()处理用户输入,一个函数搞定五种数学运算
  • 生成式AI发展现状与中长期技术演进趋势分析
  • 《医院HIS药房模块实战避坑系列》之一:月中药品调价+跨价退药账务处理全解析
  • 别再只用print了!Python格式化输出M和N运算结果的3种高级技巧
  • 本地运行的QQ账号绑定信息扫描器(2025绿色单文件版)
  • 企业AI知识库开发服务商推荐,2026年最新测评