使用 ARM Cortex SysTick 计算运行时间;当 SysTick 计数器翻转时会发生什么?

Using ARM Cortex SysTick for calculating elapsed time; what happens when SysTick counter rolls over?

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

Picture of logic I'm referring to

Code of logic I'm referring to

但是,有一点让我很困惑。我理解从 "last" 中减去 "now" 以获得经过时间的想法。但是,当 "now" 在 SysTick 计时器达到 0 并重新加载时翻转,但 "last" 是 SysTick 计时器翻转之前的值(所以 "now" 是> 比 "last",通常它应该更小)?该值存储在一个无符号长整数中,所以它会破坏程序吗?还是这永远不会发生,如果是,为什么?如果能帮我解决这个问题,我将不胜感激!

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

取一个 3 位计数器,它都相同地扩展到 24 或 32(我认为系统计时器是 24。

所以向上或向下计数都没有关系,你只需要正确调整操作数即可。所以说倒数 7,6,5,4 7 - 4 是 3 个计数。但是二进制 1,0,7,6 呢 1 - 6 = 001 - 110

     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 个单位。 基本上使用二进制补码的优点,我们知道二进制补码的加法和减法不是无符号或有符号特定的,相同的位模式导致相同的位模式。这就是我们解释它们的方式。所以我们有点作弊,采用有符号数学并将结果解释为无符号。

old_timer 有一个很好的答案,在我自己做了更多研究之后,我在 Stack Overflow 上找到了另一个很好的答案,我想我也会 link:How to subtract two unsigned ints with wrap around or overflow

有一个简单的解决方案可以计算两个大小与任何标准整数类型不同的整数之间的差值。您只需移动整数,使其 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 ;
}