【问题标题】:Using ARM Cortex SysTick for calculating elapsed time; what happens when SysTick counter rolls over?使用 ARM Cortex SysTick 计算经过时间;当 SysTick 计数器翻转时会发生什么?
【发布时间】:2017-01-21 04:18:00
【问题描述】:

我目前正在审核有关嵌入式系统的在线 edX 课程,并学习如何使用 SysTick 计时器来计算经过的时间。

Picture of logic I'm referring to

Code of logic I'm referring to

但是,有一点让我感到困惑。我理解从“最后”中减去“现在”以获得经过时间的想法。但是,当 SysTick 计时器达到 0 并重新加载时“现在”翻转时,您会怎么做,但“最后”是 SysTick 计时器翻转之前的值(所以“现在”大于“最后”,通常什么时候应该更小)?该值存储在一个无符号长整数中,所以它会破坏程序吗?或者这永远不会发生,如果是这样,为什么?如有任何帮助,我将不胜感激!

我查看了与我的问题类似的唯一其他链接:How to deal with a wrapping counter in embedded C,但我没有从中找到明确的答案。

【问题讨论】:

  • 您为什么不简单地尝试一些值呢? unsigned 整数环绕定义非常明确,即使计数器环绕,与前一个值的 有符号整数差 仍然是正确的(只要它足够小,即你的一半 int范围)
  • @tofro 我会试一试,谢谢!我只是想事先弄清楚逻辑上发生了什么,因为我认为在不完全理解逻辑的情况下实现代码是没有意义的。

标签: c arm embedded integer-overflow


【解决方案1】:

拿一个 3 位计数器,它都放大到 24 或 32(我认为 systick 计时器是 24。

所以向上或向下计数无关紧要,您只需要正确调整操作数即可。所以说倒计时 7,6,5,4 7 - 4 是 3 个计数。但是二进制 1 - 6 = 001 - 110 中的 1,0,7,6 呢

     1
   001
+  001
=======

解决它

   011
   001
+  001
=======
   011     

剪裁到 3 位是正确的答案。

为 4 位或 5 位计数器编写程序应该很简单,尝试随机大小的计数。或者使用固定大小的计数,允许使用素数进行翻转

#include <stdio.h>
#define BITMASK 0xF
int main ( void )
{
    unsigned int now;
    unsigned int beg;
    unsigned int end;
    unsigned int ra;
    unsigned int rb;
    now=0;
    for(ra=0;ra<100000;ra++)
    {
        beg=now;
        for(rb=0;rb<13;rb++) now--;
        end=now;
        if(((beg-now)&BITMASK)!=13)
        {
            printf("Error 0x%X 0x%X\n",beg,end);
        }
    }
    printf("Done.\n");
    return(1);
}

仅当它是根据方向从全零翻转到全一或从全一翻转到全零的计数器时才有效。我希望很明显,如果您将假设的 3 位计时器设置为从 5 开始,并且它从零滚动回该 5 设定点,那么三个步骤可能是 1、0、5、4,而 1-4 不是 3

    111
    001
+   011
=========
    101

另一种思考方式是二进制补码的另一个特征,在环绕点附近

101  -3
110  -2
111  -1
000  +0
001  +1
010  +2

这就像我们在小学时使用的数字轴。从位模式 110 到 010 在数轴上是 4 步 +2 - -2 = 4 或 4 个单位。 基本上使用二进制补码的美丽,我们知道二进制中的加法和减法由于二进制补码不是无符号或有符号的,相同的位模式导致相同的位模式。这就是我们解释它们的方式。所以我们有点作弊,采用有符号数学并将结果解释为无符号。

【讨论】:

  • 你必须知道你的计数器的大小,所以如果你有一个 24 位计数器的 systick,你不能只使用 32 位数学,结果将是错误的,你必须用一个 24 位掩码。您可以使用较小的掩码 20 位或 16 位或任何其他位,只要在这些位数内计数器从全零翻转到全一或全一翻转到全零。对于像这样的计数器/计时器,这通常意味着一个向下的计数器,你重新加载到所有的一个向上的计数器到全零(在翻转时)
  • 显然,如果计数器滚动两次,则它不起作用,因此您要么必须观察它的滚动,然后将这些滚动添加到结果中,要么确保您的采样速度足够快,永远不会让它通过您的开始计数,甚至离得太近。以上面的示例程序为例,而不是循环运行到 13,而是让它运行到 17 或 19,然后比较与 17 或 19 的差异。如果使用 15 的掩码,您将永远无法到达那里。
  • 这很有帮助,非常感谢您提供示例!关于位掩码的问题:如果你正在做减法运算,结果不会一直都是 24 位,所以超过 24 的额外位无论如何都会保持 0? 32 位数学对您只使用 24 位这一事实有何影响?
  • 在逻辑减法中使用加法器,我们从编程课中知道,要“取二进制补码”,您将反转并添加一个,因此您反转第二个参数并添加一个您反转或以其他方式带来一作为进位,因此减去 110 意味着反转并加一,因此 001 + 1 或 1111010 是负六。所以 1 - 6 是 001 - 110 = 001 + 001 + 1
  • 啊,太棒了,谢谢!我不小心删除了关于这个问题的原始问题,所以如果将来有人读到这个并且感到困惑,我最初的问题是 1 + 001 + 001 来自哪里。
【解决方案2】:

old_timer 有一个绝妙的答案,在我自己进行了一些研究之后,我在 Stack Overflow 上找到了另一个很好的答案,我想我也会链接:How to subtract two unsigned ints with wrap around or overflow

【讨论】:

  • 是的!答案很简单,你所要做的就是从滚动计数器中减去经过时间,减法是环绕鲁棒的。包装的计时器包含 0x05 的值,而过去的时间是 0xF5,如果您评估 0x05 - 0xF0,答案仍然是 0x10,或者 16. 16 单位时间已过,包装不可知。
【解决方案3】:

有一个简单的解决方案来计算两个与任何标准整数类型大小不同的整数之间的差异。您只需移动整数,使其 MSB 位于标准类型的 MSB 位置,执行算术运算,然后可选地将结果向后移动,使其与原始值具有相同的单位。

在这种情况下,systick 是一个 24 位计数器,因此:

uint32_t start_time = getSystick() ;

// do something

uint32_t end_time = getSystick() ;

uint32_t elapsed_time_in_systicks = ((start_time << 8) - (end_time << 8)) >> 8 ;

注意操作数的顺序 - systick 计数向下。这假设您正在设置0xffffff 的最大重新加载值,并且 systick 不会多次换行。

如果时间间隔使得计数器可能多次回绕,那么您可能不需要如此高分辨率,并且可能使用较低的重载值和递增计数器的中断处理程序,并使用较低分辨率的计数器用于时间测量。

或者,您可以使用在重新加载中断时递增的uint8_t 计数器。然后可以将该计数器与 24 位 systick 组合以创建真正的 32 位计数器。然而,为了确保一致性,需要注意一些 - 这样您就不会在重新加载之前将 systick 与之后的重新加载计数器组合在一起(反之亦然)。这可以通过以下方式完成:

uint32_t getSystick32()
{
    uint8_t msb ;
    uint32_t systick ;
    do
    {
       msb = getSystickReloadCounter() ;
       systick = getSystick() ;

    } while( msb != systick_reload_counter ) ;

    return msb << 24 | systick ;
}

【讨论】:

  • 感谢您的回复!这是一种有趣的方式。
猜你喜欢
  • 2021-12-30
  • 2021-05-28
  • 2021-01-18
  • 1970-01-01
  • 1970-01-01
  • 2020-09-13
  • 2014-10-14
  • 2016-03-08
  • 2017-07-16
相关资源
最近更新 更多