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