单片机串口处理思路:超时接收方法解析(软件03)

本文目录

  •     软件学习前言
  •     代码思路
  •     实操练习
  • 软件学习前言

            最近写了两篇硬件分享文章,要做的一个通过485串口接收指令,从而控制电机转速的内容。里面涉及到了串口的处理,于是便想写一下关于串口处理的相关经验分享,串口也是非常重要的,不管是printf打印log信息,还是涉及到协议通信部分,都是嵌入式里面必不可少的知识点。

            相关配套的硬件思路请参考我之前的硬件篇文章:

            (硬件02)按键+电位器+485控制的电机调速电路实战,上篇https://blog.csdn.net/BEXZJ/article/details/134784629

            (硬件03)按键+电位器+485控制的电机调速电路实战,下篇https://blog.csdn.net/BEXZJ/article/details/134791467

            接下来就开始正式地介绍串口的收发处理了,本篇我们要讲的是,串口的超时接收处理。

            老规矩,一张封面图进入今天的分享。

    代码思路

            1.串口理解

            说起刚学串口那会,听到的一个顺口溜叫“9681N”,让我印象很深刻,学习完串口知识之后,就理解它的意思了。

            96:代表9600波特率,英文bps,bit per second,就是每秒钟可以发9600位数据,我们知道一个字节是8位的,那么就是9600/8=1200个字节。常见的波特率有9600、115200。

            8:代表数据是8位为一个字节的,其中也可以选择7位的标准ASCII码(0-127)数据,8位的是拓展的ASCII码(0-255)数据。

            1:代表1个停止位,这个与通讯双方进行约定好即可,可选1.5、2位。

            N:代表None,是不进行校验的意思,这个与通讯双方进行约定好即可,可选奇校验、偶检验等。

            2.串口的初始化

            使用单片机对应的库函数,将串口硬件初始化为上述我们理解后的串口格式配置。再配置串口中断,以及在中断服务函数的相关处理。

            3.串口的超时接收

            串口中断收到一个字节的数据,将其存入BUFF数组,并且将数组的位置往后一位以用来存储下一个字节数据,重置一下接收超时的时间,标记我们已经开始接收了,最后清中断标记位。

            在接收完最后一字节数据后。接收时间便不会重置,于是我们可以想到,根据开始接收的标记,结合重置时间的非0自减,当重置时间减少到0时,我们就认为这一帧数据我们已经接收完了,可以对其进行处理了,并且让接收数据的位置回到第一位,其他的标记也重置一下。

            4.数据处理

            根据接收完的标记,我们可以进行数据处理,处理完,再将接收完的标记取消了,便可以开始下一轮的串口接收和处理了。

            5.思路总结

            串口有接收字节时,给它一个等待时间,如20ms(刚刚我们算出9600波特率是1秒钟传1200个字节,那么1ms可以传1.2个字节,我们只需要等1ms就可以了,我们之前设置的是1ms的中断时基处理,为了保险,我们取20ms作为等待时间,再说了,两条报文间只间隔20ms是很少见的,基本不会出现连包情况),最后一个字节接收完了后,再等20ms时间,就判读为接收完成,标记数据帧有效,可以处理数据。

    实操练习

            1.头文件的宏定义、结构体、函数申明。

            头文件zj_public.h

    #include <at32f4xx.h>
    #include <string.h>
    
    #include "at32f4xx_usart.h"
    
    
    //串口
    #define BSP_UART1_TX_GPIO_PIN      		GPIO_Pins_6
    #define BSP_UART1_TX_GPIO_AF1_Source	GPIO_PinsSource6
    #define BSP_UART1_TX_GPIO_PORT   	  	GPIOB
    #define BSP_UART1_RX_GPIO_PIN      		GPIO_Pins_7
    #define BSP_UART1_RX_GPIO_AF1_Source	GPIO_PinsSource7
    #define BSP_UART1_RX_GPIO_PORT   	  	GPIOB
    
    #define BSP_485_USART   	  			USART1
    #define BSP_485_USART_IRQHandler 		USART1_IRQHandler
    #define BSP_485_USART_BANDRATE 			115200
    
    #define BSP_485_USART_IRQN  		    USART1_IRQn
    #define BSP_485_USART_IRQN_LEVEL  		2
    #define BSP_485_USART_IRQN_HANDLER  	USART1_IRQHandler
    
    
    //时基定时器
    #define BSP_TIME_BASE_TIMER  			TMR6
    #define BSP_TIME_BASE_TIMER_ARR  		10-1
    #define BSP_TIME_BASE_TIMER_PSC  		7200-1
    #define BSP_TIME_BASE_IRQN  			TMR6_GLOBAL_IRQn
    #define BSP_TIME_BASE_IRQN_LEVEL  		0
    #define BSP_TIME_BASE_IRQN_HANDLER  	TMR6_GLOBAL_IRQHandler
    
    
    //LED灯
    #define BSP_LED_RUN_GPIO_PIN      		GPIO_Pins_10
    #define BSP_LED_RUN_GPIO_PORT     		GPIOA
    #define BSP_LED_RUN_ON      			GPIO_ResetBits(BSP_LED_RUN_GPIO_PORT, BSP_LED_RUN_GPIO_PIN)
    #define BSP_LED_RUN_OFF      			GPIO_SetBits(BSP_LED_RUN_GPIO_PORT, BSP_LED_RUN_GPIO_PIN)
    
    
    
    
    //串口数据缓存长度
    #define	UART_DATA_BUFF_LEN 				512
    
    extern volatile uint64_t gTimeBase;
    
    typedef struct
    {
    	uint8_t rx_buff[UART_DATA_BUFF_LEN];
    	uint16_t rx_index;
    	uint8_t rx_finish_flag;
    	uint8_t rx_overtime_flag;
    	uint16_t rx_overtime_ms;
    	uint16_t rx_len;
    	
    	uint8_t tx_buff[UART_DATA_BUFF_LEN];
    	uint16_t tx_len;
    	
    }UART_INFO;
    extern UART_INFO zj_485_uart;
    
    void zj_app_timebase_process(void);
    void zj_app_timebase_1ms_process(void);
    
    void zj_app_uart_process(void);
    void zj_app_uart_10ms_process(void);
    void zj_app_uart_send(uint8_t *pBuf,uint8_t mLen);
    
    
    

    硬件配置函数zj_bsp.c,配置时钟、GPIO复用、串口配置、串口中断、时基配置(软件01篇有介绍)

    #include "zj_public.h"
    
    volatile uint64_t gTimeBase = 0;
    
    
    void RCC_Configuration(void)
    {   
        RCC_AHBPeriphClockCmd(RCC_AHBPERIPH_GPIOA, ENABLE);
        RCC_AHBPeriphClockCmd(RCC_AHBPERIPH_GPIOB, ENABLE);
    	
        RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_USART1, ENABLE);//串口 
    	RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_TMR6, ENABLE);//时基
    	
    	RCC_APB2PeriphClockCmd(RCC_APB2PERIPH_SYSCFGCOMP, ENABLE);//IO复用
    }
    
    void GPIO_Configuration(void)
    {
        GPIO_InitType GPIO_InitStructure;
        GPIO_StructInit(&GPIO_InitStructure);
    	
    	GPIO_InitStructure.GPIO_MaxSpeed = GPIO_MaxSpeed_50MHz;
    
    	//UART1 管脚配置
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
        GPIO_InitStructure.GPIO_OutType = GPIO_OutType_PP;
    	GPIO_InitStructure.GPIO_Pins = BSP_UART1_TX_GPIO_PIN;
        GPIO_Init(BSP_UART1_TX_GPIO_PORT, &GPIO_InitStructure); 
    	
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    	GPIO_InitStructure.GPIO_Pull = GPIO_Pull_PU;
    	GPIO_InitStructure.GPIO_Pins = BSP_UART1_RX_GPIO_PIN;
        GPIO_Init(BSP_UART1_RX_GPIO_PORT, &GPIO_InitStructure); 
    	
    	//UART1 管脚复用
        GPIO_PinAFConfig(BSP_UART1_TX_GPIO_PORT, BSP_UART1_TX_GPIO_AF1_Source, GPIO_AF_0);
        GPIO_PinAFConfig(BSP_UART1_RX_GPIO_PORT, BSP_UART1_RX_GPIO_AF1_Source, GPIO_AF_0);
    	
    	//灯
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    	GPIO_InitStructure.GPIO_OutType = GPIO_OutType_PP;
    	GPIO_InitStructure.GPIO_Pins = BSP_LED_RUN_GPIO_PIN;
        GPIO_Init(BSP_LED_RUN_GPIO_PORT, &GPIO_InitStructure); 
        BSP_LED_RUN_ON;
    }
    
    void NVIC_Configuration(void)
    {
        NVIC_InitType NVIC_InitStructure;
    
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
    
    	// 0 TIMER6_TIMEBASE 
        NVIC_InitStructure.NVIC_IRQChannel = BSP_TIME_BASE_IRQN;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = BSP_TIME_BASE_IRQN_LEVEL;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);
    
    	// 1 USART1_485 
        NVIC_InitStructure.NVIC_IRQChannel = BSP_485_USART_IRQN;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = BSP_485_USART_IRQN_LEVEL;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);
    }
    
    void EXTI_Configuration(void)
    {
    
    }
    
    void TIMER_BASE_Configuration(TMR_Type * mTimer ,u16 mArr, u16 mPsc)
    {
        TMR_TimerBaseInitType TIM_TimeBaseStructure;
    
        //定时器初始化
        TMR_Reset(mTimer);
        TIM_TimeBaseStructure.TMR_Period = mArr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 
        TIM_TimeBaseStructure.TMR_DIV = mPsc; //设置用来作为TIMx时钟频率除数的预分频值
        TIM_TimeBaseStructure.TMR_ClockDivision = TMR_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
        TIM_TimeBaseStructure.TMR_CounterMode = TMR_CounterDIR_Up; //TIM向上计数模式
        TMR_TimeBaseInit(mTimer, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
    	
        TMR_ClearITPendingBit(mTimer, TMR_INT_Overflow);
        TMR_INTConfig(mTimer, TMR_INT_Overflow, ENABLE);
        TMR_Cmd(mTimer, ENABLE); //使能TIMx 
    }
    
    void BSP_TIME_BASE_IRQN_HANDLER(void)
    {
      if (TMR_GetINTStatus(BSP_TIME_BASE_TIMER, TMR_INT_Overflow) != RESET)//是更新中断
      {
        TMR_ClearITPendingBit(BSP_TIME_BASE_TIMER, TMR_INT_Overflow); //清除TIM更新中断标志 
    		
    	zj_app_timebase_1ms_process();
      }
    }
    
    void USART_Configuration(USART_Type *USARTx, uint32_t nBandRate)
    {
        USART_InitType USART_InitStructure;
     
        USART_InitStructure.USART_BaudRate = nBandRate; //波特率
        USART_InitStructure.USART_WordLength = USART_WordLength_8b; //数据位
        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(USARTx, &USART_InitStructure);
    	
    	 //使能串口1接收中断
        USART_INTConfig(USARTx, USART_INT_RDNE, ENABLE);	
        //使能串口
        USART_Cmd(USARTx, ENABLE);
    }
    
    void BSP_485_USART_IRQN_HANDLER(void)
    {
      if (USART_GetITStatus(BSP_485_USART, USART_INT_RDNE) != RESET)
      {
        USART_ClearFlag(BSP_485_USART, USART_FLAG_RDNE);
    		
    	zj_485_uart.rx_overtime_flag = TRUE;
    	zj_485_uart.rx_overtime_ms = 20;
    		
    	zj_485_uart.rx_buff[zj_485_uart.rx_index++] = USART_ReceiveData(BSP_485_USART);
    		
    	if(zj_485_uart.rx_index >= UART_DATA_BUFF_LEN)
    	zj_485_uart.rx_index = 0;
      }
    }
    
    
    void zj_bsp_config(void)
    {
      SystemCoreClockUpdate();
    
      RCC_Configuration();      
      NVIC_Configuration();
      GPIO_Configuration();
      EXTI_Configuration();
    
      USART_Configuration(BSP_485_USART,BSP_485_USART_BANDRATE);
      TIMER_BASE_Configuration(BSP_TIME_BASE_TIMER,BSP_TIME_BASE_TIMER_ARR,BSP_TIME_BASE_TIMER_PSC);
    }
    

    时基函数处理

    #include "zj_public.h"
     
     
    uint64_t TimeBaseMs=0,TimeBase10ms=0,TimeBase50ms=0,TimeBase100ms=0,TimeBase500ms=0,TimeBase1000ms=0,TimeBase5000ms=0,TimeBase6000ms=0,TimeBase10000ms=0,TimeBase60000ms=0;
     
    static uint64_t Time_GetTimeMs(void)
    {
        return gTimeBase;
    }
     
    static void Process(void)
    {
        zj_app_uart_process(); //串口接收处理
    }
     
    void zj_app_timebase_1ms_process(void)
    {
    	gTimeBase++;
    
    	if(zj_485_uart.rx_overtime_ms)//串口接收超时时间处理,非零自减
    	  zj_485_uart.rx_overtime_ms--; 
    }
    	
    static void TimeProcess_10MS(void)
    {
        zj_app_uart_10ms_process();//串口数据帧10ms处理
    }
    static void TimeProcess_50MS(void)
    {
     
    }
    static void TimeProcess_100MS(void)
    {
     
    }
    static void TimeProcess_500MS(void)
    {
    }                  
    static void TimeProcess_1000MS(void)
    {
    }
    static void TimeProcess_5000MS(void)
    {   
    	static uint8_t sToggleFlag = 0;
     
    	if(sToggleFlag)
    	{
    		sToggleFlag = FALSE;
    		
    		BSP_LED_RUN_ON;
    	}
    	else
    	{
    		sToggleFlag = TRUE;
    	
    		BSP_LED_RUN_OFF;
    	}
    }
    static void TimeProcess_10000MS(void)
    {   
    }
    static void TimeProcess_60000MS(void)
    {   
    }
     
    
    void zj_app_timebase_process(void)
    {
    	
      Process();
      TimeBaseMs=Time_GetTimeMs();
     
      if(((TimeBaseMs-TimeBase10ms))>9)//10ms
      {
        TimeBase10ms+=10;
        TimeProcess_10MS();
      }
      if(((TimeBaseMs-TimeBase50ms))>49)//50ms
      {
        TimeBase50ms+=50;
        TimeProcess_50MS();
      }
      if(((TimeBaseMs-TimeBase100ms))>99)//100ms
      {
        TimeBase100ms+=100;
        TimeProcess_100MS();
      }
      if(((TimeBaseMs-TimeBase500ms))>499)//500ms
      {
        TimeBase500ms+=500;
        TimeProcess_500MS();
      }
      if(((TimeBaseMs-TimeBase1000ms))>999)//1s
      {
        TimeBase1000ms+=1000;
        TimeProcess_1000MS();
      }
      if(((TimeBaseMs-TimeBase5000ms))>4999)//5s
      {
        TimeBase5000ms+=5000;
        TimeProcess_5000MS();
      }
      if(((TimeBaseMs-TimeBase10000ms))>9999)//10s
      {
        TimeBase10000ms+=10000;
    		TimeProcess_10000MS();
      }
      if(((TimeBaseMs-TimeBase60000ms))>59999)//60s
      {
        TimeBase60000ms+=60000;
    		TimeProcess_60000MS();
      }
    }
    

    应用函数zj_app_uart.c

    #include "zj_public.h"
    
    
    void zj_app_uart_send(uint8_t *pBuf,uint8_t mLen)
    {
    	while(mLen--)
    	{
    		USART_SendData(BSP_485_USART,*pBuf++); //库函数,串口发送单个字节数据
    		while(USART_GetFlagStatus(BSP_485_USART, USART_FLAG_TRAC) == RESET); 
    	}
    }
    
    void zj_app_uart_process(void)
    {
    	if((zj_485_uart.rx_overtime_flag == TRUE) && (zj_485_uart.rx_overtime_ms==0))
    	{
    		//rx_overtime_flag=TRUE :有超时接收,rx_overtime_ms=0 :最后一个字节接收完成
    
    		zj_485_uart.rx_len =  zj_485_uart.rx_index; //接收到的长度
    		zj_485_uart.rx_index = 0;                   //接收BUFF回到首位,为下次接收做准备
    		zj_485_uart.rx_overtime_flag = FALSE;       //重置超时接收标记,为下次接收做准备
    		
    		zj_485_uart.rx_finish_flag = TRUE;          //标记接收完成
    	}
    }
    
    
    void zj_app_uart_10ms_process(void)
    {
    	if(zj_485_uart.rx_finish_flag) //根据接收完成标记处理数据
    	{
    	  zj_485_uart.rx_finish_flag = FALSE;//重置开始接收标记,为下次处理数据做准备
    
          zj_app_uart_send(zj_485_uart.rx_buff,zj_485_uart.rx_len); //示例用,收到什么数据发什么数据回去
    	}
    }
    

    主函数main.c

    #include "zj_public.h"
         
    int main(void)
    {  
      zj_bsp_config();
         
      while(1)
      {
       zj_app_timebase_process();
      } 	
    }

            如此,串口的超时接收处理已经介绍完成了,具体的串口应用肯定不是接收什么发送什么回去,比如说一些私有协议解析,要解析报文头、报文尾、数据长度、校验方式等,这些都是比较复杂的,但都是在zj_app_uart_10ms_process(void);中处理就行,通过状态机的方式处理,先判断接收数组中的第一个字节,再跳转到接收数组中的第二个字节….最后判断完最后的数据,对整个接收数字进行memset(&buff,0,buff_len)重置处理。

            预告一下,下一篇软件篇将带大家实操练习一下485 Modbus的项目,包括0x03读多个寄存器指令、0x06写单个寄存器指令、0x10写多个寄存器指令,以及遇到一些关键数据怎么保存到flash中去,如从机地址。。。

    小弟感谢大家的关注!

          (利他之心,原创分享)
     

    物联沃分享整理
    物联沃-IOTWORD物联网 » 单片机串口处理思路:超时接收方法解析(软件03)

    发表回复