赞
踩
基本思路
采用数据帧的方式进行传递,数据帧格式为 帧头
数据1
数据2
数据3
数据4
帧尾
采用状态机思想进行解析比对缓存区中的数据正确性
通过串口中断接收函数,每次将接收到的一个数据存入大缓冲区,如何再建立小缓冲区将要处理使用的数据从大缓存区中取出使用。
优点:接收与处理分别进行,有利于防止数据处理卡死、混乱或丢包。
缺点:要明确定义好每个数据的帧头帧尾在缓冲区的位置,存储调用数据要精确。
实现代码
本代码以STM32与Openmv串口通信为例,STM32接收来自Openmv的传递的数据帧格式的数据,使用HAL库
*
第一项
使用
HAL_UART_Receive_IT
开启串口接收中断函数,开始接受Openmv传来的每一帧数据。
定义好接受缓冲区和定义各种所需变量,定义好处理比对函数状态机。
//定义接收缓冲区大小和帧头帧尾
#define MAX_RX_BUFFER_SIZE 255 //大缓冲区的大小
#define HEAD 0xFC //帧头
#define END 0xFE //帧尾
// 定义接收缓冲区和定义的各种变量
uint8_t rxBuffer[MAX_RX_BUFFER_SIZE]; //大缓冲区
uint8_t JugleBuffer[15]; //判断缓冲区索引
uint8_t rxIndex = 0; //缓冲区的索引
volatile uint8_t state_flag=0; //因为主频执行速度太快,而串口中断相对较慢,所以需要一个标志位来判断是否执行
/*数据帧状态机*/
typedef enum {
FRAME_HEAD = 0, //帧头
FRAME_DATA1, //数据1
FRAME_DATA2, //数据2
FRAME_DATA3, //数据3
FRAME_DATA4, //数据4
FRAME_END //帧尾
} FRAME_STATE;
FRAME_STATE frame_state = FRAME_HEAD; /*定义帧检测状态机,初始化检测帧头状态,定义了一个名为frame_state 的变量,类型为 FRAME_STATE 枚举类型,初始值为 FRAME_HEAD,用于表示数据帧的状态机,用于检测数据帧的帧头*/
在串口中断接收回调函数
HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
中将接收到的数据存入大缓冲区rxBuffer中。
// 串口接收中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART3)
{
// 将接收到的数据存入缓冲区
rxBuffer[rxIndex] = huart->Instance->DR;//huart->Instance->DR是一个串口接收寄存器的值,它存储了从串口接收到的数据。
if (rxIndex >= MAX_RX_BUFFER_SIZE)
{
rxIndex = 0;
}
else
{
rxIndex++;
}
state_flag=1; //接收到数据,标志位置1。特别注意!!!!此时在主频中开始调用比对获得函数 `FrameJudge(uint8_t *Recv_Data,uint8_t *Sendbuffer)`
// 继续接收下一个字节
HAL_UART_Receive_IT(huart, (uint8_t *)&huart->Instance->DR, 1);
}
}
书写取出小缓冲区数据的函数。
GetBufferdata(uint8_t *my_array)
//取出小缓冲区数据的函数
void GetBufferdata(uint8_t *my_array)
{
my_data1=my_array[1];//取出数据
my_data2=my_array[2];
my_data3=my_array[3];
my_data4=my_array[4];
//有几个数据取几个就行,记得定义my_data1,my_data2等变量
}
FrameJudge(uint8_t *Recv_Data,uint8_t *Sendbuffer)
其中Recv_Data
为大缓冲区参数,Sendbuffer
为小缓冲区参数。void FrameJudge(uint8_t *Recv_Data,uint8_t *Sendbuffer)
{
volatile static uint8_t HeadIndex = 0; //帧头索引
volatile static uint8_t EndIndex = 0; //帧尾索引
static uint8_t BufferIndex = 0; //判断缓冲区索引,大缓冲区的数据位置
//如果要想改成>=,就要把下面这个if放到整个函数的最后面
//if((BufferIndex > MAX_RX_BUFFER_SIZE)||(HeadIndex>=MAX_RX_BUFFER_SIZE-3))//Recv_Data这个数组在主函数里要对应UART2_Rx_Buffer[MAX_RX_BUFFER_SIZE]这个数组
if(BufferIndex > MAX_RX_BUFFER_SIZE) //MAX_RX_BUFFER_SIZE大小为255
{
BufferIndex = 0; //防止缓冲区溢出,如果大于了缓冲区,就从头开始覆盖。
frame_state = FRAME_HEAD;//也就是说,当缓冲区溢出了,就让帧检测状态机从头开始检测,就算最后一次检测到帧头,也不要了
HeadIndex = 0;
EndIndex = 0;
}
switch (frame_state)//此次使用先前定义的状态机进行数据的解析比对
{
case FRAME_HEAD://也就是0
{
if (Recv_Data[BufferIndex] == HEAD) //此前定义的HEAD为帧头0xFC,时刻铭记Recv_Data为大缓冲区!!!
{
HeadIndex = BufferIndex;//记录帧头在那个大的缓冲区的索引
frame_state = FRAME_DATA1;//帧检测状态机转移到数据状态,从0变到1
BufferIndex++;//从0开始找帧头,找到了就加1,下次就从上次帧头的位置开始找
}
else
{
BufferIndex++;//如果没读到帧头就往下找
}
state_flag=0;//主频中执行完一次,就让其标志位清零,等待下一次串口中断,也就是说,让主频的处理与串口中断的处理同步
break;
}
case FRAME_DATA1://也就是1
{
frame_state = FRAME_DATA2;//帧检测状态机转移到数据2状态,从1变到2
BufferIndex++;
state_flag=0;
break;
}
case FRAME_DATA2://也就是1
{
frame_state = FRAME_DATA3;//帧检测状态机转移到数据3状态,从2变到3
BufferIndex++;
state_flag=0;
break;
}
case FRAME_DATA3://也就是1
{
frame_state = FRAME_DATA4;//帧检测状态机转移到数据4状态,从3变到4
BufferIndex++;
state_flag=0;
break;
}
case FRAME_DATA4://也就是1
{
frame_state = FRAME_END;//帧检测状态机转移到帧尾状态,从4变到END
BufferIndex++;
state_flag=0;
break;
}
case FRAME_END:
{
if (Recv_Data[BufferIndex] == END)
{
EndIndex = BufferIndex;//记录帧尾在那个大的缓冲区的索引
// memcpy(Sendbuffer,&Recv_Data[HeadIndex],EndIndex-HeadIndex+1);//将帧数据拷贝到判断缓冲区
memcpy(Sendbuffer,Recv_Data + HeadIndex,4);//将帧数据拷贝到判断缓冲区,即小缓冲区
GetBufferdata(Sendbuffer);//将判断缓冲区的数据拷贝到帧缓冲区
// memset(Sendbuffer,0,15);//清空串口缓冲区
BufferIndex++;
frame_state = FRAME_HEAD;//帧检测状态机转移到帧头状态,从2变到0
}
else
{
BufferIndex++;
frame_state = FRAME_HEAD;//帧检测状态机转移到帧头状态,从2变到0,也就是说如果帧尾不是帧尾,那么就从头开始找
}
state_flag=0;
break;
}
default:
{
break;
//没在这加state_flag=0;是有深意的,因为如果发生错误跳出,但此时state_flag还没清零
//又因为主频会比串口中断处理快很多,所以主频就会把上一个发生意外的再次处理一遍
}
}
}
while(1)
{
if(state_flag == 1)
{
FrameJudge(rxBuffer, JugleBuffer);//rxBuffer大缓冲区,JugleBuffer小缓冲区
}
}
``
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。