自学内容网 自学内容网

STM32 串口收发HEX数据包

单片机学习!

目录

前言

一、HEX数据包格式

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

三、代码解析

3.1 数据包发送

3.2 标志位清除

3.3 数据包接收

总结


前言

        本文介绍了串口收发HEX数据包程序设计的思路并详解代码作用。


一、HEX数据包格式

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

dbae400fe79d43209889f656b3ca5f09.png

二、串口收发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博客icon-default.png?t=O83Ahttps://blog.csdn.net/Echo_cy_/article/details/142794600?spm=1001.2014.3001.5501

STM32 USART串口接收_stm32 uart发送数据-CSDN博客icon-default.png?t=O83Ahttps://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:

uint8_t Serial_RxFlag;

3.1 数据包发送

        调用Serial_SendPacket这个函数,Serial_TxPacket数组的4个数据就会自动加上包头包尾发送出去:

void Serial_SendPacket(void)
{
    Serial_SendByte(0xFF);
    Serial_SendArray(Serial_TxPacket,4);
    Serial_SendByte(0xFE);
}

        发送包头0xFF:

    Serial_SendByte(0xFF);

        Serial_SendArray函数,参数给Serial_TxPacket,长度4,这样就可以依次把4个载荷数据发出去了。

    Serial_SendArray(Serial_TxPacket,4);

        发送包尾0xFE:

    Serial_SendByte(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数据包程序设计的思路并详解了一些设计代码的细节。


原文地址:https://blog.csdn.net/Echo_cy_/article/details/144255782

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!