一、延时需求举例:
我们在开发软件过程中,偶尔需要在两个处理之间增加一段延时,譬如SPI, CAN, LIN通信时给外部芯片发送了第一条指令后,必须要延迟50us等收到它的响应命令后再发送第二条指令(如图1-a),或者不等收到它的响应,只要50us延时到了就继续发送第二条指令(如图1-b)。无论如何,这50us是必须的,那么应该怎么做这个延时函数呢?
是不是简单写一个空循环for(i=0;i
二、三种延时方法:
1.纯软件延时:
如图2-a,这是最简单的延时方法,编程者可以根据Delay()函数生成的汇编指令统计出总的机器周期,再根据主频计算出一个for循环耗用时间,从而确定参数time。或者先设一个参数譬如10000,在Delay(10000);前后加测试Port电平反转,用示波器测量真实时间T,从而确定出一个for循环的时间=T/10000;
2.定时器延时
如图2-b,选择CPU里面一个不用的定时器资源,设为UpCounter模式,频率=1MHz,则定时器每加一次就等于1us,延时开始时定时器清零(也可以不清零,这就要考虑定时器溢出从0xFFFFFFFF变为之后的延时补偿时间,比较麻烦且有风险,后面有分析),则参数是多少就延时多少。
3.操作系统任务延时
如图2-c,在高优先级任务Task_1ms里对Delay_1ms_Tick计数,在低优先级任务Task_10ms里设定初值并观测它的变化。因为低优先级任务Task_10ms的周期长,会造成延时反应较慢,最大会有10ms延时误差。但是相比较方法1,2它最大的好处是延时不需要独占CPU,也就几乎不消耗CPU负载率。有的OS里有专门的延时接口【例如:ucosii里面OSTimeDly()函数】,本质上是一样的。这种方法要思考任务切换的影响,也只能在OS启动后用。
三、延时场景分析:
1.在控制器上电后,软件OS和中断还没有启动前(对于没有OS的平台就是自定义的周期处理函数执行前),也就是初始化阶段,所有的操作都是按编程的自然顺序执行,不存在CPU抢占。可以使用方法1纯软件延时,在CPU定时器资源充裕的时候用方法2定时器延时会方便一些。但是不能用方法3延时。
2.在OS和系统中断启动后,尽量用方法3延时,但延时精度(LSB)取决于周期最短的任务或中断,注意选择优先级最高周期最短的任务作为延时Tick,从而尽量减小任务切换时周期波动造成的延时积累时间影响。
3.在OS和系统中断启动后,如果需要短延时(譬如最小中断周期1ms,但是只需要延迟100us),那么就不能用方法3,但是用方法1,2延时函数独占CPU,会增大总的CPU负载率。如果CPU负载率可以承受,那么可以用方法1,2。否则就要变软件或硬件设计。譬如,分析延时1ms系统的响应速度是否能承受从而尽量用方法3。
四,延时失败分析
延时方法选择不正确或延时计时器设计不当会增大延时误差,严重会直接使OS崩溃。且OS崩溃往往是偶发性的,对于不清楚软件设计的人员来说有时花费很长时间很难找出根本原因,从而影响项目正常进度。下面就几种延时失败情况做分析
1.方法1延时被拉长:
如图4所示,需要在Task2里A处启动一段短延时(例如10us),当延时开始后在B时刻(假设5us)OS被高优先级任务Task1抢占,则局部变量延时计数器i进入栈内保存,直到从Task1退出从C处开始继续延时到D处结束,则总延时时间=10us+Task1的运行时间,显然大于实际运行时间。
解决方案:在Delay()前先关中断或禁止OS调度,延时结束后再恢复。但是这样会影响OS调度的实时性。
2.方法2定时器不清零导致OS崩溃:
如图5示,需要在Task2里A时刻启动一段延时10us,延时起始时刻定时器值=A,预期当定时器=B时是延时结束。但是启动延时后高优先级任务Task1抢占了CPU资源且一直占有,直到定时器溢出后到C时刻才释放CPU,当回到Task2后发现延时条件未结束,要等到D时刻,而C-D的时间跨度很长,理论最坏65535us≈65ms(16位定时器变量条件下),如果Task2的周期是10ms,则在这么长时间内不可能结束任务,OS调度会使该任务重入,从而OS出错。
解决方案:每次延时开始都给定时器清零,且限制最大延时长度不能让定时器溢出,但这样的话每次只能允许一个任务独占定时器延时。
小结:
1.在OS启动前的初始化用方法1,2延时均不受影响。
2.在OS启动后尽量用方法3,避免用方法1,2。
3.在OS启动后使用方法1,2会增加CPU负荷。
4.在OS启动后使用方法1在低优先级任务会使实际延时时间变长。
5.在OS启动后使用方法2在延时开始时定时器要清零,否则会引起OS崩溃。
领取专属 10元无门槛券
私享最新 技术干货