减去两个uint32_t变量会得到一种溢出的结果?

Sal*_*din 2 c embedded overflow

我正在编写一个stm8s微控制器,并且正在使用STVDIDE和COSMIC编译器。

两个uint32_t变量相减的结果保存在另一个uint32_t变量中。有时,此过程会产生怪异的价值。这个怪异的值始终是最高位设置为的期望值1s

这是我的代码片段:

static uint32_t lastReceivedLed = 0;
uint32_t timeSinceLast = 0;

timeSinceLast = IL_TimTimeNow() - lastReceivedLed;

if(timeSinceLast > 2500U)
{
      Inhibitor = ACTIVE;  // HERE IS MY BREAKPOINT
}
Run Code Online (Sandbox Code Playgroud)

这里是如何IL_TimTimeNow()被定义的:

volatile uint32_t IL_TimNow = 0;

uint32_t IL_TimTimeNow(void)
{
    return IL_TimNow; // Incremented in timer ISR
}
Run Code Online (Sandbox Code Playgroud)

以下是调试会话中的一些实际值:

在此处输入图片说明

timeSinceLast 应该 865280 - 865055 = 225 = 0xE1

但是,编译器计算的结果是 4294967265 = 0xFFFFFFE1

注意,最低有效字节是正确的,而其余字节1s在编译器的结果中设置为!!

另请注意,这种情况仅偶尔发生一次。否则,它会按预期工作。

这是溢出吗?什么会导致这种情况?

Ian*_*ott 7

调试器中显示的值为:

  • IL_TimNow = 865280
  • lastReceivedLed = 865055
  • timeSinceLast = 4294967265

请注意,将-31转换为时,也会得到4294967265 uint32_t。这表明,值IL_TimNow由返回IL_TimTimeNow()之前的减法,实际上lastReceivedLed - 31,这是865055 - 31,这是865024。

IL_TimNow调试器(865280)中显示的值IL_TimNow与减法(865024)之前的值之差为256。此外,两个值的最低有效8位全为零。这表明正在读取该值,就像最低有效字节回绕到0并增加下一个字节一样。中的评论IL_TimTimeNow()// Incremented in timer ISR。由于8位微控制器一次只能读取一个字节,因此似乎IL_TimNow在函数读取四个字节的同时发生了定时器ISR 。

有两种解决方法。第一种方法是在读取IL_TimTimeNow()值时禁用定时器中断IL_TimNow。因此,该IL_TimTimeNow()函数可以更改为以下形式:

uint32_t IL_TimTimeNow(void)
{
    uint32_t curTime;

    disable_timer_interrupt();
    curTime = IL_TimNow;
    enable_timer_interrupt();
    return curTime;
}
Run Code Online (Sandbox Code Playgroud)

但是,您需要检查一下暂时禁用定时器中断只会导致中断被延迟,而不会完全跳过(否则您将失去定时器滴答)。

另一种方法来解决问题是保持阅读IL_TimNowIL_TimTimeNow(),直到你得到两个相同的值。因此,该IL_TimTimeNow()函数可以更改为以下形式:

uint32_t IL_TimTimeNow(void)
{
    uint32_t prevTime, curTime;

    curTime = IL_TimNow;
    do
    {
         prevTime = curTime;
         curTime = IL_TimNow;
    } while (curTime != prevTime);
    return curTime;
}
Run Code Online (Sandbox Code Playgroud)

通常,do ... while循环将进行一次迭代,读取IL_TimNow两次。有时,循环会有两次迭代,读取IL_TimNow三次。实际上,我希望循环不会重复两次以上,但是该函数也可以处理该循环。

较不安全,但可能稍快一些的上述版本将仅IL_TimNow在最低有效字节为0 时才读取两次:

uint32_t IL_TimTimeNow(void)
{
    uint32_t curTime;

    curTime = IL_TimNow;
    if ((curTime & 0xFF) == 0)
    {
        // Least significant byte possibly just wrapped to 0
        // so remaining bytes may be stale. Read it again to be sure.
        curTime = IL_TimNow;
    }
    return curTime;
}
Run Code Online (Sandbox Code Playgroud)

如果性能不是问题,请使用较安全的版本之一。