1 起因
在嵌入式应用中,对于很多控制器管脚传过来的信号要做软件防抖。如下面的图1:
在状态S1时,若C1的信号过来了,定时器T1开始计时;当T1的值达到V1时,就认为C1的信号是有效的。同理,S2与S3是一样的情况。
图1 关联定时器
计数器T1、T2和T3是互相关联的。它们之中最多有一个不为0。每当一个计数器在计数前,都要复位其它计数器。
例如: 假如T1正在计数,v1的值为500。当T1计数到200时,A1条件不符合,进入了A2条件,就要先复位T1定时器,然后再对T2进行计数。这样保证了当A1条件符合时,T1可以从0开始计数,达到了设计目的;否则T1就是从200开始计数了。 |
图1中只有3个计数器,当计数器很多时,甚至在Condition=c1处再加入几个判断,不同定时器的操作就会很复杂,而且维护起来很困难。所以有必要把几个计数器集中起来进行管理。
2 分析
在图1中,先提出基本的实现:
实现a1:当State转移之后,首先要复位与此State下不相关的计数器(例子中只有一个T1;若不止一个的话,是不一样的);
实现a2:然后根据Condition的情况,相关计数器复位(C1)或是继续计算(T1++);
当计数器很多时,若每一个计数器分配一块内存,消耗很大;同时在需求中,最多只有一个计数器在工作,所以就定义一个计数器,通过其它名称来判断是为个条件计数。
实现a1就可以变成将计数器复位。考虑到这是一个循环执行的函数,就要保证不能每一次循环都要复位,这样就使用下面的算法完全没有作用了;
实现a2的计数器复位和实现a1一样,继续计算就是自增1;
现在就变成了两种情况:计数器复位和计数器数值自增1。而它们的前提是要分辨出前后操作的计数器是否一致。
那么计数器的名称在哪一步确定呢?
在图1中,因为条件C1并不是一定不会成立,这样当前计数器的计数和其它计数器的复位完全没有关系了,对于编程来说,不好操作,所以要更改程序流程,如图2:
图2 关联定时器
进而,实现就变成了如下的样子
实现b1:在不符合当前计数器计数条件下,将计数器复位,同时将计数器的名称复位;
实现b2:在符合当前计数器计数的条件下,当前要使用的计数器名称与上一次的名称不同,更新计数器名称;
实现b3:在符合当前计数器计数的条件下,当前计数器名称和上一次计数器的名称是相同的,计数器中的数值自增1。
根据实现b1,要为图2中的每个计数器分配一个名称。
通过对比C1~C4四种情况的计数器操作,可以看出根据这一次和上一次操作计数器名称的不同,每次操作最多修改两个计数器的值。
假设0为复位全部的计数器(如图2中的C3情况),1~4对应着T1~T4,LasterOrder表示上一次操作的计数器,Order表示本次操作的计数器,则对计数器的处理如表1所示:
表1 不同情况对计数器的处理
LastOrder | Order | 实际操作 |
0 | 0 | 什么也不做 |
0 | >0 | 计数器等于1 |
>0 | 0 | 计数器复位 |
>0 | =LastOrder | 计数器加1,不能让计数器溢出 |
>0 | !=LastOrder | 计数器等于1 |
现在已经很明确了,可以开始写代码了。
3 编码
如下:
#define TIMER_NUM 4 //计数器个数#define RESET_ALL 0 //复位名称#define T1 1#define T2 2#define T3 3#define T4 4ubyte CounterManage(ubyte Order){ static ubyte LastOrder; static uword CoupleCounter; if (Order > 255 || Order >= TIMER_NUM) //要修改的计数器不在正确范围内 { return 1; } else { if (LastOrder == 0) { if (Order > 0) { CoupleCounter = 1; } } else { if (Order = 0) { CoupleCounter = 0; } else if (Order = LastOrder) { CoupleCounter++; } else { CoupleCounter = 1; } } LastOrder = Order; return 0; }}
上面的代码对于计数器溢出的处理并没有做相应的设计。
同时,对于几种条件的判断也可以分成下面三种情况Order=0、Order=LastOrder和Order!=LastOrder我不是很喜欢这种写法,虽然代码少了,但是给理解增添了一些难度。我的标准就是一看就能明白什么意思,越简单越好。
另,流程很重要。像图一的流程,对于图二中多个Condition的情况,很难实现上面的方法。