单片机软件定时器的实现方法

1.1 背景目前市面上的单片机基本都带有硬件定时器功能,单片机应用程序开发中也经常会用到定时器进行一些和时间相关的开发,比如延时或者周期性地执行一些操作。单片机的硬件定时器个数一般都是固定的,而且一些低端单片机的定时器个数一般都比较少,在一些有多个周期性操作的应用场合就无法满足要求。这时,就可以基于硬件定时器派生出软件定时器,来满足这种多种周期性或多个单次延时操作的需求。软件定时器的优点就是个数可以根据实际需求进行灵活配置,而且可以实现多种不同的定时周期。
1.2 测试平台这里使用的开发环境和相关硬件如下。
操作系统:ubuntu 20.04.2 lts x86_64(使用uname -a命令查看)集成开发环境(ide):eclipse ide for embedded c/c++ developers,version: 2021-06 (4.20.0)硬件开发板:stm32f429i-disco本文对应的例程代码链接如下。https://download.csdn.net/download/goodrenze/85106391
1.3 软件定时器实现方法这里就结合开发板stm32f429i-disco上的stm32f429zi的单片机来演示软件定时器的实现方法。
一般定时器的计数方式有2种:一种是单次定时,即定时时间到了之后,自动停止定时;另一种是周期定时,定时时间到了之后,自动按照之前的定时周期重新定时。对于周期定时,可以手动进行定时器的启动、关闭和删除。
下面讲解软件定时器的实现步骤。
1)由于软件定时器是基于硬件定时器的,所以需要先初始化一个硬件定时器,并启动硬件定时器。这里使用stm32f429zi的硬件定时器7,定时器的定时周期为10ms,即每10ms产生一次定时器中断。初始化代码如下。
tim_handletypedef tim7handle;uint8_t inittim7(uint32_t period_ms){ uint16_t uwprescalervalue; if(0 == period_ms) { return 1; } __hal_rcc_tim7_clk_enable(); hal_nvic_setpriority(tim7_irqn, 2, 0); hal_nvic_enableirq(tim7_irqn); /* compute the prescaler value to have tim7 counter clock equal to 10 khz */ uwprescalervalue = (uint32_t) ((systemcoreclock /2) / 10000) - 1; /* set tim7 instance */ tim7handle.instance = tim7; tim7handle.init.period = period_ms * 10 - 1; tim7handle.init.prescaler = uwprescalervalue; tim7handle.init.clockdivision = tim_clockdivision_div1; tim7handle.init.countermode = tim_countermode_up; if(hal_tim_base_init(&tim7handle) != hal_ok) { return 1; } /* start the tim base generation in interrupt mode */ if(hal_tim_base_start_it(&tim7handle) != hal_ok) { return 1; } return 0;}2)硬件定时器定时时间到了之后,会产生中断,所以需要实现定时器中断处理函数。这里基于stm32的hal进行开发,所以在定时器的中断入口函数中直接调用hal_tim_irqhandler()函数,然后实现实际的中断处理回调函数hal_tim_periodelapsedcallback()。对应的代码如下。其中调用的软件定时器更新函数会在后面介绍。
void hal_tim_periodelapsedcallback(tim_handletypedef *htim){ if(htim == &tim7handle) { swtimerupdatecount(); }}3)设计软件定时器对应的结构体。按照软件定时器的实际使用特点,必须要包含定时器计数值和周期定时器的重装载值,另外还要有定时器时间到之后需要执行的回调函数。对应的软件定时器结构体如下所示。当结构体中的timercount和timerreload都为0时,说明软件定时器处于空闲状态,可以分配使用;如果timercount非0,而timerreload为0,说明软件定时器是单次定时器;如果timercount和timerreload都非0,说明软件定时器是周期定时器。
typedef void (*timercallbackfunc)(void);typedef struct _swtimer_t{ uint32_t timercount; uint32_t timerreload; timercallbackfunc timercallback;}swtimer_t;4)这里将软件定时器设置成10个,可以通过宏定义来设置软件定时器个数。使用软件定时器结构体定义一个具有10个定时器的数组。如下代码所示。
#define sw_timer_num 10swtimer_t swtimer[sw_timer_num];5)软件定时器复位函数,用于实现所有软件定时器的重置操作,重置后,所有软件定时器都处于空闲状态,可供分配使用。函数代码如下。
void swtimerreset(void){ uint8_t i; for(i = 0; i < sw_timer_num; i++) { swtimer[i].timercount = 0; swtimer[i].timerreload = 0; swtimer[i].timercallback = 0; }}6)启动单次定时器函数,用于实现单次定时,定时时间到了之后,执行对应回调函数,并停止定时和释放定时器资源。函数代码如下。如果启动成功,函数返回定时器的索引号(该值小于定时器个数值);启动失败,返回的定时器索引号等于定时器的个数。
uint8_t swtimerstartsingletimer(uint32_t single_ms, timercallbackfunc timercallback){ uint8_t i; single_ms /= mini_period_ms; if(0 == single_ms) { single_ms = 1; } for(i = 0; i < sw_timer_num; i++) { if((swtimer[i].timercount == 0) && (swtimer[i].timerreload == 0)) { swtimer[i].timercount = single_ms; swtimer[i].timercallback = timercallback; break; } } return i;}7)添加周期定时器函数,用于添加一个新的周期定时器但不启动定时,需要手动启动定时器。函数代码如下。如果添加成功,函数返回定时器的索引号(该值小于定时器个数值);添加失败,返回的定时器索引号等于定时器的个数。
uint8_t swtimeraddperiodtimer(uint32_t period_ms, timercallbackfunc timercallback){ uint8_t i; period_ms /= mini_period_ms; if(0 == period_ms) { period_ms = 1; } for(i = 0; i < sw_timer_num; i++) { if((swtimer[i].timercount == 0) && (swtimer[i].timerreload == 0)) { swtimer[i].timerreload = period_ms; swtimer[i].timercallback = timercallback; break; } } return i;}8)启动周期定时器函数,用于启动指定定时器索引号的周期定时器开始定时。函数代码如下。如果启动成功,函数返回定时器的索引号(该值小于定时器个数值);启动失败,返回的定时器索引号等于定时器的个数。
uint8_t swtimerstartperoidtimer(uint8_t timer_no){ if(sw_timer_num <= timer_no) { return sw_timer_num; } else if((swtimer[timer_no].timercount == 0) && (swtimer[timer_no].timerreload == 0)) { return sw_timer_num; } else { swtimer[timer_no].timercount = swtimer[timer_no].timerreload; return timer_no; }}9)停止定时器函数,用于结束指定定时器索引号的定时器的定时,可用于停止单次或周期定时器。函数代码如下。如果停止成功,函数返回定时器的索引号(该值小于定时器个数值);停止失败,返回的定时器索引号等于定时器的个数。
uint8_t swtimerstoptimer(uint8_t timer_no){ if(sw_timer_num <= timer_no) { return sw_timer_num; } else { swtimer[timer_no].timercount = 0; return timer_no; }}10)删除周期定时器函数,用于结束指定定时器索引号的周期定时器的定时,并释放定时器资源。函数代码如下。如果删除成功,函数返回定时器的索引号(该值小于定时器个数值);删除失败,返回的定时器索引号等于定时器的个数。
uint8_t swtimerdeleteperoidtimer(uint8_t timer_no){ if(sw_timer_num <= timer_no) { return sw_timer_num; } else { swtimer[timer_no].timercount = 0; swtimer[timer_no].timerreload = 0; return timer_no; }}11)软件定时器计数值更新函数,用于更新每个已经启动定时的软件定时器的计数值,该函数必须在硬件定时器的中断处理函数中调用。函数的实现思路是:遍历所有的软件定时器,如果遍历到的定时器的计数值非0,则进行减1操作。如果减1后计数值为0,如果定时器的重装载值非0,说明是周期定时器,需要将计数值更新成对应的重装载值以便重新定时,同时执行对应的回调函数;如果定时器的重装载值是0,说明是单次定时器,执行完回调函数后自动停止定时并释放定时器资源。如果减1后计数值不为0,继续遍历更新后续的定时器,直到所有定时器都遍历完毕。函数流程图和对应代码如下。
图1 软件定时器计数值更新函数
void swtimerupdatecount(void){ uint8_t i; for(i = 0; i < sw_timer_num; i++) { if(swtimer[i].timercount != 0) { swtimer[i].timercount -= 1; if(swtimer[i].timercount == 0) { if(swtimer[i].timerreload != 0) { swtimer[i].timercount = swtimer[i].timerreload; } if(swtimer[i].timercallback != 0) { swtimer[i].timercallback(); } } } }}12)软件定时器的实际使用示例。代码如下。
int main(void){ uint8_t no; hal_init(); /* configure the system clock to 168 mhz */ systemclock_config(); bsp_led_init(led3); bsp_led_init(led4); inittim7(mini_period_ms); swtimerreset(); no = swtimeraddperiodtimer(500, toggleled3); if(no < sw_timer_num) { swtimerstartperoidtimer(no); }#if 1 no = swtimeraddperiodtimer(1000, toggleled4); if(no < sw_timer_num) { swtimerstartperoidtimer(no); }#else no = swtimerstartsingletimer(5000, toggleled4);#endif while (1) { }}

一加手机5详细配置曝光,残暴无人性,搭载骁龙835,全玻璃机身!
QORVO推出MMIC功率放大器突破功率屏障
采用MAXQ2000实现与MAX1169的高速I²
CIC插值滤波器与直接频率合成器DDS的FPGA实现
数字功放与模拟功放的分析
单片机软件定时器的实现方法
2018年最具潜力的20大新材料
小鹏汽车实现100个城市充电服务:终身免费
ADV7441A: 10-Bit Integrated,
2020北京怀柔传感器产业发展研讨会 聚焦传感器产业发展热点难点
对CCD外观检查机的视觉检测系统的简单介绍
快速自动测氢仪,煤炭自动测氢仪厂家, 实验室测氢仪,中创仪器
CMOS振荡器设计
乾照光电收购博蓝特,距离LED产业链一体化又进一步
单相电表和三相电表有什么区别
三星电子打造开放式的智能IoT系统,助力IoT产业的整体发展
LGD正考虑将E5产线生产用于iPhone的OLED面板
天际汽车旗下增程式A+级SUV天际ME5介绍
三星今天宣布了一批采用AKG音频技术的耳机
降压变压器可以反向升压吗_降压变压器频率特性及原理