1 使用定时器判断
这种方式建立在两帧数据不可能连续发送的基础上,也是modbus判断帧结束的方式,在接收到第一个字节的时候打开定时器,如果继续接收到数据则更新定时器,在被设定时间内没有接收到数据则定时器超时。
关于定时器的设定时间有这样几个问题
- 其一是如果定时器超时时间大于发送两帧数据的时间间隔,则接收到的一帧数据实际上是几帧,更可能定时器无法超时,一直处于接收状态。
- 其二是如果定时器超时小于发生两个字节的时间间隔,则在接收到1个字节定时器就超时了。
我们于是只能设定一个尽量小但又不影响接收连续字节的时间,例如ModBus通信时规定发送完一组命令必须间隔3.5个字符时间间隔再发送下一组新命令,这里规定的便是定时器的超时时间。
关于时间的计算
首先,1个字符窗口包含起始位,数据位,校验位,停止位,其中有些位长度不一定,这里我们按1+8+1+1来计算。波特率表示的意思是在1000ms内可以传送的位数,设3.5个字节所用时间为X,波特率为9600则:
3.5*11 / X = 9600 / 1000
X = 4.010416666666667 ms
X代表的意思是两帧数据间隔时间至少为此,我们程序的超时定时器可以设定为4ms。同时也知道波特率变化是会影响该值。
示例
这里使用的是STM32F103单片机,没有使用操作系统,使用的串口1和定时器3的通道1,这样定时器还能被用来干其他事,而不是完全被串口绑定。方式和上文有一点点差距,也就是在接收到非首字节数据时,不是重置定时器,而是更新通道超时的时间。通道1使用的是输出比较,也就是定时器计数达到这个比较值就会发生中断,我在接收数据时不断修改这个比较值,达到和更新定时器同样的目的。
下列便是相关代码,其中的fifo操作见另一篇文章
- 标志位
static uint8_t u1_Data_Start_Recive_Flag=0;//标志数据开始接收
static uint8_t u1_Data_recive_Flag=0;//标志数据正在接收
- 串口1初始化
void usart_init(int buad)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
USART_DeInit(USART1); //复位串口1
//USART1_TX PA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA9
//USART1_RX PA.10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA10
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART 初始化设置
USART_InitStructure.USART_BaudRate = buad; //一般设置为9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1; //一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启中断
USART_Cmd(USART1, ENABLE); //使能串口
FifoInit( &usart1_recive_fifo, usart1_recive_buffer, 200 );
}
- 定时器初始化
这里设定的计数器自动填充值和预分配可以按照整体需求来配置,当然得大于准备设定的区分字节间隔的最小时间
void timer3_init()
{
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// RCC_PCLK1Config(RCC_HCLK_Div1);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseStructure.TIM_Period = 0xfffe; //设定计数器自动重装值
TIM_TimeBaseStructure.TIM_Prescaler =999; //预分频器 72000
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
// TIM_PrescalerConfig(TIM1, PrescalValue,TIM_PSCReloadMode_Immediate);
//输出比较时间模式配置:通道1
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Timing;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 100;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
//TIM_OC1PreloadConfig(TIM2,TIM_OCPreload_Disable);//禁止预装载使能
//TIM_ITConfig(TIM2,TIM_IT_CC1,ENABLE);//使能中断
//TIM_ITConfig(TIM2,TIM_IT_CC2,ENABLE);//使能中断
//TIM_Cmd(TIM2,ENABLE ); //使能定时器
TIM_ClearITPendingBit(TIM3, TIM_IT_CC2);
TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);//清楚中断标识位
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure);
}
- 串口1中断服务程序
该函数是库函数提供,事件的中断函数
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
usart_irq_free(1);
if( FifoPush( &usart1_recive_fifo, (char)(USART_ReceiveData(USART1)) ) == 0 ) //存入数据
{
}
else
{
//error
}
}
}
- 状态切换函数,在上面的系统中断被调用
void usart_irq_free(uint8_t usart)
{
switch(usart)
{
case 1:
if(!u1_Data_Start_Recive_Flag && !u1_Data_recive_Flag) //需要获取数据且还未开始接收
{
usart_set_recive_start(1);
}
else if(!u1_Data_Start_Recive_Flag && u1_Data_recive_Flag) //需要获取数据且正在接收
{
usart_set_recive_ing(1);
}
break;
}
- 当接到首字节
这里获取当前定时器计数值,然后设定匹配为2000个计数,这里我设定了一个比较大的值,在定时器初始化是进行了千分频,这里时间间隔约为27.7ms,当然是我随意设定的一个较大的值,更加需求修改即可。
void usart_set_recive_start(uint8_t usart)
{
if(usart ==1)
{
FifoFlush(&usart1_recive_fifo);
u1_Data_Start_Recive_Flag = 0;
u1_Data_recive_Flag = 1;
TIM_Cmd(TIM3, ENABLE);//开启定时器
TIM_ITConfig(TIM3,TIM_IT_CC1,ENABLE);
TIM_SetCompare1(TIM3, TIM_GetCounter(TIM3)+2000); //
}
}
- 数据接收中状态
更新定时器匹配值,放置在接收图中定时器中断
void usart_set_recive_ing(uint8_t usart)
{
if(usart ==1)
{
u1_Data_Start_Recive_Flag = 0;
u1_Data_recive_Flag = 1;
TIM_SetCompare1(TIM3, TIM_GetCounter(TIM3)+2000);
}
}
- 定时器中断
库函数提供,当发生中断时也就是一帧数据接收完成时,这里进行状态切换,和调用回调,告诉上层完成了一帧数据的接收
void TIM3_IRQHandler(void) //TIM3中断
{
if(TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_IT_CC1); //清除TIMx更新中断标志
usart_set_recive_end(1);
if(usart1_irq_callback !=NULL)
{
usart1_irq_callback();
}
usart_start_recive(1);
}
if(TIM_GetITStatus(TIM3, TIM_FLAG_Update) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_FLAG_Update);
//Timer3_Update_IRQ();
}
}
- 一帧数据接收完成切换状态
void usart_set_recive_end(uint8_t usart)
{
if(usart ==1)
{
TIM_Cmd(TIM3, DISABLE);
TIM_ITConfig(TIM3,TIM_IT_CC1,DISABLE);
USART_Cmd(USART1, DISABLE);
u1_Data_Start_Recive_Flag = 1;
u1_Data_recive_Flag = 0;
}
}
- 帧完成回调函数
定义函数指针
void (*usart1_irq_callback)(void);
通过接口函数让外部对其赋值
void set_usart1_irq_callback( void (*callback)(void) )
{
usart1_irq_callback = callback;
}
- 串口的数据发送(和本文没啥关系)
void usart_send_buffer(uint8_t usart,uint8_t *data,int len)
{
int i=0;
switch(usart)
{
case 1:
USART_Cmd(USART1, ENABLE);
for(i=0;i<len;i++)
{
USART_SendData( USART1,(unsigned char) data[i]);
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_ClearFlag(USART1, USART_FLAG_TXE);
}
//USART_Cmd(USART1, DISABLE);
break;
}
}
- 外部调用
每当接收到一帧数据则function_callback函数被调用
usart1_init();
timer3_init();
set_usart1_irq_callback(function_callback);
2 解析协议方式
该方式便是通过协议中的保存的长度、包头、包尾等信息来判断帧接收完成。通常是将数据保存在一个循环buffer中,从中去找对应信息,根据信息判断取出一帧数据还是继续等待。
3 空闲中断
在STM32中有个串口空闲中断,在总线由忙碌转为空闲时(RXNE被置为)参数这个中断,我们可以利用这个中断来知道一帧传输接收,需要进行处理。
下面是SMT32L151的示例代码,首先在串口初始化时,也使能空闲中断
USART_ITConfig( uart, USART_IT_IDLE, ENABLE );
然后在对于的中断函数中进行处理,其中清除中断标志位根据手册所示,对SR、DR进行读操作,最后我调用回调函数来将帧接收完成的事告诉外层。
void USART1_IRQHandler( void )
{
unsigned char temp;
···
if(USART_GetITStatus(USART1,USART_IT_IDLE)==SET)
{
temp=USART1->SR;
temp=USART1->DR; //清除标志位
//完成接收
if( UartIdel[0] != NULL )
UartIdel[0]();
}
···
}
被关联的回调函数,用来测试该功能,接收到什么就发送什么
void SerialBoardIdelCallback()
{
char tempBuffer[10];
uint8_t len=GetSerialBoardReciveDataSize();
if(len<=10)
{
GetSerialBoardReciveData(tempBuffer,len);
SerialBoardSendData((unsigned char *)tempBuffer,len);
}
}