# 单片机软件定时器 **Repository Path**: milkli/mcu-software-timer ## Basic Information - **Project Name**: 单片机软件定时器 - **Description**: 软件定时器。适合于嵌入式裸机程序中使用的软件定时器,可以有效的解决STM32、51类单片机硬件定时器不足的问题。 - **Primary Language**: C - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 15 - **Forks**: 7 - **Created**: 2021-07-14 - **Last Updated**: 2025-03-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: 软件定时器, stm32, 51, 嵌入式 ## README # 单片机软件定时器 #### 介绍 本软件定时器实现了一个向下计数自动重装载的定时器。时基通过一个硬件定时器确定。不含有任何与底层MCU相关代码,可以移植到任何嵌入式平台。定时器回调函数的运行可以在中断中,或者在主函数while循环中执行。可以使用提供的各种API函数,来修改定时器的计数值、重装载值,运行次数。可以开启或关闭单个软件定时器。 #### 使用说明 1. 需要确定时基,一般由一个硬件定时器提供,将SoftwareTimer_Tick()函数放入定时器中断服务函数下。 ``` //stm32平台1ms定时器生成1s的时基的中断服务函数 void TIM1_UP_IRQHandler(void) { static uint16_t TimesCounter_ms = 0; if (TIM_GetITStatus(TIM1, TIM_IT_Update)) { TimesCounter_ms++; TIM_ClearITPendingBit(TIM1, TIM_IT_Update); if (TimesCounter_ms >= 1000) { TimesCounter_ms = 0; SoftwareTimer_Tick(); } } } ``` 2. 使用SoftwareTimer_Init()函数初始化一个软件定时器。 ``` //这里初始化了两个软件定时器,重装载2,计数值分别为0,1,循环无限次 //可以实现SW1_callback、SW2_callback两个回调函数前后依次运行 SoftwareTimer_Init(0, 0, 2, SW1_callback, FOREVER); SoftwareTimer_Init(1, 1, 2, SW2_callback, FOREVER); ``` 3. 将SoftwareTimer_Loop()函数放到想要回调函数运行的位置 ``` //这里把SoftwareTimer_Loop放到了主函数while循环中,回调函数会在这里被调用。 int main(void) { while(1) { SoftwareTimer_Loop(); } } ``` #### 使用注意 本定时器定时时长与选择硬件定时器周期、所使用的MCU平台相关(C51类单片机、ARM类单片机)。在相同平台下,时基越大则定时时间越长;不同平台下计数值所使用的位数不同,如STC15单片机(C51类)中long型数据类型长度为4个字节,int型为2个字节;而在STM32F103中(ARM类)中,int型、long型均为4个字节。本质上来说,此软件定时器是一个软件计数器计数的最大次数与两次计数的最长时间最终都是由底层MCU来决定。 要注意的是,单个软件定时器任务要按照编写中断服务函数的原则来编写,尽量不要使用过长的延时。如果一定要使用软件延时,可以将此延时通过状态机机制实现。单个定时器任务的运行时间最好不要超过定时器时基长度。 #### 存在的问题 在STC8A8k64S4A12单片机上出现了,软件定时器任务多次调用的问题。表现的现象是,在设置的时间点出现连续调用两次软件定时器任务的情况。具体原因未知,故请不要将定时上报的通讯任务使用在本定时器中,对于一些时序要求不严的定时采集传感器数据的任务使用本定时器影响不大。如果在使用中对您的开发造成了困扰,本人表示非常的抱歉。OTZ..... 问题可能的解决方式,再后面对此上述问题进一步研究时,发现如果使用函数嵌套后会出现上述问题。在对SofwareTimer_Loop()函数进行封装后,发现出现了上述问题,如果在main()函数中直接调用则不会出现,原因未+知。所以请尽可能的直接调用SofwareTimer_Loop()这个函数,如果为了代码规范可以使用宏定义的方式实现。 经过进一步测试,通过查看C51里的map文件发现了两个函数中for循环中的计数变量,使用的一个地址。但是其中一个循环计数变量在中断中被使用。这就可能导致在主函数运行的函数中循环计数变量数据被破坏(C51里的优化会将不同函数中的局部变量使用相同的地址,,PS:稍微有点坑啊~)。在STM32中由于有堆栈所以不会出现在C51类单片机中的问题。 经过大约一年的测试,整个程序运行良好。已经应用在了各类传感器采集类项目、控制器计时任务以及各种功能的实现。基本可以替换项目中需要用到延时的地方。 最后,这个代码在我本人的项目中已经类似于系统的存在,其实可以认为这是一个时间片轮询系统。所有创建的定时器都可以认为是这个系统中的任务,只不过没有上下文切换,抢占balabala这么一说。