STM32

如何判断串口接收完成一帧数据

2018-12-11  本文已影响0人  lissettecarlr

1 使用定时器判断

这种方式建立在两帧数据不可能连续发送的基础上,也是modbus判断帧结束的方式,在接收到第一个字节的时候打开定时器,如果继续接收到数据则更新定时器,在被设定时间内没有接收到数据则定时器超时。


enter image description here

关于定时器的设定时间有这样几个问题

关于时间的计算

首先,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;//标志数据正在接收
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);
}
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;
}
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;
    
        }
}
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);
     } 
}
上一篇 下一篇

猜你喜欢

热点阅读