STM32 串口收发HEX数据包

[复制链接]
发表于 2025-12-8 10:16:52 | 显示全部楼层 |阅读模式
单片机学习!
目次
媒介
一、HEX数据包格式
二、串口收发HEX数据包代码
三、代码剖析
3.1 数据包发送
3.2 标志位扫除
3.3 数据包吸收
总结


媒介

        本文先容了串口收发HEX数据包步调计划的思绪并详解代码作用。

一、HEX数据包格式

        收发HEX数据包中HEX数据包的格式的界说如下图所示:固定包长,含包头包尾,此中包头为FF,载荷数据固定4字节,包尾为FE。


二、串口收发HEX数据包代码

总代码示例:
  1. #include "stm32f10x.h"                  // Device header
  2. #include <stdio.h>
  3. uint8_t Serial_TxPacket[4];//发送缓存区
  4. uint8_t Serial_RxPacket[4];//接收缓存区
  5. uint8_t Serial_RxFlag;//标志位
  6. void Serial_Init(void)
  7. {
  8.         //第一步开启时钟
  9.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启USART1的时钟
  10.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIO的时钟
  11.        
  12.         //第二步初始化GPIO引脚
  13.         GPIO_InitTypeDef GPIO_InitStruct;
  14.         GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AF_PP;//引脚模式
  15.         GPIO_InitStruct.GPIO_Pin= GPIO_Pin_9;//引脚选择Pin_9
  16.         GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
  17.         GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA
  18.         GPIO_InitStruct.GPIO_Mode= GPIO_Mode_IPU;//引脚模式
  19.         GPIO_InitStruct.GPIO_Pin= GPIO_Pin_10;//引脚选择Pin_10
  20.         GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
  21.         GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA
  22.        
  23.        
  24.         //第三步初始化USART
  25.         USART_InitTypeDef USART_InitStructure;
  26.         USART_InitStructure.USART_BaudRate = 9600;//波特率
  27.         USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制
  28.         USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//串口模式
  29.         USART_InitStructure.USART_Parity = USART_Parity_No;//校验位
  30.         USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
  31.         USART_InitStructure.USART_WordLength =USART_WordLength_8b; //字长
  32.         USART_Init(USART1,&USART_InitStructure);
  33.        
  34.     //配置中断
  35.         USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
  36.        
  37.         NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  38.         NVIC_InitTypeDef NVIC_InitStructure;
  39.         NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//中断通道
  40.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  41.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1;
  42.         NVIC_InitStructure.NVIC_IRQChannelSubPriority =1;
  43.         NVIC_Init(&NVIC_InitStructure);
  44.        
  45.         USART_Cmd(USART1,ENABLE);
  46.        
  47. }
  48. //发送数据的函数
  49. void Serial_SendByte(uint8_t Byte)
  50. {
  51.         USART_SendData(USART1,Byte);
  52.         while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
  53. }
  54. //发送一个数组的函数
  55. void Serial_SendArray(uint8_t *Array,uint16_t Length)
  56. {
  57.         uint16_t i;
  58.         for(i = 0 ; i < Length ; i++)
  59.         {
  60.                 Serial_SendByte(Array[i]);
  61.         }
  62. }
  63. //发送字符串
  64. void Serial_SendString(char *String)
  65. {
  66.         uint8_t i;
  67.         for(i = 0;String[i] != '\0';i++)
  68.         {
  69.                 Serial_SendByte(String[i]);
  70.         }
  71. }
  72. //这个函数的返回值是X的Y次方
  73. uint32_t Serial_Pow(uint32_t X,uint32_t Y)
  74. {
  75.         uint32_t Result = 1;
  76.         while(Y--)
  77.         {
  78.                 Result *= X;
  79.         }
  80.         return Result;
  81. }
  82. //函数可以将发送的数字显示为字符串的形式
  83. void Serial_SendNumber(uint32_t Number,uint8_t Length)
  84. {
  85.         uint8_t i;
  86.         for(i = 0;i < Length;i++)
  87.         {
  88.                 Serial_SendByte(Number / Serial_Pow(10,Length - i - 1) %10 + '0');
  89.         }
  90. }
  91. //printf函数重定向到串口
  92. int fputc(int ch,FILE *f)
  93. {
  94.         Serial_SendByte(ch);
  95.         return ch;
  96. }
  97. //调用这个函数,TxPacket数组的4个数据就会自动加上包头包尾发送出去
  98. void Serial_SendPacket(void)
  99. {
  100.         Serial_SendByte(0xFF);//发送包头0xFF
  101.         Serial_SendArray(Serial_TxPacket,4);//参数给Serial_TxPacket,长度4,这样就可以依次把4个载荷数据发出去了。
  102.         Serial_SendByte(0xFE);//发送包尾0xFE
  103. }
  104. //函数实现一个Serial_RxData变量读后自动清除标志位Serial_RxFlag的功能
  105. uint8_t Serial_GetRxFlag(void)
  106. {
  107.         if(Serial_RxFlag == 1)
  108.         {
  109.                 Serial_RxFlag = 0;
  110.                 return 1;
  111.         }
  112.         return 0;
  113. }
  114. //中断接收,执行状态机逻辑函数
  115. void USART1_IRQHandler(void)
  116. {
  117.         static uint8_t RxState = 0;//作为状态变量S
  118.         static uint8_t pRxPacket = 0;//这个静态变量用于指示接收到数据包中哪一个数据了,最开始默认为0
  119.         //先判断标志位
  120.         if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)//如果RXNE确实置1了,就进入if
  121.         {
  122.                 //首先获取一下RxData
  123.                 uint8_t RxData = USART_ReceiveData(USART1);
  124.                
  125.                 if(RxState == 0)//等待包头
  126.                 {
  127.                         if(RxData == 0xFF)
  128.                         {
  129.                                 RxState = 1;
  130.                                 pRxPacket = 0;
  131.                         }
  132.                 }
  133.                 else if(RxState == 1)//接收数据
  134.                 {
  135.                         Serial_RxPacket[pRxPacket] = RxData;
  136.                         pRxPacket ++;
  137.                         if(pRxPacket >= 4)
  138.                         {
  139.                                 RxState = 2;
  140.                         }
  141.                 }
  142.                 else if(RxState == 2)//等待包尾
  143.                 {
  144.                         if(RxData == 0xFE)
  145.                         {
  146.                                 RxState = 0;
  147.                                 Serial_RxFlag = 1;
  148.                         }
  149.                 }
  150.                
  151.                         USART_ClearITPendingBit(USART1,USART_IT_RXNE);
  152.         }
  153. }
复制代码


        串口设置部分和数据发送、吸收的代码详解可以看前两篇博文:
STM32 USART串口发送_串口发送代码-CSDN博客
https://blog.csdn.net/Echo_cy_/article/details/142794600?spm=1001.2014.3001.5501
STM32 USART串口吸收_stm32 uart发送数据-CSDN博客
https://blog.csdn.net/Echo_cy_/article/details/143817933?spm=1001.2014.3001.5501本文只分析新计划的收发HEX数据包函数。


三、代码剖析

        步调最前面为了收发数据包,先界说两个缓存区的数组和一个标志位。
  1. uint8_t Serial_TxPacket[4];
  2. uint8_t Serial_RxPacket[4];
  3. uint8_t Serial_RxFlag;
复制代码

        发送缓存区Serial_TxPacket,数据个数为4个:
  1. uint8_t Serial_TxPacket[4];
复制代码

        吸收缓存区Serial_RxPacket,数据个数为4个,这4个数据只存储发送或吸收的载荷数据,包头包尾就不存了:
  1. uint8_t Serial_RxPacket[4];
复制代码

        自界说的标志位,如果收到一个数据包,就置Serial_RxFlag为1:
  1. uint8_t Serial_RxFlag;
复制代码


3.1 数据包发送

        调用Serial_SendPacket这个函数,Serial_TxPacket数组的4个数据就会主动加上包头包尾发送出去:
  1. void Serial_SendPacket(void)
  2. {
  3.     Serial_SendByte(0xFF);
  4.     Serial_SendArray(Serial_TxPacket,4);
  5.     Serial_SendByte(0xFE);
  6. }
复制代码

        发送包头0xFF:
  1.     Serial_SendByte(0xFF);
复制代码

        Serial_SendArray函数,参数给Serial_TxPacket,长度4,如许就可以依次把4个载荷数据发出去了。
  1.     Serial_SendArray(Serial_TxPacket,4);
复制代码

        发送包尾0xFE:
  1.     Serial_SendByte(0xFE);
复制代码

3.2 标志位扫除

        Serial_GetRxFlag函数实现一个Serial_RxData变量读后主动扫除标志位Serial_RxFlag的功能
  1. uint8_t Serial_GetRxFlag(void)
  2. {
  3.     if(Serial_RxFlag == 1)
  4.     {
  5.         Serial_RxFlag = 0;
  6.         return 1;
  7.     }
  8.     return 0;
  9. }
复制代码

3.3 数据包吸收

        在克制函数USART1_IRQHandler里须要用状态机来实行吸收逻辑,吸收数据包,然后把载荷数据存在Serial_RxPacket数组里。
        根据状态转移图起主要界说一个标志当前状态的变量S,在克制函数内里界说一个静态变量。


代码示例:
  1. //中断接收,执行状态机逻辑函数
  2. void USART1_IRQHandler(void)
  3. {
  4.         static uint8_t RxState = 0;//作为状态变量S
  5.         static uint8_t pRxPacket = 0;//这个静态变量用于指示接收到数据包中哪一个数据了,最开始默认为0
  6.         //先判断标志位
  7.         if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)//如果RXNE确实置1了,就进入if
  8.         {
  9.                 //首先获取一下RxData
  10.                 uint8_t RxData = USART_ReceiveData(USART1);
  11.                
  12.                 if(RxState == 0)//等待包头
  13.                 {
  14.                         if(RxData == 0xFF)
  15.                         {
  16.                                 RxState = 1;
  17.                                 pRxPacket = 0;
  18.                         }
  19.                 }
  20.                 else if(RxState == 1)//接收数据
  21.                 {
  22.                         Serial_RxPacket[pRxPacket] = RxData;
  23.                         pRxPacket ++;
  24.                         if(pRxPacket >= 4)
  25.                         {
  26.                                 RxState = 2;
  27.                         }
  28.                 }
  29.                 else if(RxState == 2)//等待包尾
  30.                 {
  31.                         if(RxData == 0xFE)
  32.                         {
  33.                                 RxState = 0;
  34.                                 Serial_RxFlag = 1;
  35.                         }
  36.                 }
  37.                
  38.                         USART_ClearITPendingBit(USART1,USART_IT_RXNE);
  39.         }
  40. }
复制代码
        留意要用else if,如果只用三个并列的if大概会在状态转移的时间出现题目。比如在状态0,须要转移到状态1,就置RxState=1,效果就会造成下面状态1的条件就立马满意了,如许会出现一连两个if都同时创建的情况,就不符合实行逻辑了。以是这里要使用else if,包管每次进状态机代码之后只能选择实行此中一个状态的代码。大概用switch case语句也可以包管只有一个条件满意。写好状态选择的部分,就可以依次写每个状态实行的操纵逻辑和状态转移条件了。

紧张变量:        
        USART1_IRQHandler克制函数是把数据举行了一次转存,终极照旧要扫描查询Serial_RxFlag来吸收数据。
        RxState这个静态变量类似于全局变量,函数进入只会初始化一次为0,在函数退出后,数据仍旧有用。与全局变量差别的是,静态变量只能在本函数使用。这里就用RxState当做状态变量S,根据状态转换图,三个状态S分别为0、1、2,以是在if语句里根据RxState的差别,须要进入差别的处置惩罚步调。
        pRxPacket这个静态变量用于指示吸收到数据包中哪一个数据了,最开始默以为0.

        克制函数先判定标志位,如果RXNE确实置1了,就进入if 。
  1.  if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
  2.     {
  3.         ...;
  4.     }
复制代码

        在if代码框里起首获取一下RxData。
  1.         uint8_t RxData = USART_ReceiveData(USART1);
复制代码
        接着就是三个状态的条件判定和相应状态下的步调逻辑。
1.等候包头
  1.                 if(RxState == 0)//等待包头
  2.                 {
  3.                         if(RxData == 0xFF)
  4.                         {
  5.                                 RxState = 1;
  6.                                 pRxPacket = 0;
  7.                         }
  8.                 }
复制代码
        如果RxData 收到包头,那就可以转移状态RxState=1;如果没有收到0xFF就不转移状态。

2.吸收数据
        吸收数据这里要依次吸收4个数据,存在Serial_RxPacket数组里,以是还须要一个变量来记一下继承了几个,相当于吸收数据的位置编码,可以在界说一个静态变量pRxPacket在表面,最开始默以为0.
  1.                 else if(RxState == 1)//接收数据
  2.                 {
  3.                         Serial_RxPacket[pRxPacket] = RxData;
  4.                         pRxPacket ++;//接收数据后位置编码就自增,指示接收下一个位置的数据。
  5.                         if(pRxPacket >= 4)//4个载荷数据已经收完了,这时就可以转移到下一个状态了。同时对pRxPacket清0,为下次接收准备,可以在状态0转移到状态1时提前清一个0.
  6.                         {
  7.                                 RxState = 2;
  8.                         }
  9.                 }
复制代码
        此代码逻辑是,每进一次吸收数据状态就转存一次缓存数据,同时存的位置后移也就是静态变量pRxPacket++。当4个载荷数据已经收完了,这时就可以转移到下一个状态。同时对pRxPacket清0,为下次吸收预备,可以在状态0转移到状态1时提前清一个0.

3.等候包尾
        如果收到包尾,那就可以回到最初的状态RxState=0,同时为了表现一个数据包吸收到了,可以置一个吸收标志位Serial_RxFlag=1;如果没有收到0xFE就时还充公到包尾,也不做处置惩罚,仍旧在这个状态等候包尾。
  1. else if(RxState == 2)//等待包尾
  2.                 {
  3.                         if(RxData == 0xFE)
  4.                         {
  5.                                 RxState = 0;
  6.                                 Serial_RxFlag = 1;
  7.                         }
  8.                 }
复制代码

        这里调用USART_ClearITPendingBit函数,直接扫除一下标志位:
  1.    USART_ClearITPendingBit(USART1,USART_IT_RXNE);
复制代码

        以上克制吸收和变量的封装就完成了!

        这个步调还隐蔽有一个题目须要留意。Serial_RxPacket数组是一个同时被写入又同时被读出的数组,在克制函数里会依次写入,在主函数里又会依次读出。这会造成一个题目,就是数据包之间大概会混在一起。比如读出的过程太慢了,前两个数据刚读出来,等了一会儿才继承今后读取,那这时反面的数据就大概会革新为下一个数据包的数据,也就是读出的数据大概一部分属于上一个数据包,另一部分属于下一个数据包。
        题目标办理方法可以是在吸收部分参加判定,就是在每个数据包读取处置惩罚完毕后,再吸收下一个数据包。
        固然这个题目标发生也分情况。如果是HEX数据包,多是用于传输各种传感器的每个独立数据,比如陀螺仪的X、Y、Z轴数据,温湿度数据等等,这种相邻数据包之间的数据具有一连性。如许纵然相邻数据包混在一起了,也不要紧。



总结

        以上就是本日要讲的内容,本文仅仅简单先容了串口收发HEX数据包步调计划的思绪并详解了一些计划代码的细节。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表