张三和李四谁跑的快

本文讲解毫秒级延时防溢出的原理,没看过前一篇的同学可先阅读嵌入式基础–毫秒级定时模块

前情回顾

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

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\r\n");
        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)在跑道上跑步,沿逆时针方向跑。蓝色是起跑线,不过他们并不只跑一圈,假设跑三圈。并且我们知道,张三和李四的水平相差不大,短短的三圈不足以让他们拉快过长的距离,更不可能出现套圈。
在这里插入图片描述
上图谁跑的快呢?当然是张三(A)。

我们再来看几张图。
在这里插入图片描述
上述四张图,谁跑的快呢?还是张三(A)。

等等,第四张图是不是和时间溢出的情况有点像?
在这里插入图片描述
假设这个跑道长256米,从起点开始沿逆时针方向(即跑步的方向)标注坐标。那么A和B在坐标轴的位置大致如下:
在这里插入图片描述
假设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在起点的同侧。
在这里插入图片描述
对应到坐标系上为:
在这里插入图片描述
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)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值