c - stm32g483 基于 SysTick 的计时器的奇怪行为

标签 c timer arm embedded stm32

SysTick使用说明

系统节拍

我们有一个基于 STM32G483 MCU (Cortex M4) 的定制板。我们使用 SysTick 作为软件定时器的引用。 SysTick 重载寄存器设置为 0x00FFFFFF 以便产生最少的中断。 SysTick 以 128MHz 的 CPU 时钟计时,这意味着每 131ms 左右就有一个 SysTick 中断。中断使滴答计数器增加加载值 + 1。

#define SYSTICK_LOAD_VALUE 0x00FFFFFFU

static volatile uint64_t _ticks;

void 
systick_interrupt(void)
{
    _ticks += SYSTICK_LOAD_VALUE + 1;
}

然后我们使用当前值寄存器获取当前计数周期中经过的时钟周期数来计算当前时间。

uint64_t 
systick_get_ticks(void)
{
    return _ticks - SysTick->VAL;
}

软件定时器

然后我们可以将这个值用于不同的软件定时器,理论上这些定时器可以在几个时钟周期的数量级内计数。

void
timer_start(struct timer *timer)
{
    timer->_start_tick = systick_get_ticks();
}

bool
timer_check_ticks(const struct timer timer, uint64_t duration)
{
    uint64_t difference = systick_get_ticks() - timer._start_tick;
    return (difference >= duration);
}

由于函数调用的开销,不可能精确到滴答,但在更长的时间段内仍然应该是准确的,例如 1us(128 个滴答)或 1ms(128 000)。当然,软件定时器可能会过冲一些时钟周期,具体取决于主循环频率,但它不应该下冲。

测试

我们看到这些计时器有一些奇怪的行为,因此我们决定通过最简单的主循环来切换我们可以探测的 GPIO 来测试它们。

int
main(void)
{
    // Clock, NVIC, SysTick and GPIO initialisation
    struct pin test_gpio;
    struct timer test_timer;
    timer_start(&test_timer);
    while (TRUE) {   
        if (timer_check_ticks(test_timer, 128000U)) { // 128000 ticks @ 128MHz ==> 1ms
            gpio_toggle(test_gpio);
            timer_start(&test_timer);
        }
    }
}

有了这个,我们期待一个具有 50% 占空比和 2 毫秒周期 (500Hz) 的方波,这是我大部分时间得到的。然而,有些脉冲有时会更短,例如 185us。在寻找问题根源的过程中,我们也注意到,任何修改后的编译都会改变较短脉冲的长度,但在代码执行时,这个持续时间似乎没有改变。

我们检查了核心时钟确实以 128MHz 运行,SysTick 被配置为我们想要的,我们编写了一个片段来检查 SysTick 中断是否以正确的频率触发,并且 systick_get_ticks() 函数返回一个可靠的数字。这使我们认为问题出在计时器代码本身,但我们似乎无法找到问题所在。

代码是用 clang (--target=arm-none-eabi) 编译的,不使用 STM32 HAL 库

最佳答案

考虑:

#define SYSTICK_LOAD_VALUE 0x00FFFFFFU

static volatile uint32_t systick_reload_count = 0 ;

void systick_interrupt(void)
{
    systick_reload_count++ ;
}

uint64_t systick_get_ticks(void)
{
    uint32_t reload_count = 0 ; 
    uint64_t ticks = 0 ;
    do
    {
        reload_count = systick_reload_count ;

        ticks = (reload_count * (SYSTICK_LOAD_VALUE + 1)) + 
                (SYSTICK_LOAD_VALUE - SysTick->VAL + 1) ;

    } while( systick_reload_count != reload_count ) ;
}

这里的 ISR 更简单(更快)并且访问 systick_reload_count 是此 32 位设备上的原子操作(即不能被中断)。

systick_get_ticks 中的 while 循环确保如果在非原子 ticks 计算期间发生重新加载,则获取新的 systick_reload_count 并且 ticks 重新计算。循环通常不应迭代超过两次(您必须被中断 131 毫秒,在这种情况下您还有其他问题!),因此保持确定性。

此解决方案的一个重要方面是计算是在重新加载计数的本地副本中执行的,而不是在 volatile 计数本身上执行的。

关于c - stm32g483 基于 SysTick 的计时器的奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68298474/

相关文章:

arm - ARM/Thumb 上 BLX 指令解码(Android)

azure - 将 ARM 模板资源部署到一个资源组,并将 secret 部署到另一资源组中的 key 保管库

c - 这是C18底座开关盒吗?

C 如何在进程之间共享信息?

JavaScript - 是否可以修改第三方脚本(外部托管)使用的 setInterval/setTimeout 的行为

ios - 如何在下一个“ViewController”上显示计时器结果

c - 我的 arm-none-eabi-gcc 在 stm32 上的项目有什么问题?

c - 在 C 中将 Unsigned Long Long 类型转换为字符指针

c - 考虑C编程代码和逻辑构建

ios - 当我退出 View Controller 时,如何在不按停止按钮的情况下终止倒计时计时器?