探讨单片机时基处理的优秀代码思路(软件01)

本文目录

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

            今天和大家分享一下我的软件学习心得,刚毕业那会,我只能写简单写点51的C语言代码,功能无非是那些按键、数码管、LED灯这块,也不注重代码的可读性,多次编译、烧录、看效果、再调整、最终拼拼凑凑地把功能实现了就行了。16年参加工作后的第一年,我接触到了用STM32F103C8T6做的一些产品,开始接触到32位单片机,第一感觉是编译环境好麻烦呀,点了编译按键总是报一些很奇怪的错误,不是软件代码的那一类的,更多的是找不到XXXX文件这种。后来也是在同事的帮忙下一一地将头文件关联,包含路径,到后来还是慢慢修改调试,才能把这一套的编译环境给配好,到了代码端的编程。ARM单片机和51单片机相比,复杂了很多,就拿GPIO配置来说,51的直接就可以让IO输出高低电平,但ARM单片机的话,需要对管脚进行配置,时钟使能这些步骤。在写应用函数的时候也是不怎么注重时序,都是在while循环中加延时或等待按键释放这些操作,单片机的资源浪费得严重。后来随着接触的深入,开始了解到变量标志位这种方法,再到后来的17年,接触到现在仍很喜欢使用的定时器时基处理法,也是本文要正式介绍给大家的一直方法。后面其实也有了解到uCOSII这种操作系统(只跑通了demo功能),后面的工作中也使用过FreeRTOS操作系统,其实原理都是差不多的,创建任务,设置优先级,通过全局变量互相调用这类。每个任务都是单独的函数,代码可读性也还不错,这些便捷性的东西都是建立在单片机资源足够的基础上,另外的,跑系统单片机的网上教学资源也是较少的,裸机编程的话,我还是推荐大家使用时基处理法,让大家的裸机编程变得更简单高效。

    (一张图片进入今天的分享)

    代码思路

            定义一个全局变量uint64_t  gTimeBase ;     

            配置定时器,让定时器每ms产生一次中断

            定时器中断后,清中断标记,gTimeBase ++;

            定义多个时刻的变量

            举例:

            TimeBaseMs、TimeBase10ms、TimeBase50ms、TimeBase100ms、TimeBase500ms、TimeBase1000ms、TimeBase5000ms、TimeBase6000ms、TimeBase10000ms、TimeBase60000ms

            在while循环中开始获取全局变量的值,一开始,当gTimeBase  自增到10时,满足gTimeBase  – TimeBase10ms >9 (10-0=10)的时候,我们给TimeBase10ms变量+10,然后执行一次10ms时基处理函数TimeProcess_10MS();由此可见,当gTimeBase  自增到20时,又满足gTimeBase  – TimeBase10ms >9 (20-10=10),又会执行一次0ms时基处理函数TimeProcess_10MS();其他的时基也是如此类推,我们就可以准确地知道每10ms我们要处理一次什么函数,每50ms、100ms、1000ms…..等时间到了之后我们该做什么了。

            这个时候我们就可以在10ms中处理我们的按键扫描函数了,按下后,按键计数变量+1,加到2的时候还处于按下,那么这个2*10ms就是我们知道的按键防抖时间。

            每到50ms我们处理一下软件看门狗的喂狗操作。

            每5秒切换一下LED灯的亮灭状态。我每次都喜欢这样操作,一边手按在手机的秒表计时器开始上,一边等待灯熄灭,看到灯熄灭了,开始计时,等灯亮起来的时候我再点击手机上秒表计时器的暂停键,观察记录的时间是否为5秒,从而来判断的我时基配置是否准确。      

            在while循环中每次获取全局变量时,我们也可以执行一个Process();函数,如此,也相当于有一个接近while循环的处理了,比较关键的处理放在这里执行。

    实操练习

            就以我刚刚举例的LED灯控制来实操吧。

            单片机硬件平台为雅特力AT32F421C8T7,库函数和STM32有一点点不同,不过大同小异。

           头文件zj_public.h

    //时基定时器
    #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_0
    #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)
    
    //时基全局变量
    extern volatile uint64_t gTimeBase;
    
    
    void zj_bsp_config(void);
    
    void zj_app_timebase_process(void);
    void zj_app_timebase_1ms_process(void);

       配置函数zj_bsp.c

    #include "zj_public.h"
    
    
    volatile uint64_t gTimeBase = 0;
    
    void RCC_Configuration(void)
    {   
        RCC_AHBPeriphClockCmd(RCC_AHBPERIPH_GPIOA, ENABLE);
    	RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_TMR6, ENABLE);//时基
    }
    
    void GPIO_Configuration(void)
    {
        GPIO_InitType GPIO_InitStructure;
    	GPIO_StructInit(&GPIO_InitStructure);
    
    	GPIO_InitStructure.GPIO_MaxSpeed = GPIO_MaxSpeed_50MHz;
    	//灯
    	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);
    }
    
    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 zj_bsp_config(void)
    {
      SystemCoreClockUpdate();
    
      RCC_Configuration();      
      NVIC_Configuration();
      GPIO_Configuration();
    
    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)
    {
    
    }
    
    void zj_app_timebase_1ms_process(void)
    {
    	gTimeBase++;
    }
    	
    static void TimeProcess_10MS(void)
    {
    
    }
    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();
      }
    }

    主函数main.c

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

            代码中特别注重了通用性的问题,时基这块应用完全可以移植到所有的单片机中去。烧录运行后就可以按到灯按照亮5秒灭5秒交替地运行了。

            volatile,提一下这个关键字吧,修饰变量是不稳定的意思,用法就是确保访问这个变量时不会被其他的优化或改写,确保数据准确,很适合这类毫秒级别访问的变量。

    小弟感谢大家的关注!

          (利他之心,原创分享)

    物联沃分享整理
    物联沃-IOTWORD物联网 » 探讨单片机时基处理的优秀代码思路(软件01)

    发表评论