Embedded C - 插入延迟的最优雅方式

电器工程 C 手臂 延迟 海合会
2022-01-31 17:25:55

我正在从事一个涉及 cortex-m4 MCU (LPC4370) 的项目。而且我需要在打开编译器优化时插入延迟。到目前为止,我的解决方法是在 for 循环中上下移动数字输出:

for (int i = 0; i < 50000; i++)
{
     LPC_GPIO_PORT->B[DEBUGPIN_PORT][DEBUG_PIN1] = TRUE;
     LPC_GPIO_PORT->B[DEBUGPIN_PORT][DEBUG_PIN1] = FALSE;
}

但我想知道是否有更好的方法来愚弄 GCC。

4个回答

这里缺少这种内联无依赖延迟的上下文。但我假设您在初始化期间或代码的其他允许阻塞的部分需要一个短暂的延迟。

你的问题不应该是如何愚弄GCC。你应该告诉GCC 你想要什么。

#pragma GCC push_options
#pragma GCC optimize ("O0")   

for(uint i=0; i<T; i++){__NOP()}

#pragma GCC pop_options

在我看来,这个循环大约是 5*T 时钟。

来源


科林对另一个答案的公平评论不保证 NOP 在 M4 上执行循环。如果您想放慢速度,也许 ISB(冲洗管道)是一个更好的选择。请参阅通用用户指南

如果您有可用的计时器,请使用计时器。SysTick 的配置非常简单,在 Cortex M4 用户指南(或 M0,如果您使用 M0 部分)中有文档。在其中断中增加一个数字,在您的延迟函数中,您可以阻止,直到该数字增加了一定数量的步数。

如果 systick 已在使用中,则您的部分包含许多计时器,并且原理保持不变。如果使用不同的定时器,您可以将其配置为计数器,只需查看其计数寄存器即可避免中断。

如果你真的想用软件来做,那么你可以把它asm("nop");放在你的循环中。nop不必花费时间,处理器可以在不执行它的情况下将它们从其管道中删除,但编译器仍应生成循环。

不是要减损这里的其他答案,而是您需要什么长度的延迟?一些数据表提到纳秒;其他微秒;还有一些毫秒。

  • 纳秒延迟通常最好通过添加“浪费时间”的指令来解决。事实上,有时微控制器的速度意味着您显示的“将引脚设置为高电平,然后将引脚设置为低电平”之间的延迟已经得到满足。否则,一个或多个NOPJMP-to-next-instruction,或其他浪费时间的指令就足够了。
  • 短微秒延迟可以通过for循环来完成(取决于 CPU 速率),但更长的延迟可能需要等待实际的计时器;
  • 毫秒延迟通常最好通过在等待过程完成时完全做其他事情来解决,然后在继续之前返回以确保它实际上已经完成。

简而言之,这一切都取决于外围设备。

最好的方法是使用片上定时器。Systick、RTC 或外围定时器。这些具有时序精确、确定性的优点,并且可以在 CPU 时钟速度发生变化时轻松调整。或者,您甚至可以让 CPU 休眠并使用唤醒中断。

另一方面,脏的“忙延迟”循环很少是准确的,并且会出现各种问题,例如与特定 CPU 指令集和时钟的“紧密耦合”。

一些注意事项:

  • 反复切换 GPIO 引脚是个坏主意,因为这会不必要地消耗电流,如果引脚连接到走线,还可能导致 EMC 问题。
  • 使用 NOP 指令可能不起作用。许多架构(如 Cortex M、iirc)可以在 CPU 级别上随意跳过 NOP 而实际上不执行它们。

如果您想坚持生成脏忙循环,那么只需volatile限定循环迭代器就足够了。例如:

void dirty_delay (void)
{
  for(volatile uint32_t i=0; i<50000u; i++)
    ;
}

这保证会生成各种废话代码。例如 ARM gcc-O3 -ffreestanding给出:

dirty_delay:
        mov     r3, #0
        sub     sp, sp, #8
        str     r3, [sp, #4]
        ldr     r3, [sp, #4]
        ldr     r2, .L7
        cmp     r3, r2
        bhi     .L1
.L3:
        ldr     r3, [sp, #4]
        add     r3, r3, #1
        str     r3, [sp, #4]
        ldr     r3, [sp, #4]
        cmp     r3, r2
        bls     .L3
.L1:
        add     sp, sp, #8
        bx      lr
.L7:
        .word   49999

从那里你理论上可以计算每条指令需要多少滴答声,并相应地更改幻数 50000。流水线、分支预测等将意味着代码的执行速度可能比时钟周期的总和更快。由于编译器决定涉及堆栈,因此数据缓存也可以发挥作用。

我的重点是准确计算这段代码实际需要多少时间是很困难的。与尝试进行理论计算相比,使用范围进行试错基准测试可能是一个更明智的想法。