利用一个硬件定时器实现多个虚拟定时器的两种方法

第一种方法比较适合单片机,第二种方法在 PC 上很有优势。
1.固定周期法  【重点看这种方法的实现】
使用一个硬件定时器进行固定周期(比如 1m s)定时,用一个结构体数组作为软定时器描 
述表, 数组的结构体数就是最大虚拟定时器的数量,  每个结构体的成员都包括虚拟定时器状 
态(空闲、激活、运行、超时触发、周期触发)、定时值(换算成定时周期数,例如  1m s  的硬 
件定时周期, 现进行 125m s 的定时, 定时值就是 125 )、 标识 ID 和回调函数等; 用一个变量 
作为定时周期计数器, 每次进入定时中断, 重置定时器, 扫描结构体数组中的每个成员结构 
体, 对定时值做减一操作, 然后判断该定时值是否为 0, 是则判定该值对应的虚拟定时器定 
时时间到, 调用相应的回调函数进行相应的处理。 如此往复。 比如有三个定时需求, 分别是 
30ms、80ms、62ms,硬件定时周期 1m s 且随系统初始化后一直运行,在每次定时中断后去 
扫描那个结构体数组,在某次定时中断退出后不久,30ms  定时任务来了,找一个结构体初 
始化激活其状态置定时值 30, 又一次硬件定时中断到, 置该结构体状态为运行, 开始定时, 
又经过 14 个硬件定时周期, 该定时值变为 15, 此时第二个定时任务来了, 新开虚拟定时器 
结构体置定时值 80 并将其状态置位激活, 又经过 10 个定时周期,  这两个结构体定时值分别 
为  5 和 71,这时第三个定时任务来了,同上再激活一个成员结构体并置定时值 62,然后又 
5 个硬件定时周期中断后  30m s 定时到期,置对应结构体状态空闲释放该虚拟定时器并通过 
设置的回调函数指针调用回调函数,此时还剩  2  个定时任务在运行,一个值为  66,另一个 
值为  58,再经过  58 和  66  个硬件定时周期后定时任务都执行完毕,至此描述了一个利用固 
定硬件定时周期来执行多个定时任务的典型过程,  在这里有两个问题, 一是虚拟定时器激活 
后在下一个硬件定时中断后才会开始运行,  也就是说有可能有接近一个硬件定时周期的定时 
误差(延迟) ,如果没有激活态直接运行则也有可能是一个硬件定时周期的定时误差(提前), 
总之这种方法一定会有一个硬件周期的定时误差;  第二个问题是在定时中断中进行结构体数 
组扫描、运算以及有可能的回调函数的执行会消耗  CPU 指令周期的,势必会使定时中断函 
数执行时间变长有可能影响其他中断响应, 因此要注意在该硬件中断中开全局中断,  允许其 
他中断响应,  并且硬件定时器值重置要一进中断就重置以免影响硬件定时周期的精度, 还有 
必须保证在硬件定时周期内执行完这个中断程序,以避免中断重入。 
2.浮动定时法 
定时器咱也只用一个,  接口函数是设置硬件定时器和获得定时器运行剩余时间, 还有就 
是定时中断会调用中断函数, 资源就这么些, 我们也要通过软件来虚拟定时器了。 硬件定时 
器有相关的定时寄存器资源来设置定时时间和一些配置参数,  用软件来模拟也需要定时时间 
和配置参数来描述这个定时器,这里我们用一个结构体来保存这些参数数据,其成员包括: 
定时器状态(空闲、激活、在使用、超时触发、周期触发) 、全局结构体指针、回调函数指 
针(用于定时时间到后调用触发函数) 、ID(用于标识定时器) 、定时时间和定时周期(0 表 
示非周期) 。因为要虚拟多个定时器,所以这个结构体要变成结构体数组了。好了,虚拟定 
时器的描述参数表有了, 怎么实现定时功能呢?现假设要实现 3 个定时任务, 任务 1 要实现 
2 秒定时,任务 2 要实现 3 秒定时,任务 3 实现 1 秒定时。首先用虚拟定时器设置函数将 2 
秒定时值设置到虚拟定时器参数结构体[0](虚拟定时器  1) ,启动  2 秒的硬件定时,然后置 
参数结构体[0]的定时器状态为使用中,  则表示虚拟定时器 1 开始工作了,  接下来设置任务 2, 
查找空闲虚拟定时参数结构体数组,就是结构体[1]了(虚拟定时器  2) ,将定时值  3 秒写入 
结构体,再获得硬件定时器运行剩余时间,假设还有 1.5 秒定时时间到,小于写入的 3 秒, 
硬件定时器继续运行,置参数结构体[1]的定时器状态为使用中,则表示虚拟定时器  2  开始 
工作了, 最后设置任务 3, 查找空闲虚拟定时参数结构体数组, 就是结构体[2] (虚拟定时器
2)了,将定时值  1  秒写入结构体,再获得硬件定时器运行剩余时间,假设还有  1.2  秒定时 
时间到,那么任务  1  对应虚拟定时器参数结构体[0]的定时时间还剩  1.2  秒,结构体[1]剩余 
定时时间  3­(2­1.2)=2.2  秒,然后判断剩余定时时间哪个最小,是结构体[2]的定时值(1 
秒) ,设置硬件定时器  1  秒,置总定时时间变量值为(2­1.2)+1=1.8  秒,置参数结构体[2] 
的定时器状态为使用中,虚拟定时器  3  开始工作了;之后再没有需要定时任务了,等待  1 
秒定时时间到,调用定时中断处理函数,该函数查询到这次结束的定时触发结构体[2]对应 
的虚拟定时器 3 定时时间到, 确定是任务 3 定时时间到了, 那么执行任务 3 对应的定时触发 
函数吧; 之后将结构体[0]和结构体[1]的定时值都减去总定时时间 (1.8 秒) 得到 0.2 秒和 1.2 
秒, 然后设置 0. 2 秒硬件定时, 释放虚拟定时器 3, 定时到后对应的任务 1 触发函数被执行, 
虚拟定时器  1 完成被释放,最后设置 1.2­0.2=1 秒定时,定时时间到后,执行任务  2 对应触 
发函数,虚拟定时器  2  最后被释放。至此三个定时任务实现并行执行的目的,也就是用  1 
个硬件定时器实现了并行的 3 个定时任务, 不就是相当于 3 个定时器同时运行, 达到了多个 
定时器的目的了么! 同理超过 3 个定时任务也能轻松实现, 且是宽定时时间的, 因为上述方 
法实现了定时时间分段了,当然长定时时间可以分成多段自然就符合硬件定时器的要求了, 
并且实现周期性定时也没问题。  该方法要比固定周期法复杂, 但却可以避免一个定时周期的 
误差, 同时效率更高, 减少了定时中断响应, 然而问题也还是有的, 下一次的定时周期都要 
在当次定时中断中计算确定,这段计算时间形成了误差(延迟) ,不过计算控制的好的话相 
比固定周期法一个周期的误差要小得多;  还有就是复杂度上升意味着在中断中执行程序的时 
间也加大,并且可变的定时周期更要注意避免中断重入。 
当然上述两种方法的中断响应程序中大部分可以放在中断程序外运行,  即放在系统大循 
环里, 但是这种情况又形成了新的误差就是这个大循环周期。 如果一定要这么做, 那么这个 
大循环的周期的延迟要在你的定时的实时性误差允许范围内。虚拟定时器毕竟是虚拟出来 
的,不可能达到硬件定时器的精确度,不过在很多应用中还是很好用并且是必不可少的。 
上述两种方法笔者均在 A VR 单片机和 PC 上实现过,效果都还不错。 

你可能感兴趣的:(单片机/STM32)