串口数据实时处理:定时器+串口 判断串口数据接收完成

使用背景:

之前在做项目的时候,串口接收的数据要及时进行处理,虽然采用了自定义的串口协议,但是协议的包尾只有一个字节,经常判断不准数据是否接受完毕,所以就采用计时器+串口的方式来判定串口是否接受完成。

核心思想

根据波特率来计算接收一个字节所需要的时间,当超过这个时间没有收到数据,则表明这一帧数据已经接受完毕

实现方法

串口中断函数接收第一个字节之后,开启定时器计数。接受下一个字节的时候清空定时器计数。如此,当没有数据接收后,计时器无法清零,当计时器计数超过设定的数值之后,触发定时器溢出中断,此时数据即接收完毕

Created with Raphaël 2.2.0 开始 定义 计时标志位、完成标志位 接收数据,清空计时标志位 开启定时器,计时标志位增加 串口未收到数据? 计时标志位增加,超过设定值 接收完成标志位置1,处理数据 结束 yes no

下面是代码:

  • 1、为了方便,定义一个结构体,主要内容如下所示:
typedef struct _UART_FLAG_STRUCT
{
	uint8_t UART1Flag;//数据接受完成标志
	uint8_t UART1String[160];//最大长度,自定义
	uint8_t UART1Counter;//收到的数据长度,计数作用
	uint8_t usart1_start;//接收开始,定时器计时启动
	uint8_t usart1_counter;//定时器计时次数
}	UartFlagSt;

UartFlagSt UartFlagStC;
  • 串口配置:
/************************************************
| 关键词 |USARTInit(u32 band)
| - - - - - - - - - - - - - - - - - - - - - - - -
|  入参  |u32 band,配置波特率
  - - - - - - - - - - - - - - - - - - - - - - - - 
| 返回值 |None
  - - - - - - - - - - - - - - - - - - - - - - - -
|  功能  |初始化串口1,使能中断
**************************************************/
void USARTInit(u32 band)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef  NVIC_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1|RCC_APB2Periph_AFIO,ENABLE);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin  =GPIO_Pin_9;//send
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//receive
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	USART_InitStructure.USART_BaudRate = band;
	USART_InitStructure.USART_HardwareFlowControl = 0;
	USART_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;
	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); //初始化串口1

	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//开启串口接受中断
	USART_Cmd(USART1,ENABLE);                    //使能串口1 
}

/************************************************
| 关键词 |UART1_SendString(uint8_t* str,uint8_t counter)
| - - - - - - - - - - - - - - - - - - - - - - - -
|  入参  |uint8_t* str    发送的数据
|	|uint8_t counter 发送数据的长度
  - - - - - - - - - - - - - - - - - - - - - - - - 
| 返回值 |无
  - - - - - - - - - - - - - - - - - - - - - - - -
|  功能  |串口1发送函数
**************************************************/
void UART1_SendString(uint8_t* str,uint8_t counter)
{
	for(int i = 0;i<counter;i++)
	{
		USART_SendData(USART1,*str);
		while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
		str++;
	}
}
  • 定时器配置:
/************************************************
| 关键词 |TIM2_Init(u16 arr,u16 psc)
| - - - - - - - - - - - - - - - - - - - - - - - -
|  入参  |u16 arr 周期,u16 psc 预分频
| - - - - - - - - - - - - - - - - - - - - - - - - 
| 返回值 |无
| - - - - - - - - - - - - - - - - - - - - - - - -
|  功能  |定时器初始化
**************************************************/  
void TIM2_Init(u16 arr,u16 psc)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	
	TIM_TimeBaseStructure.TIM_Period = arr; //	周期 72-1 max
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //预分频 
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; 
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); 
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE );

	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; 
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
	NVIC_Init(&NVIC_InitStructure);

	TIM_Cmd(TIM2, ENABLE);
}
  • 串口中断函数:
/************************************************
| 关键词 |USART1_IRQHandler
| - - - - - - - - - - - - - - - - - - - - - - - -
|  入参  |None
  - - - - - - - - - - - - - - - - - - - - - - - - 
| 返回值 |None
  - - - - - - - - - - - - - - - - - - - - - - - -
|  功能  |1、消除错误;
|	|2、将数据接受至结构体中的数,
|       |并启动UartFlagStC.usart1_start = 1;UartFlagStC
|       |.usart1_counter = 0;定时/刷新功能。
**************************************************/
void USART1_IRQHandler(void)
{
/*****此段注释代码为消除各种串口错误,在串口环境很差的情况下可以直接将此段代码取消注释,基本可以保证串口代码正常工作******
	if((USART_GetITStatus(USART1,USART_IT_ORE) == SET)||(USART_GetFlagStatus(USART1,USART_FLAG_ORE) == SET))
	{
		USART_ReceiveData(USART1);
		USART_ClearITPendingBit(USART1,USART_IT_ORE);
		USART_ClearFlag(USART1,USART_IT_ORE);
	}
	
	if((USART_GetITStatus(USART1,USART_IT_NE) == SET)||USART_GetFlagStatus(USART1, USART_FLAG_NE) != RESET)
	{
		USART_ClearITPendingBit(USART1,USART_IT_NE);
		USART_ClearFlag(USART1, USART_FLAG_NE);
	}

	if((USART_GetITStatus(USART1,USART_IT_FE) == SET)||USART_GetFlagStatus(USART1, USART_FLAG_FE) != RESET)
	{
		USART_ClearITPendingBit(USART1,USART_IT_FE);
		USART_ClearFlag(USART1, USART_FLAG_FE);
	}

	if((USART_GetITStatus(USART1,USART_IT_PE) == SET)||USART_GetFlagStatus(USART1, USART_FLAG_PE) != RESET)
	{
		USART_ClearITPendingBit(USART1,USART_IT_PE);
		USART_ClearFlag(USART1, USART_FLAG_PE);
	}
****************************************************************************************************/
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
	{
		if(UartFlagStC.UART1Counter<160)//设定的数组最大为160,要小于这个数,防止溢出
		{
			UartFlagStC.UART1String[UartFlagStC.UART1Counter] = USART_ReceiveData(USART1);//将数据存到数组里面
			UartFlagStC.UART1Counter++;//收到的数据个数+1
			UartFlagStC.usart1_start = 1;//定时器开始工作
			UartFlagStC.usart1_counter = 0;//清空定时器计数
		}else UartFlagStC.UART1Counter = 0;//如果接受的数据超过设定值,则清空接收值,防止数据溢出
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
	}
}

可以看到,当有数据进来的时候,UartFlagStC.UART1Counter不断自增,数据将会依次存入UartFlagStC.UART1String[]数组中。
同时,UartFlagStC.usart1_start计时器开始计数标志位置一(让在定时器中断函数里面自增的UartFlagStC.usart1_counter得以正常增加),同时也将UartFlagStC.usart1_counter清零,以表示有数据接收,防止超过设定值,使得UartFlagStC.UART1Flag置一,错误的提示数据提前接收完成。

  • 定时器中断函数:
    在下面代码提示插入任务的地方插入我们想执行的任务/代码,即可正常使用
/************************************************
| 关键词 |TIM2_IRQHandler
| - - - - - - - - - - - - - - - - - - - - - - - -
|  入参  |无
| - - - - - - - - - - - - - - - - - - - - - - - - 
| 返回值 |无
| - - - - - - - - - - - - - - - - - - - - - - - -
|  功能  |定时器中断函数
**************************************************/  
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
	{
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
		/**********************/
		 在此处插入执行的任务。
		/**********************/
	}
}
  • 关键代码:
    这个代码是我们任务重最重要的部分,主要实现了我们上述流程图的计时/完成任务,之所以将这部分功能单独拆解写成一个函数的形式,主要是为了移植方便,并且在定时器多任务的时候让定时器中断函数看起来更整洁一点。
使用方法:将此函数插入定时器中断函数。
/************************************************
| 关键词 |USART1InsetTimer
| - - - - - - - - - - - - - - - - - - - - - - - -
|  入参  |None
  - - - - - - - - - - - - - - - - - - - - - - - - 
| 返回值 |无
  - - - - - - - - - - - - - - - - - - - - - - - -
|  功能  |嵌入定时器中断函数中,串口数据接收完毕后
|		 |立刻执行,适用于延时性小、数据量密的场景。
**************************************************/
void USART1InsetTimer(void)
{
	if(UartFlagStC.usart1_start == 1)//此标志位是在串口接收数据时候会置1
	{
		UartFlagStC.usart1_counter++;//定时器计数标志位
		if(UartFlagStC.usart1_counter >USART_COUNTER_9600)//如果超过波特率为9600时一个字节的所需要的时间,时间计算方法下面有讲解
		{
			UartFlagStC.UART1Flag = 1;//接收完成标志位置1
			UartFlagStC.usart1_counter = 0;//计数值清零
			UartFlagStC.usart1_start = 0;//计数器启动标志位置0
		}
	}
}

在这里,当(UartFlagStC.usart1_start置一后,UartFlagStC.usart1_counter会不断自增(串口中断中会清零此计数位),而一旦超过设定值USART_COUNTER_9600,就会将接收完成标志位UartFlagStC.UART1Flag置一,同时清空定时器技术位UartFlagStC.usart1_counter,并清零计数允许标志位UartFlagStC.usart1_start

  • 处理数据:
    如果处理数据很快的话可以直接放在定时器中断函数里面执行,如果还有比较长的延时函数,或者在执行过程中花费时间太久则可以放入主函数循环中进行处理:
    老规矩,先进行封装一层:
/************************************************
| 关键词 |USART1Hanndle
| - - - - - - - - - - - - - - - - - - - - - - - -
|  入参  |None
  - - - - - - - - - - - - - - - - - - - - - - - - 
| 返回值 |无
  - - - - - - - - - - - - - - - - - - - - - - - -
|  功能  |串口1接收数据处理函数,任务若花费时间较长,
|        |可放置于while()循环中,由定时器确定是否执行。
**************************************************/
void USART1Hanndle(void)
{
	if(!UartFlagStC.UART1Flag) return;
	/*********执行任务*************/		
	printf("%s\r\n",UartFlagStC.UART1String);
	/****************************/
	memset(UartFlagStC.UART1String,0,160);
	UartFlagStC.UART1Counter = 0;
	UartFlagStC.UART1Flag = 0;
}

主函数实现:

/************************************************
| 关键词 |main
| - - - - - - - - - - - - - - - - - - - - - - - -
|  入参  |None
| - - - - - - - - - - - - - - - - - - - - - - - - 
| 返回值 |无
| - - - - - - - - - - - - - - - - - - - - - - - -
|  功能  |主函数入口,配置文件,设置时钟,滴答定时器
|        |周期,并开启看门狗
**************************************************/  
int main()
{
	SystemInit();   				 	//系统时钟72MHz
	SysTick_Config(SystemCoreClock/1000);        /* SysTick 1 msec interrupts */
	TIM2_Init(71,999);//1ms中断一次
	USARTInit(9600);//波特率为9600
	while(1)
	{
		USART1Hanndle();
	}
}

流程/细节讲解

  • 不同波特率延时时间计算:

可能会有人对void USART1InsetTimer(void)中的溢出时间USART_COUNTER_9600有疑问,不知道如何计算,计算方法如下:

首先,1个字符串口包含起始位数据位校验位停止位,其中有些位长度可以自己设定,
这里我们按一个字节传输有1+8+1+1共10位长度来计算。
波特率表示的意思是在1sec内可以传输的位数


接下来就是一元一次方程


设1个字节所用时间为X,波特率为9600,则:

( 1 ∗ 10 ) / X = 9600 / 1000 ( m s ) (1 * 10) / X = 9600 / 1000(ms) (110)/X=9600/1000(ms)
解得X ≈ 1.04167 ms = 2(X为整型,必须向上取整!)

X代表的意思是一帧数据传输的时间,意思就是每过X单位时间,即有一个数据接受完毕,同时下一个数据也即将接受。
USART_COUNTER_9600的数值设定为X1
UartFlagStC.usart1_counter则只要在串口中断函数内清空,那么UartFlagStC.usart1_counter就不会超过X,
那么也就不会将接受完成标志位UartFlagStC.UART1Flag置1;
一旦没有数据继续接收,那么UartFlagStC.usart1_counter在中断函数里面将不断自增,直至超过X,此时接受完成标志位UartFlagStC.UART1Flag将会置1。
同时,时间设定要根据波特率的不同要计算不同的数值。

下载地址 :https://download.csdn.net/download/qq_31431301/12287318


  1. 实际使用过程中,一定要将USART_COUNTER_9600的设定值大于X,因为在此方法中,串口中断函数不仅要判断数据接收标志位,还有清零置一的操作,实际工作时间肯定要大于X!,一般取3~5倍X的时间。 ↩︎

  • 30
    点赞
  • 168
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值