使用 STM32 设备获取当前时间(以微秒为单位)时出现问题

Problems getting the current time in microseconds with a STM32 device

我正在使用 stm32f103c8,我需要一个函数,当从中断处理程序中调用时,该函数将 return 以微秒为单位的正确时间。我在网上找到了以下代码可以做到这一点:

uint32_t microsISR()
{
    uint32_t ret;
    uint32_t st = SysTick->VAL;
    uint32_t pending = SCB->ICSR & SCB_ICSR_PENDSTSET_Msk;
    uint32_t ms = UptimeMillis;

    if (pending == 0)
        ms++;

    return ms * 1000 - st / ((SysTick->LOAD + 1) / 1000);
}

我对其工作原理的理解是使用系统时钟计数器,该计数器从 8000 (LOAD+1) 开始重复递减计数,当它达到零时,会生成一个中断,使变量 UptimeMills 递增。这给出了以毫秒为单位的时间。为了获得微秒,我们获取系统时钟计数器的当前值并将其除以 8000/1000 以给出以微秒为单位的偏移量。由于计数器正在倒计时,我们从当前时间中减去它(以毫秒为单位 * 1000)。(实际上,我认为应该在这个计算中的 # 毫秒中加上一个是正确的)。

这一切都很好,除非在调用此函数时(在中断处理程序中),系统时钟计数器已经包装但尚未调用系统时钟中断,然后 UptimeMillis 计数将关闭一。这是以下几行的目的:

if (pending == 0)
    ms++;

然而,看这个没有意义。如果没有挂起的中断,它会增加 # ms。事实上,如果我使用此代码,我会在 returned 时间内在计数器翻转的点出现大量故障。所以我将行更改为:

if (pending != 0)
    ms++;

这产生了更好的结果,但我仍然偶尔会遇到故障(大约每 2000 次中断中有 1 次),它总是在计数器滚动时发生。

在中断期间,我记录了当前的毫秒值、微秒值和计数器值。我发现有两种情况会出错:

  Milli Micros  DT  Counter Pending
1 1661  1660550 826  3602   0
2 1662  1661374 824  5010   0
3 1663  1662196 822  6436   0
4 1663  1662022 -174 7826   0
5 1664  1663847 1825 1228   0
6 1665  1664674 827  2614   0
7 1666  1665501 827  3993   0

中断以大约 820us 的正常速率进入。在这种情况下,似乎在中断 3 和中断 4 之间发生的事情是计数器已回绕,但挂起标志未设置。所以我需要将 1000 添加到值中,因为我没有这样做,所以我得到了一个负的经过时间。

第二种情况如下:

  Milli Micros  DT  Counter Pending
1 1814  1813535 818  3721   0
2 1815  1814357 822  5151   0
3 1816  1815181 824  6554   0
4 1817  1817000 1819 2      1
5 1817  1816817 -183 1466   0
6 1818  1817637 820  2906   0

这是一个非常相似的情况,除了在这种情况下计数器还没有结束,但我已经得到了挂起的中断标志,这导致我错误地加了 1000。

很明显,两个竞争中断之间存在某种竞争条件。我已尝试将时钟中断优先级设置为高于和低于外部中断的优先级,但问题仍然存在。

有没有人对如何处理这个问题有任何建议,或者对在中断处理程序中获取时间为微秒的不同方法有何建议。

阅读UptimeMillis之前和之后SysTick->VAL以确保未发生翻转。

uint32_t microsISR()
{
    uint32_t ms = UptimeMillis;
    uint32_t st = SysTick->VAL;

    // Did UptimeMillis rollover while reading SysTick->VAL?
    if (ms != UptimeMillis)
    {
        // Rollover occurred so read both again.
        // Must read both because we don't know whether the
        // rollover occurred before or after reading SysTick->VAL.
        // No need to check for another rollover because there is
        // no chance of another rollover occurring so quickly.
        ms = UptimeMillis;
        st = SysTick->VAL;
    }

    return ms * 1000 - st / ((SysTick->LOAD + 1) / 1000);
}

或者这里是 do-while 循环中的相同想法。

uint32_t microsISR()
{
    uint32_t ms;
    uint32_t st;

    // Read UptimeMillis and SysTick->VAL until
    // UptimeMillis doesn't rollover.
    do
    {
        ms = UptimeMillis;
        st = SysTick->VAL;
    } while (ms != UptimeMillis);

    return ms * 1000 - st / ((SysTick->LOAD + 1) / 1000);
}