0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

剖析毫秒级延时防溢出的原理

Linux阅码场 来源:RTThread物联网操作系统 作者:RTThread物联网操作 2021-08-02 17:38 次阅读

前文指出了基于系统滴答计数实现的毫秒级延时的问题。

uint32_t comm_get_ms(void)

{

return sys_tick_get();

}

void comm_delay(uint32_t ms)

{

uint32_t timeout = comm_get_ms() + ms;

while(comm_get_ms() 《 timeout);

}

comm_get_ms返回当前系统时间(系统滴答计数),即系统从启动到现在经过了多少毫秒。comm_delay先获取当前时间,加上延时时间以计算出到期时间timeout,之后循环等待当前时间超过timeout以完成延时。

系统时间使用uint32_t变量来记录,经过49.71天后将达到最大值UINT32_MAX(0xffffffff),溢出后回到0重新累加。不仅是当前时间会溢出,在接近49.71天时,计算的timeout将会更先一步溢出,从而使延时判断失效。

前文在结尾给出了解决方案:

void comm_delay(uint32_t ms)

{

uint32_t timeout = comm_get_ms() + ms;

while(comm_get_ms() - timeout 》 UINT32_MAX / 2);

}

其实改动很小,仅仅修改了判断超时的条件。为什么要用两个时间差去与UINT32_MAX / 2比较?判断条件为什么是大于?

了解其中的原理是有必要的。因为延时的条件如上,而如果想实现定时的话,条件就会倒过来。知其所以然,方能灵活运用。

定时任务:

uint32_t timeout = 0;

while (1)

{

if (comm_get_ms() - timeout 《 UINT32_MAX / 2)

{

printf(“hello

”);

timeout = comm_get_ms() + 1000;

}

}

主要矛盾

无论是延时还是定时,我们都是在进行时间的比较。先根据延时或定时时长计算出到期时间timeout,之后不停的判断当时时间有没有超过这个timeout。

所有的时间变量都是uint32_t,由于它的最大值非常大,为了方便讲解,我们假设所有的变量都是uint8_t,即8位无符号整型,取值范围为0-255。同样为方便叙述,以cur_time表示当前时间,以timeout表示目标到期时间。

现在的任务也非常清楚了,在各种场景下比较cur_time是否超过了timeout。比如:

起始cur_time为10,延时目标为5,则timeout为10 + 5 = 15。判断依据非常简单,cur_time 《 15时视为未超过timeout,或者说cur_time 《 timeout视为未超过timeout。

起始cur_time为250,延时目标为10,则timeout为250 + 10 = 260 = 4。此时cur_time 《 timeout不再适用。

张三和李四谁跑的快

既然时间溢出问题让我们头疼,那我们先来看一个简单的问题,一个任何人都可以不假思索得出答案的问题:判断跑道上的张三和李四谁跑的快,或者说谁跑在前面。

如下图,张三(A)和李四(B)在跑道上跑步,沿逆时针方向跑。蓝色是起跑线,不过他们并不只跑一圈,假设跑三圈。并且我们知道,张三和李四的水平相差不大,短短的三圈不足以让他们拉快过长的距离,更不可能出现套圈。

假设这个跑道长256米,从起点开始沿逆时针方向(即跑步的方向)标注坐标。那么A和B在坐标轴的位置大致如下:

77309ec8-e032-11eb-9e57-12bb97331649.png

假设A为10,B为240,A 《 B,但是从跑道的图中大家不假思索就得出A跑在前面。这是为什么呢?

大家在判断谁在前面时,其实根本没去管那根蓝色的线(起点或终点)。因为跑道首尾相连,而且张三和李四要跑好几圈,必将多次经过起终点,所以起终点没有任何判断价值。

人脑是怎么判断的

笔者反复自我剖析,觉得可能是这样判断的:

人脑会做两种假设,张三(A)快,或者李四(B)快。最终选择一个最合理的假设。

假设张三(A)快,那么A沿顺时针跑回B(逆时针是前进方向,往回跑就是顺时针)的距离即为A超前B的距离,如下图的红色箭头,相对于一圈的长度而言是一个较小的距离。假设李四(B)快,则B沿顺时针方向需要跑大半圈才能遇到张三(A)。如果李四确实比张三快的话,那么快了不只一点点,而是超前大半圈。先前说过,张三和李四的水平相差不大,短短的三圈不足以让他们拉快过长的距离。所以我们更愿意相信第一种假设成立,即张三(A)比李四(B)跑的快。

人脑做上述判断的时候,并没有给跑道建立坐标系,也不是判断张三和李四的坐标值哪个大,而是判断张三和李四的距离。这个距离是有方向性的。

假设张三(A)快,则目测A跑回B的距离L(A-B)。这个距离比较小,所以判断成立,A确实在B前面。

假设李四(B)快,则目测B跑回A的距离L(B-A)。这个距离比较大,所以判断不成立,B其实在A的后面。

其实根本不需要验证两种假设,只需要验证一个就行了,因为它们是对立的。

回归代码

人脑通过视觉来估测张三与李四的距离,但是计算机不行,它需要一个明确的方法,还是需要坐标系的。

还是假设这个跑道长256米,从起点开始沿逆时针方向(即跑步的方向)标注坐标。

简单情况

先看简单的情况,即A和B在起点的同侧。对应到坐标系上为:

77be2e96-e032-11eb-9e57-12bb97331649.png

A在40米处(记为Xa),B在20米处(记为Xb)。A返回到B的距离为

L = Xa - Xb = 40 - 20 = 20

这个距离远小于256,所以A在B的前面。

溢出情况

再来看看复杂的溢出情况,即A和B在起点两侧。

对应在坐标系上时,为方便绘制,将A、B与起终点的距离拉远一点。Xa=30,Xb=220。A返回到B的距离为:

L = L1 + L2 = (Xa - 0) + (256 - Xb) = 30 + (256 - 220) = 66

66也是远小于256的,所以A还是在B的前面。

归一

有没有发现什么不对?刚才讨论区分简单情况和溢出情况,在计算L时的公式是不同的,这可有点小麻烦。如果有统一的公式就好了。

让我们再看一眼溢出情况的公式:

L = L1 + L2

= (Xa - 0) + (256 - Xb)

= Xa - Xb + 256 = Xa - Xb

这么一调整就和简单情况一样了吧。为什么把加256给去掉了?因为我们讨论Xa和Xb是uint8_t,加256和没加是一样的。

验证

还是上一个例子的场景,我们来假设B在A前面。B返回到A的距离为:

L = Xb - Xa = 220 - 30 = 190

190比较接近256,所以假设不成立,B并不在A前面,而是A在B前面。

我们在判断距离时,用了两种标准:

L远小于256

L比较接近256

对于计算机而言,这是无法实现的,它需要一个明确的标准。那是什么呢?就一刀切吧,以256 / 2为阈值。

L 《 256 / 2:假设成立

L 》 256 / 2:假设失败至于L == 256 / 2的情况,随便归入哪个都行。

再看时间判断

void comm_delay(uint32_t ms)

{

uint32_t timeout = comm_get_ms() + ms;

while(comm_get_ms() - timeout 》 UINT32_MAX / 2);

}

再看这时间判断,有没有豁然开朗呢?comm_get_ms()是张三,timeout是李四,变量范围由uint8_t变成了uint32_t,仅此而已。

后记

这种超时判断方法并非由笔者想出,是笔者在阅读RT-Thead操作系统的timer源码时发现的。rt_timer是RT-Thread定时器模块,提供基于系统滴答计数的定时功能,其计数值就是32位无符号整型uint32_t,时间久了必然溢出。

笔者之前也为溢出问题感到头疼,而RT-Thread号称不惧溢出,于是笔者怀着好奇的心态挖掘了其解决方法。在rt_timer中,有多处这样的判断,现在看起来是不是感觉很亲切呢?

/*

* 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)

编辑:jq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 延时
    +关注

    关注

    0

    文章

    106

    浏览量

    25284

原文标题:从rtthread timer模块中找到裸机定时问题的解决方案

文章出处:【微信号:LinuxDev,微信公众号:Linux阅码场】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    实现一个ns延时函数,延时时间不可控的原因?

    现在要实现一个ns延时函数,用nop指令已经调试完成,然而问题在于这个延时函数经常被中断,导致延时时间不可控,我在延时函数前后加了 p
    发表于 06-26 06:50

    如何在ESP32上获得一个微妙延时

    求助!如何在ESP32上获得一个微妙延时
    发表于 06-19 07:47

    平板是指哪三,浅谈三工业平板电脑

    工业平板电脑成为许多行业中的重要工具。本文将介绍三工业平板电脑的特点以及其在各个领域中的广泛应用。 三工业
    的头像 发表于 02-21 11:11 559次阅读

    jvm内存溢出该如何定位解决

    在Java应用程序中,JVM(Java虚拟机)内存溢出是指Java应用程序试图分配的内存超过了JVM所允许的最大内存大小,导致程序无法正常执行。内存溢出通常是由以下几个原因引起的:内存泄漏、对象大小
    的头像 发表于 12-05 11:05 834次阅读

    jvm内存溢出故障排查

    JVM内存溢出是常见且令人头疼的问题,特别是在运行大型Java应用程序或长时间运行的应用程序时。当JVM分配给应用程序的内存不足以处理应用程序所需的数据时,就会发生内存溢出。本文将详细讨论JVM内存
    的头像 发表于 12-05 11:04 478次阅读

    请问sigmastudio上的delay延时的单位是多少?

    如题,sigmastudio上的delay延时的单位是多少(秒,毫秒,微秒)??我只想进行普通延时,使用哪个delay? 谢谢!
    发表于 11-29 06:22

    java内存溢出排查方法

    Java内存溢出(Memory overflow)是指Java虚拟机(JVM)中的堆内存无法满足对象分配的需求,导致程序抛出OutOfMemoryError异常。内存溢出是Java开发
    的头像 发表于 11-23 14:46 1129次阅读

    STM32如何使用定时器实现微秒(us)延时

    STM32如何使用定时器实现微秒(us)延时? 在STM32微控制器中,可以使用定时器实现微秒延时。具体来说,可以使用定时器的计数器和自动重装载寄存器来生成精确的
    的头像 发表于 11-06 11:05 4238次阅读

    详解C语言中整形溢出问题

    整型溢出有点老生常谈了,bla, bla, bla… 但似乎没有引起多少人的重视。整型溢出会有可能导致缓冲区溢出,缓冲区溢出会导致各种黑客攻击。
    的头像 发表于 11-06 10:58 937次阅读
    详解C语言中整形<b class='flag-5'>溢出</b>问题

    51单片机如何实现毫秒精确延时

    51单片机如何实现毫秒精确延时
    发表于 10-27 06:25

    51单片机怎么进行ns延时

    51单片机怎么进行ns延时
    发表于 10-17 07:36

    Linux内核延时函数接口

    ( unsigned int msecs) ; //毫秒延时 long msleep_interruptible ( unsigned int msecs) ; //毫秒
    的头像 发表于 10-04 15:40 540次阅读

    延时可低至千分之一毫秒,犀灵视觉传感器技术再突破

    减少不同组件之间数据传输的需要,传感加运算最快做到10万帧/秒,延时可低至千分之一毫秒......近日,北京亦庄(北京经济技术开发区)企业北京犀灵视觉科技有限公司(以下简称“犀灵视觉”)推出全新
    的头像 发表于 07-28 16:34 448次阅读

    Embedded Studio堆栈溢出预防简析

    为了识别运行的嵌入式系统中的堆栈溢出问题,SEGGER编译器通过为每个函数生成检测代码的方式来检查堆栈溢出
    的头像 发表于 07-14 11:07 639次阅读

    基于STM32F207介绍4种不同方式实现的延时函数

    单片机编程过程中经常用到延时函数,最常用的莫过于微秒延时delay_us()和毫秒delay_ms()
    的头像 发表于 07-11 15:37 1025次阅读
    基于STM32F207介绍4种不同方式实现的<b class='flag-5'>延时</b>函数