乐于分享
好东西不私藏

【源码分享】MCU裸机下,非阻塞式定时器组实现方案

【源码分享】MCU裸机下,非阻塞式定时器组实现方案

 1 开 篇
写MCU裸机程序最难的部分往往不是业务逻辑,而是那些不起眼的细节处理;
譬如延时作为逻辑中必不可少的“螺丝钉”,处理方式会直接影响对整个设备的体验;
因此,针对非精确、固定延时的场景,本期内容将分享一个开销小、使用简单的非阻塞式定时器组实现方案,以供参考。
 2 阻塞延时与非阻塞延时
延时的概念都不陌生,控制LED以固定频率闪烁,需要延时…,Modbus主站任务定时轮询,也需要延时…;
但这些延时的处理方式不同,运行的效果也大不相同:
【阻塞延时】
这种延时方式最简单,常见形式如delay(100),内部为死循环,它的运行原理是基于systick定时器进行计数,判断计数结果决定是否跳出内部循环逻辑;
特点是延时过程CPU空等,直到延时结束才能继续向下执行;
     阻塞延时运行展示    
【非阻塞延时】
这种延时方式相对复杂一些,它的运行原理也是基于systick定时器计数,不同的是内部逻辑不再是死循环,而是通过判断定时标志决定后续的运行状态,但无论是否触发标志都会继续向下执行,触发则按顺序执行,没有触发则跳过此处代码块继续执行其它逻辑;
特点是CPU不再空等,可继续执行其他代码块;
     非阻塞延时运行展示    
由此,在MCU延时处理上,优先使用非阻塞延时可以大幅提高MCU的运行效率。
 3 非阻塞延时的实现原理
整个逻辑实现非常简洁,核心原理如下:
首先,实现定时器结构体;结构体内包含定时所需要的计数缓存、超时标志等变量,再将这个结构体定义为一个结构体数组,这样就开辟了一组相互独立运行的定时器。
.c/*自定义定时器结构体*/typedef struct timer{    uint8_t   TimerId;        //计时器id,索引定时器    uint32_t  TimerBaseValue; //计时基准,更新定时基准    uint32_t  TimerValue;     //定时值,存储定时值    uint8_t   Timer_Enable;   //开启    uint8_t   Timer_Disable;  //关闭} Timer;#define TimeNum   16          //定时器数量Timer userTimer[TimeNum];     //自定义定时器实体
接着,实现核心的定时算法;当使用32bit变量存储计时值,会出现两种情况,一种是计数没有溢出,另一种是计数溢出,按照下面算法,就可以实现定时器的循环计数,不用担心计时溢出、定时器失效的问题。
/* .c*说明:计算定时器时间间隔*参数:TimerID timerid 定时器ID,根据ID查询对应的定时器是否超时*返回:1/计时到达  0/计时未达到*/TimerFlags TimerInterval(TimerID timerid){           uint8_t Timer_ID = timerid;    //判断当前定时器是否启用?    if(userTimer[Timer_ID].Timer_Enable)      {        //检查当前定时器是否溢出?        if(userTimer[Timer_ID].TimerBaseValue <= u32SystickTimer)        {            //如果没有溢出,则直接计算并判断是否超时?            if((u32SystickTimer - userTimer[Timer_ID].TimerBaseValue) >= userTimer[Timer_ID].TimerValue)                return Timer_Success;            else                return Timer_Failure;        }        else        {            //如果溢出,先计算出实际时间间隔,最后判断是否超时?            if((0xffffffff - userTimer[Timer_ID].TimerBaseValue + u32SystickTimer) >= userTimer[Timer_ID].TimerValue)                return Timer_Success;            else                return Timer_Failure;        }    }    else   //若当前定时器未启用,则直接返回Failure    {        return Timer_Failure;    }}
除上述两个部分外,还有其它的部分,但都属于非核心的API接口,它们的作用是将结构体内的元素封装,这里不再赘述。
 3 使用方法
第一步,将非阻塞延时的计数接口放到Systick中断处理中,这样就可以实现定时计数,如果需要精确延时,就把计数接口放到高精度定时器中断函数中(至于时间的计算:定时时间=Systick触发间隔*计数值)
/* .cthis function handles SysTick exception*/voidSysTick_Handler(void){    /*定时时间间隔计数值递加*/    TimerValueAdd();    /*自定义Systick延时函数递减*/    userDelay_decrement();}
第二步,在定时器枚举中定义定时器的名称,这样有利于分辨定时器的作用,同时在定时器的初始化接口中,设置定时器触发的间隔,例如此处,设置的是1s触发。
/*定时器ID枚举*/typedef enum {    MONITOR_USART0_RS485_DEN = 0,  //monitor USART0 RS-485 DEN-PIN status timer    TIMER_NUM} TimerID;/*    ……其它逻辑*//**说明:初始化systick定时器*参数:none*返回:none*/voidSystickTimer_Init(void){    u32SystickTimer = 0x00;    /*初始化所有定时器*/    memset(userTimer, 0x00sizeof(userTimer));    //set monitor usart1 RS-485 status timer,1s    SetSystickTimer(MONITOR_USART0_RS485_DEN, SYSTICK_TIMER_1s);    }
第三步,在主逻辑中调用定时器初始化接口,然后在需要轮询处理的业务逻辑外增加定时判断,这样就可以实现非阻塞延时处理逻辑。
/* .c  */int main(void){    /*...其它初始化逻辑...*/    /*初始化systick定时器*/    SystickTimer_Init();    while (1)    {	        /*...其它逻辑...*/        if(TimerInterval(MONITOR_USART0_RS485_DEN))        {            /*...需要定时处理的业务逻辑...*/            //如果不需要轮询,可以调用接口关闭定时器            Disable_SysTimer(MONITOR_USART0_RS485_DEN);         }        /*...其它逻辑...*/    }}
按照上面的简单示例,可以配置多个非阻塞定时器,这些定时器相互间独立运行,不受影响。
 4  .h和.c源码
上述内容只是介绍了实现非阻塞延时的核心原理以及如何使用,还有一些辅助接口,如复位定时器、开启定时器等接口函数没有介绍,整个方案的源码如下:
/************************************************************************************说明:userSystick.h文件用于系统嘀嗒定时器计时调用接口*创建:MaJinWen*时间:2021.12.13*版本:V_1.0.0************************************************************************************/#ifndef __USERSYSTICK_H__#define __USERSYSTICK_H__#include<stdint.h>#include<stdio.h>/*Systick定时器模块参数封装*/#define SYSTICK_TIMER_100us			1#define SYSTICK_TIMER_10ms    		(100*SYSTICK_TIMER_100us)#define SYSTICK_TIMER_100ms      	(10*SYSTICK_TIMER_10ms)#define SYSTICK_TIMER_1s       		(10*SYSTICK_TIMER_100ms)#define SYSTICK_TIMER_1min     		(60*SYSTICK_TIMER_1s)#define SYSTICK_TIMER_1h       		(60*SYSTICK_TIMER_1min)/*函数返回值状态标志*/typedef enum status_flags{    Timer_Failure = 0,    Timer_Success = 1}TimerFlags;/*定时器ID枚举*/typedef enum {    MONITOR_USART0_RS485_DEN = 0,  //monitor USART0 RS-485 DEN-PIN status timer    TIMER_NUM} TimerID;/*自定义定时器结构体*/typedef struct timer{    uint8_t  TimerId;        //计时器id,索引定时器    uint32_t TimerBaseValue; //计时基准,更新定时基准    uint32_t TimerValue;     //定时值,存储定时值    uint8_t  Timer_Enable;   //开启    uint8_t  Timer_Disable;  //关闭} Timer;	/*自定义Systick延时函数*/voiduserSystick_Delay(uint32_t count);/*自定义Systick延时函数递减*/voiduserDelay_decrement(void);/*初始化systick定时器*/voidSystickTimer_Init(void);/*设置systick自定义定时器*/TimerFlags SetSystickTimer(TimerID timerid, uint32_t TimerValue);/*读取systick定时器的计数值*/uint32_tGet_SystickTimerValue(void);/*计算定时时间间隔*/TimerFlags TimerInterval(TimerID timerid);/*定时时间间隔计数值递加*/voidTimerValueAdd(void);/*Systick定时器关闭*/voidDisable_SysTimer(TimerID timerid);/*Systick定时器开启*/voidEnable_SysTimer(TimerID timerid);/*Systick定时器重置*/voidReset_SysTimer(TimerID timerid);#endif/*#ifndef __USERSYSTICK_H__*/
/************************************************************************************说明:userSystick.c源文件用于系统嘀嗒定时器计时调用*创建:MaJinWen*时间:2021.12.13*版本:V_1.0.0************************************************************************************/#include"userSystick.h"#include<string.h>volatile uint32_t u32SystickTimer = 0;        //定义一个计时变量volatile uint32_t u32SystickDelay = 0;        //定义一个延时变量#define TimeNum   16                  //定时器数量Timer userTimer[TimeNum];             //自定义定时器实体 /**说明:自定义延时函数,可以在Systick中断中调用*参数:count是延时计数值(计时时长 = count * systick定时)*返回:none*/voiduserSystick_Delay(uint32_t count){    u32SystickDelay = count;    do{        //等待延时计数值递减到0    }while(0U != u32SystickDelay);}/**说明:自定义Systick延时函数递减*参数:none*返回:none*/voiduserDelay_decrement(void){    if(0U != u32SystickDelay){        u32SystickDelay--;  //延时计数值做递减    }}/**说明:初始化systick定时器*参数:none*返回:none*/voidSystickTimer_Init(void){    u32SystickTimer = 0x00;    /*初始化所有定时器*/    memset(userTimer, 0x00sizeof(userTimer));    /*在这里添加定时间隔设置*/}/**说明:设置systick自定义定时器*参数:TimerID 定时器ID*      TimerValue 定时值*返回:设置成功返回true,设置失败返回flaes*/TimerFlags SetSystickTimer(TimerID timerid, uint32_t TimerValue){    uint8_t Timer_ID = timerid;    uint32_t Timer_Value = TimerValue;    /*设置定时器ID*/    userTimer[Timer_ID].TimerId = Timer_ID;    /*获取定时器计数的初始基准*/    userTimer[Timer_ID].TimerBaseValue = Get_SystickTimerValue();    /*设置定时器计时值*/    userTimer[Timer_ID].TimerValue = Timer_Value;    /*设置定时器默认开启*/    Enable_SysTimer(timerid);    if(userTimer[Timer_ID].TimerValue == 0)        return  Timer_Failure;    else        return  Timer_Success;}/**说明:读取systick定时器当前计数值*参数:none*返回:返回定时器计数值*/uint32_tGet_SystickTimerValue(void){    return u32SystickTimer;}/**说明:计算定时器时间间隔*参数:u32oldTick 定时器启动时获取到的初始计数值*返回:1/计时到达  0/计时未达到*/TimerFlags TimerInterval(TimerID timerid){           uint8_t Timer_ID = timerid;    if(userTimer[Timer_ID].Timer_Enable)    {        if(userTimer[Timer_ID].TimerBaseValue <= u32SystickTimer)        {            if((u32SystickTimer - userTimer[Timer_ID].TimerBaseValue) >= userTimer[Timer_ID].TimerValue)                return Timer_Success;            else                return Timer_Failure;        }        else        {            if((0xffffffff - userTimer[Timer_ID].TimerBaseValue + u32SystickTimer) >= userTimer[Timer_ID].TimerValue)                return Timer_Success;            else                return Timer_Failure;        }    }    else    {        return Timer_Failure;    }}/**说明:定时时间间隔计数值递加*参数:none*返回:none*/voidTimerValueAdd(void){    u32SystickTimer++;}/**说明:Systick定时器关闭*参数:TimerID timerid  定时器索引*返回:none*/voidDisable_SysTimer(TimerID timerid){    uint8_t Timer_Idx = timerid;    userTimer[Timer_Idx].Timer_Enable = 0x00;}/**说明:Systick定时器开启*参数:TimerID timerid  定时器索引*返回:none*/voidEnable_SysTimer(TimerID timerid){    uint8_t Timer_Idx = timerid;    userTimer[Timer_Idx].Timer_Enable = 0x01;}/**说明:Systick定时器重置*参数:TimerID timerid  定时器索引*返回:none*/voidReset_SysTimer(TimerID timerid){    /*重新获取定时器计数基准*/    userTimer[timerid].TimerBaseValue = Get_SystickTimerValue();}
以上就是整个非阻塞式定时器组的实现源码。
 5  写在最后
个人觉得这个“轮子”还是很好用的,尤其是在处理LED闪烁、Modbus主站轮询、喂狗等逻辑,这个版本也是迭代多次才完善好,也已在项目中反复验证过。
本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 【源码分享】MCU裸机下,非阻塞式定时器组实现方案

评论 抢沙发

2 + 1 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮