单片机学习! 目次
媒介
一、HEX数据包格式
二、串口收发HEX数据包代码
三、代码剖析
3.1 数据包发送
3.2 标志位扫除
3.3 数据包吸收
总结
媒介
本文先容了串口收发HEX数据包步调计划的思绪并详解代码作用。
一、HEX数据包格式
收发HEX数据包中HEX数据包的格式的界说如下图所示:固定包长,含包头包尾,此中包头为FF,载荷数据固定4字节,包尾为FE。
二、串口收发HEX数据包代码
总代码示例:
- #include "stm32f10x.h" // Device header
- #include <stdio.h>
- uint8_t Serial_TxPacket[4];//发送缓存区
- uint8_t Serial_RxPacket[4];//接收缓存区
- uint8_t Serial_RxFlag;//标志位
- void Serial_Init(void)
- {
- //第一步开启时钟
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启USART1的时钟
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIO的时钟
-
- //第二步初始化GPIO引脚
- GPIO_InitTypeDef GPIO_InitStruct;
- GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AF_PP;//引脚模式
- GPIO_InitStruct.GPIO_Pin= GPIO_Pin_9;//引脚选择Pin_9
- GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
- GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA
- GPIO_InitStruct.GPIO_Mode= GPIO_Mode_IPU;//引脚模式
- GPIO_InitStruct.GPIO_Pin= GPIO_Pin_10;//引脚选择Pin_10
- GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
- GPIO_Init(GPIOA,&GPIO_InitStruct);//初始化GPIOA
-
-
- //第三步初始化USART
- USART_InitTypeDef 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);
-
- NVIC_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);
-
- USART_Cmd(USART1,ENABLE);
-
- }
- //发送数据的函数
- void Serial_SendByte(uint8_t Byte)
- {
- USART_SendData(USART1,Byte);
- while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
- }
- //发送一个数组的函数
- void Serial_SendArray(uint8_t *Array,uint16_t Length)
- {
- uint16_t i;
- for(i = 0 ; i < Length ; i++)
- {
- Serial_SendByte(Array[i]);
- }
- }
- //发送字符串
- void Serial_SendString(char *String)
- {
- uint8_t i;
- for(i = 0;String[i] != '\0';i++)
- {
- Serial_SendByte(String[i]);
- }
- }
- //这个函数的返回值是X的Y次方
- uint32_t Serial_Pow(uint32_t X,uint32_t Y)
- {
- uint32_t Result = 1;
- while(Y--)
- {
- Result *= X;
- }
- return Result;
- }
- //函数可以将发送的数字显示为字符串的形式
- void Serial_SendNumber(uint32_t Number,uint8_t Length)
- {
- uint8_t i;
- for(i = 0;i < Length;i++)
- {
- Serial_SendByte(Number / Serial_Pow(10,Length - i - 1) %10 + '0');
- }
- }
- //printf函数重定向到串口
- int fputc(int ch,FILE *f)
- {
- Serial_SendByte(ch);
- return ch;
- }
- //调用这个函数,TxPacket数组的4个数据就会自动加上包头包尾发送出去
- void Serial_SendPacket(void)
- {
- Serial_SendByte(0xFF);//发送包头0xFF
- Serial_SendArray(Serial_TxPacket,4);//参数给Serial_TxPacket,长度4,这样就可以依次把4个载荷数据发出去了。
- Serial_SendByte(0xFE);//发送包尾0xFE
- }
- //函数实现一个Serial_RxData变量读后自动清除标志位Serial_RxFlag的功能
- uint8_t Serial_GetRxFlag(void)
- {
- if(Serial_RxFlag == 1)
- {
- Serial_RxFlag = 0;
- return 1;
- }
- return 0;
- }
- //中断接收,执行状态机逻辑函数
- void USART1_IRQHandler(void)
- {
- static uint8_t RxState = 0;//作为状态变量S
- static uint8_t pRxPacket = 0;//这个静态变量用于指示接收到数据包中哪一个数据了,最开始默认为0
- //先判断标志位
- if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)//如果RXNE确实置1了,就进入if
- {
- //首先获取一下RxData
- uint8_t RxData = USART_ReceiveData(USART1);
-
- if(RxState == 0)//等待包头
- {
- if(RxData == 0xFF)
- {
- RxState = 1;
- pRxPacket = 0;
- }
- }
- else if(RxState == 1)//接收数据
- {
- Serial_RxPacket[pRxPacket] = RxData;
- pRxPacket ++;
- if(pRxPacket >= 4)
- {
- RxState = 2;
- }
- }
- else if(RxState == 2)//等待包尾
- {
- if(RxData == 0xFE)
- {
- RxState = 0;
- Serial_RxFlag = 1;
- }
- }
-
- USART_ClearITPendingBit(USART1,USART_IT_RXNE);
- }
- }
复制代码
串口设置部分和数据发送、吸收的代码详解可以看前两篇博文:
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数据包函数。
三、代码剖析
步调最前面为了收发数据包,先界说两个缓存区的数组和一个标志位。
- uint8_t Serial_TxPacket[4];
- uint8_t Serial_RxPacket[4];
- uint8_t Serial_RxFlag;
复制代码 发送缓存区Serial_TxPacket,数据个数为4个:
- uint8_t Serial_TxPacket[4];
复制代码 吸收缓存区Serial_RxPacket,数据个数为4个,这4个数据只存储发送或吸收的载荷数据,包头包尾就不存了:
- uint8_t Serial_RxPacket[4];
复制代码 自界说的标志位,如果收到一个数据包,就置Serial_RxFlag为1:
3.1 数据包发送
调用Serial_SendPacket这个函数,Serial_TxPacket数组的4个数据就会主动加上包头包尾发送出去:
- void Serial_SendPacket(void)
- {
- Serial_SendByte(0xFF);
- Serial_SendArray(Serial_TxPacket,4);
- Serial_SendByte(0xFE);
- }
复制代码 发送包头0xFF:
Serial_SendArray函数,参数给Serial_TxPacket,长度4,如许就可以依次把4个载荷数据发出去了。
- Serial_SendArray(Serial_TxPacket,4);
复制代码 发送包尾0xFE:
3.2 标志位扫除
Serial_GetRxFlag函数实现一个Serial_RxData变量读后主动扫除标志位Serial_RxFlag的功能。
- uint8_t Serial_GetRxFlag(void)
- {
- if(Serial_RxFlag == 1)
- {
- Serial_RxFlag = 0;
- return 1;
- }
- return 0;
- }
复制代码
3.3 数据包吸收
在克制函数USART1_IRQHandler里须要用状态机来实行吸收逻辑,吸收数据包,然后把载荷数据存在Serial_RxPacket数组里。
根据状态转移图起主要界说一个标志当前状态的变量S,在克制函数内里界说一个静态变量。
代码示例:
- //中断接收,执行状态机逻辑函数
- void USART1_IRQHandler(void)
- {
- static uint8_t RxState = 0;//作为状态变量S
- static uint8_t pRxPacket = 0;//这个静态变量用于指示接收到数据包中哪一个数据了,最开始默认为0
- //先判断标志位
- if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)//如果RXNE确实置1了,就进入if
- {
- //首先获取一下RxData
- uint8_t RxData = USART_ReceiveData(USART1);
-
- if(RxState == 0)//等待包头
- {
- if(RxData == 0xFF)
- {
- RxState = 1;
- pRxPacket = 0;
- }
- }
- else if(RxState == 1)//接收数据
- {
- Serial_RxPacket[pRxPacket] = RxData;
- pRxPacket ++;
- if(pRxPacket >= 4)
- {
- RxState = 2;
- }
- }
- else if(RxState == 2)//等待包尾
- {
- if(RxData == 0xFE)
- {
- RxState = 0;
- Serial_RxFlag = 1;
- }
- }
-
- USART_ClearITPendingBit(USART1,USART_IT_RXNE);
- }
- }
复制代码 留意要用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 。
- if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
- {
- ...;
- }
复制代码 在if代码框里起首获取一下RxData。
- uint8_t RxData = USART_ReceiveData(USART1);
复制代码 接着就是三个状态的条件判定和相应状态下的步调逻辑。
1.等候包头
- if(RxState == 0)//等待包头
- {
- if(RxData == 0xFF)
- {
- RxState = 1;
- pRxPacket = 0;
- }
- }
复制代码 如果RxData 收到包头,那就可以转移状态RxState=1;如果没有收到0xFF就不转移状态。
2.吸收数据
吸收数据这里要依次吸收4个数据,存在Serial_RxPacket数组里,以是还须要一个变量来记一下继承了几个,相当于吸收数据的位置编码,可以在界说一个静态变量pRxPacket在表面,最开始默以为0.
- else if(RxState == 1)//接收数据
- {
- Serial_RxPacket[pRxPacket] = RxData;
- pRxPacket ++;//接收数据后位置编码就自增,指示接收下一个位置的数据。
- if(pRxPacket >= 4)//4个载荷数据已经收完了,这时就可以转移到下一个状态了。同时对pRxPacket清0,为下次接收准备,可以在状态0转移到状态1时提前清一个0.
- {
- RxState = 2;
- }
- }
复制代码 此代码逻辑是,每进一次吸收数据状态就转存一次缓存数据,同时存的位置后移也就是静态变量pRxPacket++。当4个载荷数据已经收完了,这时就可以转移到下一个状态。同时对pRxPacket清0,为下次吸收预备,可以在状态0转移到状态1时提前清一个0.
3.等候包尾
如果收到包尾,那就可以回到最初的状态RxState=0,同时为了表现一个数据包吸收到了,可以置一个吸收标志位Serial_RxFlag=1;如果没有收到0xFE就时还充公到包尾,也不做处置惩罚,仍旧在这个状态等候包尾。
- else if(RxState == 2)//等待包尾
- {
- if(RxData == 0xFE)
- {
- RxState = 0;
- Serial_RxFlag = 1;
- }
- }
复制代码
这里调用USART_ClearITPendingBit函数,直接扫除一下标志位:
- USART_ClearITPendingBit(USART1,USART_IT_RXNE);
复制代码 以上克制吸收和变量的封装就完成了!
这个步调还隐蔽有一个题目须要留意。Serial_RxPacket数组是一个同时被写入又同时被读出的数组,在克制函数里会依次写入,在主函数里又会依次读出。这会造成一个题目,就是数据包之间大概会混在一起。比如读出的过程太慢了,前两个数据刚读出来,等了一会儿才继承今后读取,那这时反面的数据就大概会革新为下一个数据包的数据,也就是读出的数据大概一部分属于上一个数据包,另一部分属于下一个数据包。
题目标办理方法可以是在吸收部分参加判定,就是在每个数据包读取处置惩罚完毕后,再吸收下一个数据包。
固然这个题目标发生也分情况。如果是HEX数据包,多是用于传输各种传感器的每个独立数据,比如陀螺仪的X、Y、Z轴数据,温湿度数据等等,这种相邻数据包之间的数据具有一连性。如许纵然相邻数据包混在一起了,也不要紧。
总结
以上就是本日要讲的内容,本文仅仅简单先容了串口收发HEX数据包步调计划的思绪并详解了一些计划代码的细节。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |