如何获得可靠的 Cortex M4 短延迟

Posted

技术标签:

【中文标题】如何获得可靠的 Cortex M4 短延迟【英文标题】:How to obtain reliable Cortex M4 short delays 【发布时间】:2014-06-30 00:11:57 【问题描述】:

我正在将一些代码从 M3 移植到 M4,它使用 3 个 NOP 在串行输出时钟更改之间提供非常短的延迟。 M3 指令集将 NOP 的时间定义为 1 个周期。我注意到 M4 中的 NOP 不一定会延迟任何时间。 我知道我需要禁用编译器优化,但我正在寻找一个低级命令,它会给我可靠、可重复的时间。 实际上,在这种特殊情况下,串行的使用非常偶然,并且可能非常慢,但我仍然想知道获得周期级延迟的最佳方法。

【问题讨论】:

您无法使用 UART 或外设定时器吗? 不,我没有可以及时设置或空闲运行的计时器。 uart 有自己的时钟除数。 我无法使用 UART 或外设定时器来产生 24ns 延迟。 根据ARM Cortex-M3 Devices Generic User Guide,NOP 指令也不一定会在 Cortex M3 上消耗任何时间。 【参考方案1】:

如果您需要如此短但确定性的“至少”延迟,也许您可​​以考虑使用除nop 之外的其他具有确定性非零延迟的指令。

The Cortex-M4 NOP 所描述的不一定很耗时。

您可以将其替换为and reg, reg,或者在上下文中大致相当于nop。或者,在切换 GPIO 时,您也可以自己重复 I/O 指令以强制实现状态的最小长度(例如,如果您的 GPIO 写入指令至少需要 5ns,则重复五次以获得至少 25ns)。如果您在 C 程序中插入 nops,这甚至可以在 C 中很好地工作(只需重复写入端口,如果它应该是 volatile,编译器不会删除重复的访问)。

当然这仅适用于非常短的延迟,否则对于短延迟,就像其他人提到的那样,等待某个时序源的繁忙循环会工作得更好(它们至少需要采样时序源所需的时钟,设置目标,并通过一次等待循环)。

【讨论】:

非常感谢,正如我在下面所说的,我正在使用 MOV R0,#1。自从我在 2014 年写下这个问题后不久,它就已在许多生产单元中使用,到目前为止它运行良好。【参考方案2】:

使用循环计数寄存器 (DWT_CYCCNT) 获得高精度计时!

注意:我也使用数字引脚和示波器对此进行了测试,结果非常准确。

参见stopwatch_delay(ticks) 和下面的支持代码,它使用 STM32 的 DWT_CYCCNT 寄存器,专门用于计算实际时钟滴答,位于地址 0xE0001004。

有关使用STOPWATCH_START/STOPWATCH_STOP 来衡量stopwatch_delay(ticks) 实际花费的时间的示例,请参见main,使用CalcNanosecondsFromStopwatch(m_nStart, m_nStop)

修改ticks输入进行调整

uint32_t m_nStart;               //DEBUG Stopwatch start cycle counter value
uint32_t m_nStop;                //DEBUG Stopwatch stop cycle counter value

#define DEMCR_TRCENA    0x01000000

/* Core Debug registers */
#define DEMCR           (*((volatile uint32_t *)0xE000EDFC))
#define DWT_CTRL        (*(volatile uint32_t *)0xe0001000)
#define CYCCNTENA       (1<<0)
#define DWT_CYCCNT      ((volatile uint32_t *)0xE0001004)
#define CPU_CYCLES      *DWT_CYCCNT
#define CLK_SPEED         168000000 // EXAMPLE for CortexM4, EDIT as needed

#define STOPWATCH_START  m_nStart = *((volatile unsigned int *)0xE0001004);
#define STOPWATCH_STOP   m_nStop = *((volatile unsigned int *)0xE0001004);


static inline void stopwatch_reset(void)

    /* Enable DWT */
    DEMCR |= DEMCR_TRCENA; 
    *DWT_CYCCNT = 0;             
    /* Enable CPU cycle counter */
    DWT_CTRL |= CYCCNTENA;


static inline uint32_t stopwatch_getticks()

    return CPU_CYCLES;


static inline void stopwatch_delay(uint32_t ticks)

    uint32_t end_ticks = ticks + stopwatch_getticks();
    while(1)
    
            if (stopwatch_getticks() >= end_ticks)
                    break;
    


uint32_t CalcNanosecondsFromStopwatch(uint32_t nStart, uint32_t nStop)

    uint32_t nDiffTicks;
    uint32_t nSystemCoreTicksPerMicrosec;

    // Convert (clk speed per sec) to (clk speed per microsec)
    nSystemCoreTicksPerMicrosec = CLK_SPEED / 1000000;

    // Elapsed ticks
    nDiffTicks = nStop - nStart;

    // Elapsed nanosec = 1000 * (ticks-elapsed / clock-ticks in a microsec)
    return 1000 * nDiffTicks / nSystemCoreTicksPerMicrosec;
 

void main(void)

    int timeDiff = 0;
    stopwatch_reset();

    // =============================================
    // Example: use a delay, and measure how long it took
    STOPWATCH_START;
    stopwatch_delay(168000); // 168k ticks is 1ms for 168MHz core
    STOPWATCH_STOP;

    timeDiff = CalcNanosecondsFromStopwatch(m_nStart, m_nStop);
    printf("My delay measured to be %d nanoseconds\n", timeDiff);

    // =============================================
    // Example: measure function duration in nanosec
    STOPWATCH_START;
    // run_my_function() => do something here
    STOPWATCH_STOP;

    timeDiff = CalcNanosecondsFromStopwatch(m_nStart, m_nStop);
    printf("My function took %d nanoseconds\n", timeDiff);

【讨论】:

您还可以使用示波器和数字引脚验证此行为。 这给了我短暂的延迟,但不是很短的延迟。 @Ant,你可以根据需要设置延迟时间;你希望多短? 我想要的延迟是3个周期。 与 this answer 中的 cmets 相同。在 168MHz 处理器上,DWT_CYCCNT 在 25 秒后溢出,但是当你做 1000 * nDiffTicks 时,你会在 25ms 后溢出,这是不必要的。 stopwatch_reset() 通常也不需要,尽管如果您将其删除,那么 stopwatch_getticks() &gt;= end_ticks 将无法工作。我会建议一个更简单(和正确)的实现,比如the delayUS_DWT function posted near the end of this article。【参考方案3】:

对于任何可靠的计时,我总是建议使用通用计时器。您的部分可能有一个计时器,该计时器的时钟频率足够高,可以为您提供所需的时间。对于串口,是否有理由不能使用相应的串口外围设备?我知道的大多数 Cortex M3/M4 都提供 USARTS、I2C 和 SPI,还有多个还提供 SDIO,这应该可以满足大多数需求。

如果这不可能,this *** question/answer 详细说明在 Cortex M3/M4 上使用循环计数器(如果可用)。您可以获取周期计数器并向其添加一些并对其进行轮询,但我认为您不会使用此方法在约 8 个周期以下合理地实现任何最小延迟。

【讨论】:

这不是标准串行,对于 SPI 和 I2C,我很乐意使用外围设备。这需要通过几个周期延迟来驱动 GPIO。我也同意循环计数器不起作用。【参考方案4】:

首先你必须从 ram 而不是 flash 运行,因为 flash 时间会很慢,一个 nop 可能需要很多个周期。 gpio 访问也应该至少需要几个时钟,所以你可能不需要/想要 nops 只是在 gpio 上敲击。循环结束时的分支也会很明显。你应该写一些指令来 ram 和分支到它,看看你能多快摆动 gpio。

不过,最重要的是,如果您的预算如此紧张,以至于您的串行时钟的速度与您的处理器时钟非常接近,那么您很可能不会让这个处理器与这个处理器一起工作。提高处理器中的 pll 不会改变闪存速度,它可能会使情况变得更糟(相对于处理器时钟),但 sram 应该可以扩展,所以如果你的处理器时钟上留有余量并且功率预算可以支持,那么重复实验在 sram 中具有更快的处理器时钟速度。

【讨论】:

在实践中 3 NOP 给了我我想要的时间,但我认为这还不够好,因为文档指出它们可能会被管道删除。我可以想象使用具有更好优化的下一个版本处理器的产品交付产品,并且突然之间没有像以前那样工作。我正在寻找一种插入几纳秒延迟的可靠方法。我目前在关闭编译器优化后使用 MOV R0,#1,因为我没有发现关于这些被删除的评论。 我会考虑那句话,什么会导致他们决定将它们从管道中删除,内部或外部力量是什么,如果你的代码没有改变,系统被严格控制核心不会有任何新的输入或获取变化等会导致管道不做它一直在做的事情。现在另一方面,肯定从一个版本的芯片到另一个可能会改变,但你可以查看可用内核的版本和芯片供应商正在使用的版本(我怀疑他们不只是弹出一个皮质- m4 并用另一个替换它 在一个简单的筹码旋转中,但谁知道呢。 如果你能做的最好的事情是三个 nop 来获得你的时间,那么底线是一样的,这不是 PIC,你太紧了,你需要一些其他芯片,你的处理器速度信号速度没有足够的余量。 什么会导致他们决定将它们从管道中删除?因为他们正在实施他们记录的内容 - 文档说它们可能会被删除。需要一些其他芯片 - 该产品正在生产中,它不是卧室的爱好。

以上是关于如何获得可靠的 Cortex M4 短延迟的主要内容,如果未能解决你的问题,请参考以下文章

单片机行业经常提到的M0 M1 M2 M3 M4 M7指的是啥

ARM Cortex-M3/M4 启动分析

stm32mp1 Cortex M4开发篇7:窗口看门狗

Cortex M4_M3学习摘要(一)

stm32mp1 Cortex M4开发篇1:stm32CubeIDE开发环境搭建

东芝推出TXZ+TM族高级系列新型M4N组Arm(R) Cortex(R)-M4微控制器