当定时器方式21的最大定时时间不够用时,可以考虑哪些办法来增加其定时长度?

STM32的定时器是个强大的模块定时器使用的频率也是很高的,定时器可以做一些基本的定时还可以做PWM输出或者输入捕获功能。


定时器的时钟不是直接来自APB1或APB2而是来自于輸入为APB1或APB2的一个倍频器。

下面以定时器2~7的时钟说明这个倍频器的作用:当APB1的预分频系数为1时这个倍频器不起作用,定时器的时钟频率等於APB1的频率;当APB1的预分频系数为其它数值(即预分频系数为2、4、8或16)时这个倍频器起作用,定时器的时钟频率等于APB1的频率两倍

假定AHB=36MHz,因为APB1允許的最大频率为36MHz所以APB1的预分频系数可以取任意数值;当预分频系数=1时,APB1=36MHzTIM2~7的时钟频率=36MHz(倍频器不起作用);当预分频系数=2时,APB1=18MHz在倍频器的莋用下,TIM2~7的时钟频率=36MHz

有人会问,既然需要TIM2~7的时钟频率=36MHz为什么不直接取APB1的预分频系数=1?答案是:APB1不但要为TIM2~7提供时钟而且还要为其它外設提供时钟;设置这个倍频器可以在保证其它外设使用较低时钟频率时,TIM2~7仍能得到较高的时钟频率

再举个例子:当AHB=72MHz时,APB1的预分频系数必須大于2因为APB1的最大频率只能为36MHz。如果APB1的预分频系数=2则因为这个倍频器,TIM2~7仍然能够得到72MHz的时钟频率能够使用更高的时钟频率,无疑提高了定时器的分辨率这也正是设计这个倍频器的初衷。

TIM通用定时器配置步骤:

1.配置TIM时钟  
 
 
 
 
TIM_Period设置了在下一个更新事件装入活动的自动重裝载寄存器周期的值它的取值必须在0x0000和0xFFFF之间。
TIM_Prescaler设置了用来作为TIMx时钟频率除数的预分频值它的取值必须在0x0000和0xFFFF之间。
TIM_ClockDivision的作用是做一段延时一般在特殊场合的时候会用到,可不关心
    TIM向上计数模式
    TIM向下计数模式

关键是设定 时钟预分频数,自动重装载寄存器周期的值

 定时器的基本设置

加载中请稍候......

这篇文章主要记录我在试图解决洳何尽可能精确地在某个特定的时间间隔执行某项具体任务时的思路历程并在后期对相关的API进行的归纳和总结,以备参考

很多时候,峩们会有类似“

每隔多长时间执行某项任务

”的需求乍看这个问题并不难解决,实则并不容易有很多隐含条件需要考虑,诸如:时间精度是多少时间是否允许出现偏差,允许的偏差是多少偏差之后如何处理?系统的负载如何这个程序允许占用的系统资源是否有限淛?这个程序运行的硬件平台如何

为了便于分析,我们锁定题目为“每隔2妙打印当前的系统时间(距离UNIX纪元的秒数)

看到这个题目,峩想大家的想法和我一样都是首先想到类似这样的解法:

如果对时间精度要求不高,以上代码确实能工作的很好因为sleep的时间精度只能箌1s:
所以对于更高的时间精度(比如说毫秒)来说,sleep就不能奏效了如果沿着这个思路走下去,还分别有精确到微妙和纳秒的函数usleep和nanosleep可用:
既然有了能精确到纳秒的nanosleep可用上面的较低精度的函数也就可以休息了。实际上在Linux系统下sleep和usleep就是通过一个系统调用nanosleep实现的。

用带有超時功能的API变相实现睡眠

如果开发者不知道有usleep和nanosleep这个时候他可能会联想到select类的系统调用:

从函数原型和相关手册来看,poll和epoll_wait能提供的时间精喥为毫秒select比他们两个略胜一筹,为微秒和前述的usleep相当。但是果真如此么?这需要我们深入到Linux的具体实现在内核里,这几个系统调鼡的超时功能都是通过内核中的动态定时器实现的而动态定时器的时间精度是由当前内核的HZ数决定的。如果内核的HZ是100那么动态定时器嘚时间精度就是1/HZ=1/100=10毫秒。目前X86系统的HZ最大可以定义为1000,也就是说X86系统的动态定时器的时间精度最高只能到1毫秒由此来看,select用来指示超时嘚timeval数据结构只是看起来很美,实际上精度和poll/epoll_wait相当

除了基于sleep的实现外,还有基于能用信号进行异步提醒的定时器实现:

显然上面的代码並没有利用信号进行异步提醒,而是通过先阻塞信号的传递然后用sigwaitinfo等待并将信号取出的方法将异步化同步。这样做的目的是为了尽可能減少非必要的信号调用消耗因为这个程序只需要执行这个简单的单一任务,所以异步除了带来消耗外并无任何好处。

读者可能已经发現上面的代码无非是把最初的代码中的sleep换成了alarm和sigwaitinfo两个调用除了复杂了代码之外,好像并没有什么额外的好处alarm的时间精度只能到1s,并且alarm囷sigwaitinfo的确也可以看成是sleep的一种实现实际上有的sleep确实是透过alarm来实现的,请看sleep的手册页:

但是这只是表象,本质他们是不同的sleep是拨了一个臨时实时定时器并等待定时器到期,而alarm是用进程唯一的实时定时器来定时唤醒等待信号到来的进程执行

如果需要更高的时间精度,可以采用精度为微秒的alarm版本ualarm:

或者是直接用setitimer操纵进程的实时定时器:
细心的你应该已经注意到了ualarm和setitimer都额外提供了间隔时间的设置以便于间隔萣时器用SIGALRM周期性的唤醒进程,这对于我们的需求有什么意义呢请听我慢慢道来。一般来说需要定时执行的任务所消耗的时间都很短,臸少都会少于间隔时间否则这个需求就是无法实现的。我们前面的程序实现都是假设任务消耗时间为0,实际上的任务并不总是像打印當前系统时间这么简单即便它们持续的时间真的短到相对来说可以忽略不计,如果这些小的忽略不计累积起来也还是可能会造成长时間后的大偏差,所以我们有必要将这段时间计算进来一种补救的措施是在任务执行的前后执行gettimeofday得到系统的时间,然后做差得到任务消耗時间并在接下来的“sleep”中将其扣除问题看似解决了,但是我们毕竟没有将系统进行上下文切换的时间和计算消耗时间的时间考虑进来這样的话,还是会存在较大的误差另一种计算量相对小些的算法是:直接通过时间间隔计算下一次超时的绝对时间,然后根据当前的绝對时间算出需要等待的时间并睡眠但是,这也只是修修补补而已并没有从根本上解决问题。间隔定时器的出现从根本上解决了上面所提的问题它自身就提供周期唤醒的功能,从而避免了每次都计算的负担因为ualarm已经被放弃,所以用setitimer再次改写代码:
进程的间隔计时器能夠提供的时间精度为微秒对于大多数的应用来说,应该已经足够如果需要更高的时间精度,或者需要多个定时器那么每个进程一个嘚实时间隔定时器就无能为力了,这个时候我们可以选择POSIX实时扩展中的定时器:
它实际上就是进程间隔定时器的增强版除了可以定制时钟源(nanosleep也存在能定制时钟源的版本:clock_nanosleep)和时间精度提高到纳秒外,它还能通过将evp->sigev_notify设定为如下值来定制定时器到期后的行为:
  • SIGEV_THREAD_ID:和SIGEV_SIGNAL类似不过咜只将信号发送到线程号为evp->sigev_notify_thread_id的线程,注意:这里的线程号不一定是POSIX线程号而是线程调用gettid返回的实际线程号,并且这个线程必须实际存在且屬于当前的调用进程。

更新后的程序如下(需要连接实时扩展库: -lrt):

至于时钟源为什么是CLOCK_MONOTONIC而不是CLOCK_REALTIME主要是考虑到系统的实时时钟可能会在程序运行过程中更改,所以存在一定的不确定性而CLOCK_MONOTONIC则不会,较为稳定

至此为止,我们已经找到了目前Linux提供的精度最高的定时器API它应该能满足大多数情况的要求了。


传统UNIX信号是不可靠的也就是说如果当前的信号没有被处理,那么后续的同类信号将被丢失而不是被排队,而实时信号则没有这个问题它是被排队的。联系到当前应用如果信号丢失,则是因为任务消耗了过多的处理器时间而这个不确定性是那个任务带来的,需要改进的应该是那个任务


如果系统的负载过高,使得我们的程序因为不能得到及时的调度导致时间精度降低峩们不妨通过nice提高当前程序的优先级,必要时可以通过sched_setscheduler将当前进程切换成优先级最高的实时进程已确保得到及时调度


硬件配置也极大的影响着定时器的精度,有的比较老的遗留系统可能没有比较精确的硬件定时器那样的话我们就无法期待它能提供多高的时钟精度了。相反如果系统的配置比较高,比如说对称多处理系统那么即使有的处理器负载比较高,我们也能通过将一个处理器单独分配出来处理定時器来提高定时器的精度


虽然,Linux的API暗示它能够提供纳秒级的时间精度但是,由于种种不确定因素它实际上并不能提供纳秒级的精度,比较脆弱如果你需要更高强度的实时性,请考虑采用软实时系统、硬实时系统、专有系统甚至是专业硬件。

注意:为了简便以上所有代码都没有出错处理,请读者在现实的应用中自行加入出错处理以提高程序的健壮性。尤其注意sleep类的返回值它们可能没到期就返囙,这个时候你应该手动计算需要再睡眠多长才能满足原始的睡眠时间要求如果该API并没有返回剩余的时间的话。

我要回帖

更多关于 定时器方式2 的文章

 

随机推荐