好,回到定时器最初的功能,定时,先掏出手机,定时十分钟,然后等着它响,盯着时间、告诉我你看到了什么?对,跳动的秒数、分数。到这里恭喜你就理解了定时器最最核心的概念:计数。这个计数可不是你自己念的1、2、3,而是规律跳变的数字,也就是时基。因此定时器的定时功能依赖时间的变化、时间的变化依赖计数、计数依赖基本的时基。比如光传播约299792458米的时间或者铯原子震荡9192631770次就是一秒。
所以简单总结一下:定时器的核心功能依托计数实现,具体表现就是计数器,那计数器有什么用呢,比如你想从七点半定时到七点三十一分,需要的计数值就是60,同理定时到七点三十二分所需的计数是120,所以计数器的值直接决定了我们需要的定时时间。所以知道计数器有多重要了吧。
现在到了你定时的时间,手机想起了铃声,这就是我们熟悉的中断,虽然你现在可能在放音乐,那也会停掉去放铃声。当然不会像下面这么简单。
void 闹铃_handler(void)
{
铃声.play();
}
你得获取闹铃服务对象,AlarmManager alarmManager = (AlarmManager) getSystemService(Service.ALARM_SERVICE);然后设置一个时间、是否在睡眠模式下有效、绑定闹铃回调动作等等。
至此最简单的定时应用你就学会了。如果你需要每天六点半起床怎么办,总不能每天设置一次闹钟吧,这之后你可以选择重复模式。就像以前的指针闹钟一样。
其实倒不是闹钟有循环模式,只是时间走到12点它就循环从1点又开始了。这也是我以前好奇闹钟下午六七点也会响的原因。有没有发现一点很像的样子,比如你的uint8_t变量加到255再加加就变成了0,没错,定时器循环工作的秘密就在这。
现在再次梳理一下:定时器依赖计数器达到我们所需的定时,定时满了溢出之后不停止就有了循环模式。有没有发现一个问题,如果任着闹钟自行运作的话,就只能每间隔12小时响一次,如果想6小时响一次怎么办呢?两种办法,一个是让闹钟一圈就转6个小时,不过也太麻烦了,还有就是响铃之后马上调成12点,不过也需要一直等着闹铃响。
当然了上述的情况基本上是用不到的,我们还有更灵活的单片机定时器,单片机的定时器同样是依托计数器实现的,闹钟数60秒变0从1开始,计数器数到65536变0从1开始,至于为什么是这么大,16位二进制最大就是这么大,2^16,就好像为什么只有2、4、8、16、32和64位的处理器一样,同样的数到65536的时候溢出变成0,触发更新中断,这时候你定时的时间就到了,不关闭它它就一直这么重复定时,又双叒叕触发中断。
欸,如果我不想定时这么久怎么办呢?计数器不让它计到那么大不就行了嘛,是的,计数器作为寄存器它允许你修改,可以让它计数到32768就变成0然后从1重新开始,这时候你定时的时间就是之前的一半了,同样它将重复的一次次产生中断告诉你你定时的时间到了。
不过细想一下,计数器本来不就是一直在变的嘛,那红红火火恍恍惚惚中修改的貌似不是计数器值,而是限制计数器的值吧,是的,这玩意叫预装值,它限制了计数器所能数到的最大值,而更新中断一方面是告诉你定时的时间到了,另外还会把预装值生效,就比如上次你修改了最大计数是32768,那么在这次数到65536的时候就把32768装载上了,所以接下来这次只需要数到32768就会变成0,然后再把预装放进去。这样你就可以随时随地想定时多久就定时多久。
不过有没有发现一个问题,即使你最大预装65536,可是时间还是不够怎么办?比如时基是1ms,那你最大才能定时65.536s,我想定时五分钟怎么办,这里引入另一个东西,叫预分频器,什么意思呢,就是把时基给它放慢,跟预装值一样,可设定0~65535,也就是可以放慢1~65536倍,比如你想要五分钟,那我直接把时基放慢10倍,就是把预分频器设置成9,这样最大计时就可以达到655.35s了,你想要的五分钟就是300s,预装载设置成30000-1=29999不就行了嘛。这样一来可定时范围瞬间放大65536倍。
值得注意的是,如果为什么预装值要下一次生效,比如现在计数器已经数到200了,你想要它数到100就从0开始,这时候显然就是错误的了,所以要按照上一次值就行比较,然后等下一次从头开始的时候再按照新修改的值,就像播放视频的时候把源文件删掉一样,要不播放器自己备份一份,要不直接文件加锁不给删。回到定时器,既然现在的预装还在生效、用户设置的新预装也有,所以这就有了影子寄存器的概念,我们直接修改的预分频和预装值都有影子寄存器,在计时器溢出更新的时候他们的值复制进影子寄存器起效,因此计数器与之比较的影子寄存器,也就是上一次的预装值或者预分频值。
现在还差点什么呢?我们一直强调的时基,也就是定义时间的基准。比如1us的脉冲信号作为时基的话(也就是1MHz时钟),所能定时的最小值就是1us,最大值就是65536*65536us,看到这里大家应该发现了,定时器定时的精度取决于时基,输入时钟频率越高,定时器精度越高。不过相应的定时的时间也就变短了。所以还是却决于时钟输入和寄存器位数。不过当前的数值也满足绝大多数的应用了。愣是想用51做纳秒中断它也做不到对吧。
到目前为止我们已经知道了定时器的基本构造,就差怎么利用它了,说明书就是参考手册,不过在看参考手册之前在来了解一下STM32给留出的开关,作为一个定时器,最基本的开关得有吧,也就是使能,啪,扳上去定时器就开始跑,啪,扳下来定时器就停了,定时一次时间到了就关掉定时器的控制开关叫单脉冲模式,啪、扳上去,定时时间到了就关上了。啪、扳下来,定时时间到了还会继续下一次计时,不会关了定时器。还有开关控制是否使能中断、是否触发DMA等等。除此外还有寄存器用于显示更新状态。
简化一下定时器,就是上图了。实际上肯定会复杂很多。对比一下就发现了,似乎跟洗衣机面板很像,电源输入进来、设置好漂洗时间、设置好漂洗强度、是否甩干、多次清洗、完成提示音等等,最后按下开始按键,等待计时时间计数,完成后发出提示音提醒。
实际上包括计数器、预分频等这些数值和中断使能、计数器使能这些开关及更新标志位全部被封装在一个叫寄存器的东西,比如控制计数器使能的控制寄存器CR(Control register),甚至位不够用分成CR1和CR2两部分控制寄存器。所以这时候就不是啪的一下按开关来打开定时器了,而是写寄存器的值,它的某一位的0或者1表示打开还是关闭定时器。所以我们就可以写这个寄存器的值来打开定时器、使能中断等等,又或者读取状态寄存器SR(Status register)的某个位是0还是1判断有没有发生更新中断。
至于为什么要绑定在一起成为寄存器控制,就像结构体一样,把类似属性的东西放置在一起比较方便管理。又或者仪表盘的状态指示和功能控制也是集中放置在一起的。反正就是一堆开关、变量和状态、留给用户去使用。
想要找到这些开关或者称之为寄存器,需要知道他们的地址,然后通过地址来定位并读写它们就完成了对定时器的使用。
|