我正在将一些代码从 M3 移植到 M4,它使用 3 个 NOP 在串行输出时钟更改之间提供非常短的延迟。M3 指令集将 NOP 的时间定义为 1 个周期。我注意到 M4 中的 NOP 不一定会延迟任何时间。我知道我需要禁用编译器优化,但我正在寻找一个低级命令,它会给我可靠的、可重复的时间。实际上,在这种特殊情况下,串行使用非常偶尔并且可能非常慢,但我仍然想知道获得周期级延迟的最佳方法。
4 回答
如果您需要如此短但确定性的“至少”延迟,也许您可以考虑使用其他nop
具有确定性非零延迟的指令。
所描述的 Cortex-M4 NOP不一定很耗时。
您可以将其替换为,例如,或在上下文中and reg, reg
大致等同于 a 的东西。nop
或者,在切换 GPIO 时,您也可以自己重复 I/O 指令以强制实现状态的最小长度(例如,如果您的 GPIO 写入指令至少需要 5ns,则重复五次以获得至少 25ns)。如果您在 C 程序中插入 nops,这甚至可以在 C 中很好地工作(只需重复写入端口,如果volatile
它应该是,编译器不会删除重复的访问)。
当然这仅适用于非常短的延迟,否则对于短延迟,就像其他人提到的那样,等待某个时序源的繁忙循环会工作得更好(它们至少需要采样时序源所需的时钟,设置目标,并通过一次等待循环)。
使用循环计数寄存器(DWT_CYCCNT)获得高精度计时!
注意:我还使用数字引脚和示波器对此进行了测试,结果非常准确。
见stopwatch_delay(ticks
) 和下面的支持代码,它使用 STM32 的 DWT_CYCCNT 寄存器,专门设计用于计算实际时钟滴答,位于地址 0xE0001004。
请参阅使用/来衡量实际花费的时间main
的示例,使用.STOPWATCH_START
STOPWATCH_STOP
stopwatch_delay(ticks)
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);
}
对于任何可靠的计时,我总是建议使用通用计时器。您的部分可能有一个计时器,该计时器的时钟频率足够高,可以为您提供所需的时间。对于串口,是否有理由不能使用相应的串口外围设备?我知道的大多数 Cortex M3/M4 都提供 USARTS、I2C 和 SPI,还有多个还提供 SDIO,这应该可以满足大多数需求。
如果这不可能,这个 stackoverflow 问题/答案详细信息使用 Cortex M3/M4 上的循环计数器(如果可用)。您可以获取循环计数器并向其添加一些并对其进行轮询,但我认为您不会使用此方法在约 8 个周期以下合理地实现任何延迟以最小化延迟。
好吧,首先你必须从 ram 而不是 flash 运行,因为 flash 时间会很慢,一个 nop 可能需要很多周期。gpio 访问也应该至少需要几个时钟,所以你可能不需要/想要 nops 只是在 gpio 上敲击。循环结束时的分支也会很明显。你应该写一些指令来 ram 和分支到它,看看你能多快摆动 gpio。
但最重要的是,如果您的预算如此紧张以至于您的串行时钟的速度与您的处理器时钟非常接近,那么您很可能不会让这个处理器与这个处理器一起工作。提高处理器中的 pll 不会改变闪存速度,它可能会使情况变得更糟(相对于处理器时钟),但 sram 应该可以扩展,所以如果你的处理器时钟上留有余量并且功率预算可以支持,那么重复实验在 sram 中具有更快的处理器时钟速度。