写一个软件定时器
在公司做项目的时候,经常有一些需求会用到一些延时,比如说MCU有一个引脚需要延时100ms后拉高,保持200ms后再拉低。最简单的办法就是调用延时函数,delay_ms(t)来实现,但是这个函数实际就是一直在这里等待,啥事都不管了,这很浪费资源,所以我想通过某些方法来实现延时,而且在延时的时候MCU可以去处理其他一些事情,这样才不耽误事。
Cortex-M系列的单片机都有一个滴答定时器,也就是SysTick,一般用于提供系统节拍,很多单片机官方提供的库函数也都是把它配置为1ms中断一次,典型的例子就是stm32和GD32。它们的库函数中一般有以下这个中断服务函数:
void SysTick_Handler(void) { delay_decrement(); }
这个中断服务函数一般是1ms进入一次,所以想到了利用这个中断服务函数来做一个软件定时器的功能:①可以任意添加软件定时器(比如说LED1闪烁(频率100ms),LED2闪烁(频率200ms),LED3闪烁(频率400ms)……);②可以选择时单次计时,或者多次计时,或者连续不间断地计时;③计时时间到后可以根据实际情况执行不同的操作;④代码要简洁,要求把基础代码完善后,每次用到软件定时器时只需要调用少量的接口函数就能实现功能;
代码如下:
MyApplication.h #ifndef _MYAPPLICATION_H_ #define _MYAPPLICATION_H_ #include "gd32e50x.h" #include "systick.h" #include "uart.h" #include "key.h" #include "led.h" #define MAX_TIMER_NUM 10 #define LED_TIMER 0 typedef struct { uint32_t Delay; uint32_t Cnt; uint8_t State; uint8_t Timeout; uint8_t Continue; }SoftTimer; typedef enum { Disable = 0, Enble, }STATE; typedef enum { FALSE = 0, TRUE, }TrueOrFalse; typedef enum { OK = 0, Error, }Result; extern SoftTimer soft_timer[MAX_TIMER_NUM]; Result Soft_Timer_Add(uint8_t timer,uint32_t delay,uint8_t IsContinue); void Soft_Timer_Run(void); void Soft_Timer_Task(uint8_t timer,void(*Handle)(void)); #endif
MyApplication.c #include "MyApplication.h" SoftTimer soft_timer[MAX_TIMER_NUM]; Result Soft_Timer_Add(uint8_t timer,uint32_t delay,uint8_t IsContinue) { if(timer >= MAX_TIMER_NUM) { return Error; } if(soft_timer[timer].State != ENABLE) { soft_timer[timer].State = ENABLE; soft_timer[timer].Delay = delay; soft_timer[timer].Cnt = 0; soft_timer[timer].Timeout = FALSE; soft_timer[timer].Continue = IsContinue; } return OK; } void Soft_Timer_Run(void) { uint8_t i; for(i = 0; i < MAX_TIMER_NUM;i++) { if(soft_timer[i].State != DISABLE) { soft_timer[i].Cnt++; if(soft_timer[i].Cnt >= soft_timer[i].Delay) { soft_timer[i].Timeout = TRUE; } } } } void Soft_Timer_Task(uint8_t timer,void(*Handle)(void)) { if(soft_timer[timer].State != DISABLE) { if(soft_timer[timer].Timeout != FALSE) { Handle(); if(soft_timer[timer].Continue != FALSE) { soft_timer[timer].Cnt = 0; soft_timer[timer].Timeout = FALSE; } else { soft_timer[timer].Cnt = 0; soft_timer[timer].Delay = 0; soft_timer[timer].Timeout = FALSE; soft_timer[timer].Continue = FALSE; soft_timer[timer].State = DISABLE; } } } }
应用部分:
gd32e50x_it.c
void SysTick_Handler(void)
{
delay_decrement();
Soft_Timer_Run();
}
main.c void LED_Running(void) { static uint8_t state = 0; state++; if(state == 5) state = 0; switch(state) { case 0 : LED1_OFF;LED2_OFF;LED3_OFF;LED4_OFF; break; case 1 : LED1_ON;LED2_OFF;LED3_OFF;LED4_OFF; break; case 2 : LED1_ON;LED2_ON;LED3_OFF;LED4_OFF; break; case 3 : LED1_ON;LED2_ON;LED3_ON;LED4_OFF; break; case 4 : LED1_ON;LED2_ON;LED3_ON;LED4_ON; break; } } int main(void) { systick_config(); led_init(); usart_config(); key_init(); Soft_Timer_Add(LED_TIMER,500,TRUE); while(1)
{ Soft_Timer_Task(LED_TIMER,LED_Running); } }
这是一个跑马灯的程序,有4个LED灯,实现的功能就是按顺序逐个点亮LED灯,然后熄灭,再重新逐个点亮……频率是500ms,
① Result Soft_Timer_Add(uint8_t timer,uint32_t delay,uint8_t IsContinue)
该函数是添加软件定时器,第一个参数timer实际是一个软件定时器的编号,必须小于宏定义 MAX_TIMER_NUM 的值;第二个参数delay是该软件定时器需要延时的时间,第三个参数是是否连续计数,决定这个定时器计时时间到之后是停止计数或者继续重新计数;成功返回OK,失败返回Error;
② void Soft_Timer_Run(void)
该函数是核心部分,这个函数在SysTick定时器的中断服务函数中调用,它会轮询各个软件定时器(根据软件定时器编号),看是否是ENABLE状态,如果是,再判断该定时器的计数值是否达到了延时的数值,如果是,即是计时时间到,否则计数值继续+1;如果是DISABLE状态,即是没启用这个定时器,那就不需要执行任何动作;
③ void Soft_Timer_Task(uint8_t timer,void(*Handle)(void));
该函数是判断某个定时器是否已经计时到了,并且执行相关的操作。第一个参数是软件定时器编号,第二个参数是一个函数指针,使用者具体要执行的操作就通过这个指针传入这个函数,在即使时间到之后执行操作。这个函数可以写在主循环中,也可以写在SysTick中断服务函数中,各有利弊,需要使用者自己衡量利弊,写在主循环中,你需要确保主循环中处理的其他事情不会占用很多时间,不然精度就会受到影响,大大降低。写在中断服务函数中,就会增加中断服务函数执行的时间,中断服务函数一般不允许占用太长时间,不然会出问题的,,当然如果你只使用到10个以内的软件定时器,应该不会有问题,这点代码量应该不算太多。
上面的代码还没有完全完善,暂时没有多次计数的功能,只有单次计数和连续不间断计数。有空我还会在完善一下。个人觉得这样会比单纯用delay延时函数好,可以减少占用资源,当然仁者见仁智者见智,也许有人也觉得累赘。