在公司做项目的时候,经常有一些需求会用到一些延时,比如说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延时函数好,可以减少占用资源,当然仁者见仁智者见智,也许有人也觉得累赘。