Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
定时器
时间溢出
RTThread解密--定时器模块解决时间溢出的问题
5.00
发布于 2020-11-26 16:28:32 浏览:3852
订阅该版
[tocm] # 引子 笔者在使用RTThread时,时常会探究一下相关模块的实现机制。往往是带着明确的目的去探索,不过有时会有意外的收获。比如这次说的规避时间溢出的机制,就是在探索RTThread的定时器模块(rt_timer_t)时发现的。 先看一段代码: ```c static void led_loop(void) { static uint32_t next_tick = 0; if(HAL_GetTick() >= next_tick) { next_tick = HAL_GetTick() + 100; HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); } } int main(void) { // 此处省略一万字 while (1) { // other loop led_loop(); } } ``` 这是stm32裸机平台的LED闪烁代码,每100毫秒翻转一次LED电平。笔者用它来演示定时任务的实现,即每隔一段时间执行一个任务。 # 问题 **这段代码有一个小问题,不知各位同学有没有发现。**如果没有,那你看这篇文章就看对了,哈哈。 HAL_GetTick返回的是从系统启动到现在所经过的时间uwTick,uwTick在SysTick_Handler累加。即uwTick的单位取决于SysTick_Handler执行频率,通常是1000Hz,所以uwTick的单位是1毫秒。 uwTick是32位无符号整型变量,它最大能记录多少天的时间呢? ``` 2^32 / 1000 / 3600 / 24 = 49.71..... ``` uwTick在系统一直运行49天后将溢出,溢出后重新从0开始累加。在即将溢出时,上面这个定时程序将出现问题。为了方便描述,要使用到一个宏UINT32_MAX。UINT32_MAX表示32位无符号整型变量的最大值,即0xffffffff。 时间溢出会引发两种问题: 1. 如果HAL_GetTick()溢出而next_tick尚未溢出,则长时间内HAL_GetTick()
next_tick,定时任务将不停执行。 问题1不是必现的,如果led_loop每毫秒都执行一次,就没问题。问题2是必现的,next_tick不断累加,49天后必然溢出。 # 解决 49天是一个并不算短的时间,而RTThread的rt_tick_t的默认单位是10ms,其溢出所需要的时间是497天。不过无论是49天还是497天,对于一个一直运转的设备来说,溢出都是必然的。所以我们得想办法解决这个问题。答案就在RTThread的定时器模块之中,因为RTThread本身是不惧时间溢出的。 rt_timer的实现机制还是挺复杂的,这里直接给出关键判断代码,在rt_soft_timer_check中。 ``` /* * It supposes that the new tick shall less than the half duration of * tick max. */ if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2) ``` 这就是RTThread判断定时任务时否到期的方法。t->timeout_tick是目标定时器的超时时间,即下一次执行的时刻。current_tick是当前时间。两个变量的类型都是rt_tick_t,也就是uint32类型,而RT_TICK_MAX就是UINT32_MAX。为了方便叙述,我们将t->timeout_tick简化为timeout_tick,从而判断式变为: ``` if ((current_tick - timeout_tick) < RT_TICK_MAX / 2) ``` current_tick与timeout_tick的差值与RT_TICK_MAX / 2有什么关系呢?我们知道,它的功能就是```if (current_tick >= timeout_tick)```,只不过它不怕时间溢出,无论是current_tick还是timeout_tick。我们先用几个具体的数字,来验证一下这个功能。 在举例前,需要明确一点。current_tick和timeout_tick都是无符号的,无符号减无符号的结果还是无符号,uint32减uint32还是uint32。如果(int32_t)current_tick - (int32_t)timeout_tick = -1,则current_tick - timeout_tick = UINT32_MAX。int32类型的负数对应的uint32的正数有如下的关系: > int32_t类型的变量x为负数,(uint32_t)x = UINT32_MAX + 1 - |x|。 1. current_tick = 1, timeout_tick = 2, current_tick - timeout_tick = 1 - 2 = -1 = UINT32_MAX > UINT32_MAX / 2,if判断不成立。 2. current_tick = 2, timeout_tick = 1, current_tick - timeout_tick = 2 - 1 = 1 < RT_TICK_MAX / 2,if判断成立。 3. current_tick = UINT32_MAX, timeout_tick = 1(timeout_tick溢出,所以实际上当时时间current_tick还没达到超时时间timeout_tick ), current_tick - timeout_tick = UINT32_MAX - 1 > UINT32_MAX / 2, if判断不成立。 4. current_tick = 1, timeout_tick = UINT32_MAX(这描述的场景是,current_tick原先是接近UINT32_MAX的,只不过由于检查的间隔过长,再次检查时current_tick溢出了,),current_tick - timeout_tick = 1 - UINT32_MAX = UINT32_MAX + 1 - |1 - UINT32_MAX| = 2 < UINT32_MAX / 2, if判断成立。 例1和例2是普通场景,其结果与```if (current_tick >= timeout_tick)```一致。例3和例4是我们之前所讨论的两种时间溢出场景,例3是timeout_tick溢出,例4是current_tick溢出。例3和例4的判断结果也满足实际的情况,比如例3中,timeout_tick溢出,current_tick尚未溢出,所以尚未满足超时条件,例3的结果也是if判断不成立。 # 原理 ```if ((current_tick - timeout_tick) < RT_TICK_MAX / 2)```为什么能实现超时判断而又不怕溢出呢? 原理还是在无符号整型的减法运算上面。之前提到无符号减无符号的结果还是无符号,即还是正数(或0),并且提了正负数转换的规律。有的同学可能看的云里雾里,那么现在,笔者将告诉你两个无符号数相减的终极含义:两个数的距离。准确说,**是被减数在坐标轴上向左走到减数所经过的距离**。下面画图说明B-A的两种情况: ![image.png](/uploads/20201126/ba9e465fe764d4be26a9e8dd4ba05254.png) 1. B1 > A1,B1向左直接能走到A1。 2. B2 < A2,B2向左走到A2得分为三个步骤:B2向左走到0,从0跳到UINT32_MAX(算走1步),从UINT32_MAX走到A2。 现在再想想“解决”那节里面的例4,current_tick = 1, timeout_tick = UINT32_MAX,从current_tick走到timeout_tick的距离就是```1 + 1 + 0 = 2```。 将A和B替换为代表时间的timeout_tick和current_tick,则向右代表未来,而向左代表过去。```current_tick - timeout_tick```就是current_tick向过去走到timeout_tick的距离。由前面B-A的叙述可知,无论current_tick是不是在timeout_tick前面(前面表未来),current_tick向左总能走到timeout_tick,因为这是一个环路,从0向左会走到UINT32_MAX。 我们可以通过比较```current_tick - timeout_tick```这个距离的大小,从而判断current_tick是否能从过去走到timeout_tick。 我们细想下实际的使用场景的特点: - 规划的肯定是未来的任务,而不是过去的任务。 - 会定期去检查有没有超时,检测的间隔不会很长,比如每次时间更新时都检查。 基于如上特点,我们可以得出结论:如果检查超时的频率比较高,当检查到current_tick超过timeout_tick时,current_tick不会超太多。可能超了1,10,100,反正是一个较小的值。进一步推论得出:若current_tick确实在timeout_tick前面的话,那么current_tick往回走到timeout_tick的距离不会太长。如果current_tick要走很长的距离才能到达timeout_tick,那它到达的应该是未来的timeout_tick,current_tick自己穿越时空了。什么样的距离算很长的距离呢,uint32的最大值为UINT32_MAX,那就以一半为界,因此就有了之前看到的判断式: ``` if ((current_tick - timeout_tick) < RT_TICK_MAX / 2) ``` 再重复叙述下: 1. 如果current_tick向左走较短的距离(小于RT_TICK_MAX / 2)就到了timeout_tick,说明current_tick往过去走能达到timeout_tick,从而判断current_tick比timeout_tick更前,即达到了超时条件。 2. 如果current_tick得走很长距离(大于等于RT_TICK_MAX / 2)才能走到timeout_tick,说明current_tick往过去走不到timeout_tick,它走到了未来,从而判断timeout_tick比current_tick更前,即尚未超时。 最后,让我们再用图来演示下4种场景: > 这里用<<表示远小于,比如current_tick << timeout_tick表示:current_tick < timeout_tick - UINT32_MAX / 2。同理,用>>表示远大于。 1. current_tick < timeout_tick,两者均未溢出,即未超时。 ![image.png](/uploads/20201126/de68ce48ac3b4cc76a1c7bc5a1d0b2e5.png) 2. current_tick > timeout_tick,两者均未溢出,即已超时。 ![image.png](/uploads/20201126/6d073f033c02ce1d9b9581674ce1e410.png) 3. current_tick << timeout_tick,current_tick溢出,即已超时。 ![image.png](/uploads/20201126/96bb96db9bc5dc9b38aa9df3546e7143.png) 4. current_tick >> timeout_tick, timeout_tick溢出,即未超时。 ![image.png](/uploads/20201126/1376b9823b197bc0b53466a07d6ff036.png) # 质疑 最后再让我们看一眼这个判断式,它是万能的吗,有没有限制条件呢? ``` if ((current_tick - timeout_tick) < RT_TICK_MAX / 2) ``` 其实是有的,那就是定时任务的周期必须小于RT_TICK_MAX / 2,否则就会得出相反的判断结果: - 实际未超时时认为超时。 - 实际超时时认为尚未超时。 RTThread中就对定时周期做了限制,在rt_timer_start函数中: ``` /* * get timeout tick, * the max timeout tick shall not great than RT_TICK_MAX/2 */ RT_ASSERT(timer->init_tick < RT_TICK_MAX / 2); ```
9
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
wenbodong
这家伙很懒,什么也没写!
文章
4
回答
44
被采纳
3
关注TA
发私信
相关文章
1
定时器中要延时,用什么办法?
2
cubemx配置定时器PWM可以输出移到RT_stdio不能输出?
3
对源码中优先级处理和定时器的两个疑问?
4
关于定时器时钟,怎么定时一个1MHz时钟
5
rt_spi_send、rt_spi_recv不能放在定时器里进行使用
6
关闭定时器中断,再开启中断
7
关于在L4潘多拉上定时器TIM3跑hwtimer_sample例程出错的问题
8
rtthread studio里定时器倍频问题
9
rt-thread-studio开发潘多拉的定时器出现问题?
10
多个软件定时器之间会不会冲突
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
I2C_IIC
ESP8266
UART
WIZnet_W5500
ota在线升级
cubemx
PWM
flash
freemodbus
BSP
packages_软件包
潘多拉开发板_Pandora
定时器
ADC
flashDB
GD32
socket
编译报错
中断
Debug
rt_mq_消息队列_msg_queue
SFUD
msh
keil_MDK
ulog
C++_cpp
MicroPython
本月问答贡献
xusiwei1236
8
个答案
2
次被采纳
踩姑娘的小蘑菇
1
个答案
2
次被采纳
用户名由3_15位
9
个答案
1
次被采纳
bernard
4
个答案
1
次被采纳
RTT_逍遥
3
个答案
1
次被采纳
本月文章贡献
聚散无由
2
篇文章
15
次点赞
catcatbing
2
篇文章
5
次点赞
Wade
2
篇文章
4
次点赞
Ghost_Girls
1
篇文章
7
次点赞
YZRD
1
篇文章
2
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部