使用背景:
之前在做项目的时候,串口接收的数据要及时进行处理,虽然采用了自定义的串口协议,但是协议的包尾只有一个字节,经常判断不准数据是否接受完毕,所以就采用计时器+串口的方式来判定串口是否接受完成。
核心思想
根据波特率来计算接收一个字节所需要的时间,当超过这个时间没有收到数据,则表明这一帧数据已经接受完毕
实现方法
串口中断函数接收第一个字节之后,开启定时器计数。接受下一个字节的时候清空定时器计数。如此,当没有数据接收后,计时器无法清零,当计时器计数超过设定的数值之后,触发定时器溢出中断,此时数据即接收完毕
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,则:
解得X ≈ 1.04167 ms = 2(X为整型,必须向上取整!)
X代表的意思是一帧数据传输的时间,意思就是每过X单位时间,即有一个数据接受完毕,同时下一个数据也即将接受。
将USART_COUNTER_9600
的数值设定为X1UartFlagStC.usart1_counter
则只要在串口中断函数内清空,那么UartFlagStC.usart1_counter
就不会超过X,
那么也就不会将接受完成标志位UartFlagStC.UART1Flag
置1;
一旦没有数据继续接收,那么UartFlagStC.usart1_counter
在中断函数里面将不断自增,直至超过X,此时接受完成标志位UartFlagStC.UART1Flag
将会置1。
同时,时间设定要根据波特率的不同要计算不同的数值。
下载地址 :
- 实际使用过程中,一定要将
USART_COUNTER_9600
的设定值大于X,因为在此方法中,串口中断函数不仅要判断数据接收标志位,还有清零置一的操作,实际工作时间肯定要大于X!,一般取3~5倍X的时间。 ↩︎