vxwoks如何获得毫秒级时间问题
不管如何,实现毫秒级的精度需要使用硬件定时器中断--无论tickGet或者是timestamp都(从实现角度上来说最终)是这样。至于这个时间精不精确,则要看你的需求了,但这个精确度与如下因素有关:
1. 时钟频率的准确性。比如x86,8254定时器使用的是一个独立的晶振。其它系统嵌入式处理器,cpu内部通常包含了定时器,定时器的晶振就是CPU的晶振。
2. VxWorks的中断响应时间:比较小,通常在5us左右(不能一概而论,这个与硬件性能相关度很大)。
不过,如果我们认为晶振频率一直稳定,精确的话,这个定时顶多受中断响应时间的影响,又或者是中断嵌套的影响(也就是若干个us,当然条件是你把需要处理的内容放在ISR中而不是需要调度的任务中,如果挂在主时钟上,就没有中断嵌套的影响了,因为它的优先级是最高的),而且不会产生累计误差。
就实现方式上来讲,挂主时钟还是辅助时钟,还是timestamp时钟。就看你的需要了:),必要的情况下,直接自己挂中断服务程序,这样最精确了。呵呵。
版主详细的解析让我收获不少,感谢先!
我查了一下,好多网友也给出了不少关于获得毫秒级时间的方法和建议,粘贴如下,
供大家参考:
帖1:
如果支持Timestamp,BSP应提供以下函数
sysTimestampConnect() - 连接时间戳中断
sysTimestampEnable() - 使能时间戳
sysTimestampDisable() - 禁止时间戳
sysTimestampFreq() - 取得时间戳的频率
sysTimestampPeriod() - 取得时间戳周期
sysTimestamp() - 取得时间戳
sysTimestampLock() - 禁止中断,取得时间戳
sysTimestampInt() - 可选的时间戳ISR
帖2:
时间戳就是一个高精度的时钟吧?由于精度高,记录时间的这个变量变化的很快,所以他很快就会溢出,重新从0开始计数,我有时候用它来计算某个函数的运行时间。
time1,time2,freq;
freq=systimestampFreq();
time1=sysTimestamp();
myFunc();
time2=sysTimestamp();
(time2-time1)/freq大概就是时间了。
但是,这有一个条件,就是myFunc()运行的很快,用tickGet()难以计算的情况。否则,可能当time2获取值的时候,时间戳都已经溢出了好几次了。
就算是这样,也不定就成功,可能当得到time1的时候,变量已经要溢出,等得到time2的时候,得到是是一个很小的值,结果,time2-time1就是负值了。
总之,利用它,在某些情况下,还是可以做一些估算的。
帖3:
vxWorks下的tick和timestamp没有什么相关性。也不是你说的秒和分的关系。
tick其实可以看成内核调度的频率,也是是时间片、watchdog超时、任务延时的单位,所以是system clock。timestamp能够得到高精度的计时,跟RTOS调度没什么关系。比如在某个时候读寄存器的计数为A,过一段时间后再读为B,主频为f。那么B和A之间的时间距离就是:T = (B-A)*f。(假设用做timestamp的timer没有rollover)对应到硬件上,tick只要timer能够产生periodic中断就行,用做timestamp的timer要求多一点点。
我目前的解决办法是用tickNum = tickGet();根据tickNum的低位值来计算当前的毫秒数,呵呵,实现简单。
[align=right][color=#000066][此贴子已经被作者于2007-7-5 14:46:41编辑过][/color][/align]
呵呵,的确如此,1ms之内,什么事都可以发生了。但设置成1ms,可以保证精确到1ms-也就是说所获取的时间差别不会大于1ms。如果把主时钟的tick再往上设,在RTOS中就有点不合适了。因为每一次主时钟中断,都要关中断,保护现场,跳转系统主时钟中断服务程序,执行操作系统调度,恢复现场,返回到调度所得新的pc地址。如果tick设置为1000。则1s之内这些步骤发生1000次,也就是1K的中断频率。如果是10000(0.1ms)则是10000次,10K的中断频率。虽说主时钟的中断处理对VxWorks来说还是很优化的(时间不长,多半用汇编实现的,自己看代码可以发现),但是,发生太频繁了,还是需要一些系统开销的,严重的话,其它硬件的中断就会受到影响,一般情况下,最好不要让CPU的中断频率操作30K(还有很多其它中断源)。多了不适合的,太多的时候协处理器就出现了。
实在要很高的定时率,比如1ms以下的。可以考虑另外用的时钟,或者0.1ms以下的,用协处理器或者硬件来实现。
呵呵,嵌入式系统的好处就在啥都可以自己调整,想做成更加软实时,还是更加硬实时,就看自己需要了。
精确刻度和准确计时不是一个概念,精确到1ms表示刻度的粒度是1ms,这是可以设ticks为1ms做到的,但这样做,是肯定做不到准确定时1ms的,存在+/-1ticks的误差,这个误差的存在是客观的,由ticks的实现机制决定,这个概念需要清楚。尤其在大家编程的时候,taskDelay(1)使用要谨慎,最好使用taskDelay(2)来做延时,相信这个经验对于很多人来说并不十分清楚。
tick为0.1ms当然在嵌入式系统中并不恰当,除了版主提到的理由之外,有些操作系统甚至不能在几个ms内完成完整中断处理的。但这里是从ticks的角度给LZ提出一个解决办法。其实我遇到这种问题的时候,使用的汇编指令死循环,从一个附加时钟做计时,可以准确计时1ms的,vxWorks 5.5中有类似的这种例子代码可以参考。
没有和版主争辩的意思,不过想把技术上的事情说清楚一些,避免模糊的概念影响大家的工作,[em07]。
wasuke大虾确实很严谨。争辩也没有关系呀,论坛本来就是通过讨论,达到相互学习,共同成长的目的嘛。
不过我对于你提出的一些观点有一些不解。下面做一些分析。
把tick设置成1ms再用tick进行延时1ms确实是不准确。但对于命题来说:是“获得毫秒级时间”,我的理解是:精确到若干个毫秒,比如说8ms,大概也就是7.x到8.x个毫秒。大致精确了。所以我对“获得毫秒级时间”的理解是:允许误差容限是1ms,这叫做ms级,如果要获得精确的1ms时间(或延时),这种做法就不行了。当然,绝对精确的1ms,用软件方法,特别是有操作系统、中断源不止1个的情况下,是很难实现的,就看对1ms到底有多少允许的误差,比如说允许100us,还是50us。所以,我认为,要获得严格的1ms时间(延时),就不应该叫做“获得毫秒级时间”了。理解错误的话,还请指点。
关于taskDelay(1)还是taskDelay(2),我确实没有这样的经验。通常,对于程序员来说,调用taskDelay(1)的真实想法类似于“让本任务(线程)停止(挂起)1个tick之后再接着往下运行”,简单地说就是“延时1个tick”--,现在分析一下从调用taskDelay(1)开始,对于VxWorks操作系统来说,究竟做了些什么:我们假设调用taskDelay(1)的任务为TaskA,TaskA任务(线程)调用taskDelay(1),系统登记当前tick数目,保护好任务(线程)的上下文环境,把任务(线程)放置到等待(delayed)任务(线程)列表,执行调度器,看有没有其它事情可做(就绪列表中还有任务),有就做,没有就执行Idle状态的语句。我们可以假设从0时刻开始,且tick数目可以用小数表示,调用taskDelay(1)的精确时刻是tick 1000.9,那么登记到的tick应该是1000。过了0.1个tick之后,主时钟中断发生了,关中断,保护现场,开中断,进主时钟中断,tick加1,变成1001,执行调度器,而TaskA的延时已经到了(延时启动时登记的tick数目与现在的tick数目差值等于延时设置的数目),那么taskA就进入就绪(ready)队列,假设此时没有更高优先级的任务就绪(ready),那么TaskA就得到执行,因为从主时钟中断发生到调度器最终决定让TaskA执行这个时间(对于1个tick来说)是非常短的(否则系统开销就太大了),假设为0.01个tick,当前时间点(以tick为单位)就是1001.01。那么相当于说TaskA从调用TaskDelay(1)开始,到延时结束,又被执行的时间是从1000.9到1001.01这段时间。显然,这个时间只有1001.01-1000.9 = 0.11tick这么长,根本就是不是我们想要的1ms,而且可以说是“差远了”。
假如当初我们调用的是taskDelay(2)而不是taskDelay(1)。继承上面的分析中的假设,时间应该是1002.01 - 1000.9 = 1.11tick。差别还要小一些。不过,这种情况不总成立,因为我们做的假设,比如从1000.9时间点调用taskDelay(1),有可能是从1000.03就开始了,那么taskDelay(1)带来的误差就比taskDelay(2)小些。另外,TaskA进入就绪队列就马上得到执行也不一定,有可能系统中存在更高优先级的任务也就绪了,且执行需要0.7个tick之如此长,那么taskDelay(1)带来的误差也比taskDelay(2)要小一些。
从上面可以看出,用taskDelay这种方式做很小时间(tick)的延时,带来的误差是有不确定性的,且与系统的负荷强相关。通常,含有调用taskDelay(x)这种语句,而且是用于延时目的的情况,都与某种事务处理的循环执行有关(也可以是其它)。从宏观角度来看,taskDelay(1)可以大致使得任务在1个tick内就绪一次(至于在1个tick内的哪个时间点执行,就不一定了),当然也不绝对正确,例如,TaskA执行需要0.7个tick才能完成,它的执行有可能被主时钟打断(中断比任务优先级要高,是“不得不处理”的事情,而且主时钟中断永远是最高优先级的),那么TaskA跨tick执行的可能性是很大的,如果调用taskDelay(1),肯定会出现就绪次数与tick数目不相等(肯定是小于)的情况。而taskDelay(2),在宏观上的效果,大致(不是绝对,原因同上,taskA执行本身需要时间,还有负荷导致的执行机会问题)是两个tick就绪一次,同样,上面的例子,虽然第一次调用taskDelay()的时间点处于1个tick的尾端(可以说不合理了),但是它下次执行,在系统轻载的情况下,就到了前端,再下一次也是前端,如此下去就成了真正的2个tick就绪一次了。
所以,总得来看,如果从设计目的上是需要taskDelay(1),那么在通常情况下(系统负荷不是很满,且task的执行在1次调用taskDelay(x)之间执行的其它代码不需要很长时间),使用taskDelay(1)比使用taskDelay(2)更加能够达到(宏观上的)真实目的。但评价依据还得根据这个task到底需要执行多么频繁,task的自身特点,以及系统的负荷,特别是比这个task优先级更高的事务(包括中断和任务)的总负荷。所以,我并没有弄清楚,你提出来的最好使用taskDelay(2)是出于什么考虑?
关于“有些操作系统甚至不能在几个ms内完成完整中断处理”的说法,按我的了解,应该来说还是非常罕见的。首先,中断处理到底要多长时间才能完成,与硬件的性能强相关,操作系统来说,有差别,但不会特别大,或者说,特别是用ms来描述的情况。
我们来对CPU的性能做一个了解,看在1ms内,CPU到底能干多少活--执行多少指令。
首先来个低速的极端:以8位机,没有缓存,接11.0592M的晶振最基本的MCS-51(没有4分频,3分频等功能的)的情况为例,12分频位1个指令周期,设指令平均周期位2,那么1s内,可以执行的指令数目大致为460,800条(0.46MIPS),1ms就可以执行460条指令。那么在这样的机器上运行复杂的操作系统(衡量复杂,最起码,中断由操作系统来管理,且有多任务调度器),那么中断处理在1ms之内就非常难于完成,因为1个中断管理程序,和1个任务调度器,用460个指令完成困难太大了。所以这种性能水平的硬件,通常并不会运行复杂的操作系统(换而运行一些简单的操作系统,中断不由操作系统管),也不会把主时钟设成那么快,多数情况下,程序员选择了裸机编程。啊,需要说明的是,现在的51系列单片机性能,通常远比上述情况要高,在指令周期分频系数,外接晶振的允许频率都大大提高了。
再看我们通常用的系统,在这里以通常大家用来运行VxWorks操作系统的CPU来看,均32位机,其中ARM、x86、68K、CodFire,PPC较为常见。ARM的性能高高低低有很多档,以MIPS来衡量性能(浮点在嵌入式系统中,无论从系统还是应用角度对性能评价来说毕竟不是最主要因素),ARM最小的也有20个MIPS以上,通常超过100个MIPS。1个60M的ARM,MIPS数目大概在70左右。1个70M的MPC860,MIPS数目大概在150左右,一个333M的PowerPC603核的CPU,MIPS数目大概在630左右,而通常的x86系列,MIPS数目均超过了500(以上关于MIPS数目的估计,不一定准确但不至于有数量级差别)。以MPC860为例,性能为70MIPS,1ms可以执行的指令数目大约为70K(7万)条,对于执行中断处理,调度器已经足够用了。当然,从微观角度来说,MIPS是一个宏观量,我们不能单独用简单的MIPS数目来判断指令执行的确切的微观时间,现代的一些性能较高的CPU大多采用了高速缓存,支持指令、数据的预取,采用指令流水线技术和分支预测技术,在取指令、执行指令的这个过程中,遇到流水线冲突、分支预测失败等不期望的事情出现了,是要消耗一定时间来处理的。这个情况出现后,处理时间最大是多少,最小需要多少,发生多么频繁就跟CPU的设计架构有关了,与代码本身也有一定的关系,特别是程序的分支复杂的情况下,分支预测失败就发生频繁了。关于哪种CPU的架构好不好,流水线深度对CPU性能的影响,网上资源很多,我就不多说了。
而关于“完成中断处理”,其时间通常由中断响应时间+中断处理时间+中断恢复时间构成。中断处理时间根据中断程序的大小决定,而响应时间和恢复时间,在相同硬件条件下,由于每个操作系统的实现机制不一样,是有很大差别的,我们常说什么“实时系统”、“硬实时”、“确定性”等话题,与这项性能有关。典型地,在相同400M的x86硬件条件下,DOS的中断响应时间最小为2.5us,不确定(有时候很长,与系统负荷有很大关系),vxWorks通常为3us左右,最大不会超过11us(可以说得上很确定了),Windows通常为50us,但最大可以达到500us(不确定),加上中断恢复时间,通常在具备运行一个功能较全的操作系统的硬件条件下,不会超过1ms的(windows的tick就是设置为1ms)。
所以,我觉得, 用操作系统来衡量能否在几个ms能否中断处理,不太合适,衡量因素应该主要是硬件,且是一个与操作系统,系统负荷相关的综合指标。
关于你的实现方法,“使用的汇编指令死循环,从一个附加时钟做计时”。虽然并不确切知道你的实现方式,比如具体用的什么样的汇编指令做死循环,又从哪个附加时钟做计时,在感觉上,觉得这种方法的基本想法就是“一有机会就判断时间是否到达预定值”,“机会”的概念就是判断时间是否到达的死循环代码被调度器命中,得到执行(对于用户来说,代码通常(启动阶段,操作系统没有起来时除外)总处于某个任务(线程)的上下文中,又或者是中断服务程序中,除非改系统本身的源代码)。这种方法之所以更加准确,是因为判断时间的“机会”变多了。但是,这种做法是有些代价和局限性的。
从实现方式上讲,使用死循环是一种轮询的方法,因为不管怎么说,判断事件的发生就只有几种方式,中断,轮询,或者二者混着用。通常,不用死循环,用tick,timestamp等方式,判断的就是某个时钟的中断(经操作系统处理后最终)给出的信号(或者是其它操作系统对象),采用的是一种被动的做法,亦即任务被外部事件被动地激活,任务本身并不能判断时间过了多长而将自己激活。而对于轮询做法,无论是使用汇编还是C或者其它任何语言,做死循环,从附加时钟读取时间,都是一种主动地做法,也就是说任务不断获知时间变更是否达到预定值,并在达到后主动将自己激活。
首先,轮询带来了硬件代价。如果死循环判断的是附加时钟芯片的寄存器,那么必然要操作IO,也就是不断地从芯片中把寄存器值读出来(无论是集成的芯片,还是外置的),如果死循环判断的是某个内存变量,那么必然要操作内存。照这样分析,系统不管有没有在真正执行有效代码(不在执行延时等待的代码),系统都一直要操作外设(发起IO或者MEM的传输),那么外设(MEM或者IO)(与不采用轮询的方法相比)会被非常频繁地选通,对于功耗敏感,又或者散热敏感的的系统来说,就不太合适了,因为,从本质上讲,延时的这段时间,如果没有其它事情可做,那么CPU应该是空闲的,空闲时间执行的指令是具有轻载特性的,也就是说没有什么消耗的,包括,CPU的一些内部引擎不工作,不访问外设等。当然,具体有多大影响很难说清楚,因为功耗敏感和散热敏感程度都是与系统相关的。
其次,用死循环做一直判断的做法,产生了软件代价。因为你的这种做法,代码应该是运行在某个任务的上下文中,假设任务的优先级是100,因为对于任务自身来说,由于没有等待操作系统对象,也没有做系统调用方式的延时,所以,这个任务一直是“有事可做”,可以认为任务始终没有主动告诉操作系统,说本任务要让出CPU给其它任务使用。而对操作系统来说,会按照严格的基于优先级调度机制的调度器来分配CPU。如果系统中存在优先级为101的任务,那么这个任务因为优先级不够,而100优先级的那个任务又始终处于就绪状态,所以,101优先级的这个任务虽然可能一直就绪,但却从未得到执行。所以,要使得多任务环境下,带有死循环的任务没有挂起环节,系统能正常工作,用死循环的这个任务最好处于最低优先级。
当然,这种方法还是有工程化意义的,如果是这种情况,带有死循环的任务在等待某个操作系统对象(挂起)之后,精确延时1ms,再做某些操作,就比较合适。不过,无论怎么精确,也不会是严格的1ms,毕竟是一个多任务的系统,其它负荷所带来的碰撞会使得这个时间不准确,但对于工程化来说,还是比单独判断tick又或者做taskDelay()一类的操作要准确很多。
反过来想,当我们设计的时候,如果明知CPU不够强,明知有多个任务要运行,还要采用CPU的周边功能来实现严格的精确的微小延时,我们可以认为,系统设计不合理。曾经有同事有出现过这样一个情况:使用Pentium的板子,运行DOS,想达到40us的周期性的准确的计时,无疑,如此快,只能用硬件中断了(当然可以用轮询,通过计算代码本身执行时间,不过他的代码包含了很多条件分支,执行时间是一个不定值),后来发现,基本上可以达到要求,但有时候很不准,中断响应时间本身就到了50us以上,再把串口和GUI加上去,就更加不准了,而且GUI和串口有时候响应还不正常,完全无法使用。后来,嫌弃CPU的能力不够,改用了pentium3,后面又改用pentium4。还是未见起色。最后,干脆把方式换了,CPU做GUI,做串口通信等,快速任务就放到一个专门的芯片里面来实现,通过DMA和中断方式完成二者间的数据交换,测试下来终于达到要求。
所以,我觉得,系统最终要实现什么样的功能,到底要多实时,定时要多精准,应该在设计之初做好合理评估,而不要到最后,用不得已和不恰当的方式实现所需功能。总的感觉就是“用合适的东西实现合适的功能”。比如说,做电气系统工程类嵌入式系统的开发人员经常会用到数据采集卡,多串口卡,或者是现场总线卡,或者电机控制卡等专用卡片。以多串口卡为例,其实主CPU通过IO方式访问串口芯片寄存器,管理串口的中断,是完全可以实现的,也非常简单。但是,应用中,由于UART一类的芯片较老的那种是接受到一个字节就中断,如果串口数量过多,通信波特率又很高的情况下,中断发生将非常频繁(比如说8250芯片(或者不带FIFO的,或者驱动没有启用FIFO的16550兼容芯片)),115200的波特率,10串口,10位方式,全部工作,同时接收,那么CPU的中断率,单就UART这一块,就达到了115200(11.5K)之如此多,现在市面上无论多强的CPU和OS看到这个数据,估计都害怕了,中断丢失的情况肯定发生,也影响了主CPU处理其它事务的实时性。所以,后来厂家就为串口芯片加了FIFO(好像最大的有128个字节之多),让它收到或者发出多个字节后再中断,再后来,板卡厂家就做了所谓的“智能串口卡”,卡片本身带有一个CPU(或者多数用FPGA),可以在这个CPU上运行基本的串口通信,并包含一些协议,把这些协议底层处理好了,再通过中断方式,一次性与主CPU进行大批数据交换,效率就高多了,一般的CPU都能够容纳这种规模的数据吞吐量。正好有个新帖“新型多总线UART芯片在嵌入式系统设计中的应用”描述了较为智能的改进型UART。又例如那些数据采集卡,模拟量的连续采样会本身需要非常精确的定时触发,而且所产生的数据量也是巨大的,卡片上为此通常会有一个IO处理器,把下层事务都做好,再通过中断和DMA传输与主CPU进行批量数据交换,且FIFO的大小通常都在1M以上。
呵呵,写太长了。
本意上没有想把技术上的事情不说清楚,概念搞模糊,而影响大家工作的想法,但水平十分有限,认识有一些误区,不当之处还请大虾提出并指正。
期待继续交流。
回复
有奖活动 | |
---|---|
【有奖活动】分享技术经验,兑换京东卡 | |
话不多说,快进群! | |
请大声喊出:我要开发板! | |
【有奖活动】EEPW网站征稿正在进行时,欢迎踊跃投稿啦 | |
奖!发布技术笔记,技术评测贴换取您心仪的礼品 | |
打赏了!打赏了!打赏了! |