在一个实时应用程序¹中,我需要在ARM Cortex M3上(类似于STM32F101)轮询一个内部外设寄存器的某个位,直到它为零,在尽可能紧凑的循环中执行。我使用位带技术来访问相应的位。以下是(有效的)C代码:
如何降低投票的不确定性?一个5周期循环可以满足我的目标:在它归零后尽可能接近15.5个周期时采样相同的位。
我的规格要求可靠地检测至少6.5个CPU时钟周期的低脉冲;如果持续时间小于12.5个周期,则可靠地将其分类为短脉冲;如果持续时间超过18.5个周期,则可靠地将其分类为长脉冲。这些脉冲与CPU时钟没有定义的相位关系,而CPU时钟是我唯一准确的时间参考。这就需要一个最多5个时钟周期的轮询循环。实际上,我正在模拟运行在几十年前的8位CPU上的代码,该CPU可以使用5个时钟周期进行轮询,而这也成为了规格的要求。
我试图通过在循环之前插入NOP来调整代码对齐,尝试了许多变体,但从未观察到任何变化。
我尝试了反转CMP和LDR的顺序,但仍然得到6个周期。
这个是8个周期。
但这个是9个周期。
while (*(volatile uint32_t*)kMyBit != 0);
该代码被复制到芯片上的可执行RAM中。经过一些手动优化²,轮询循环缩减为以下代码,我计时³为6个周期:
0x00600200 681A LDR r2,[r3,#0x00]
0x00600202 2A00 CMP r2,#0x00
0x00600204 D1FC BNE 0x00600200
如何降低投票的不确定性?一个5周期循环可以满足我的目标:在它归零后尽可能接近15.5个周期时采样相同的位。
我的规格要求可靠地检测至少6.5个CPU时钟周期的低脉冲;如果持续时间小于12.5个周期,则可靠地将其分类为短脉冲;如果持续时间超过18.5个周期,则可靠地将其分类为长脉冲。这些脉冲与CPU时钟没有定义的相位关系,而CPU时钟是我唯一准确的时间参考。这就需要一个最多5个时钟周期的轮询循环。实际上,我正在模拟运行在几十年前的8位CPU上的代码,该CPU可以使用5个时钟周期进行轮询,而这也成为了规格的要求。
我试图通过在循环之前插入NOP来调整代码对齐,尝试了许多变体,但从未观察到任何变化。
我尝试了反转CMP和LDR的顺序,但仍然得到6个周期。
0x00600200 681A LDR r2,[r3,#0x00]
; we loop here
0x00600202 2A00 CMP r2,#0x00
0x00600204 681A LDR r2,[r3,#0x00]
0x00600206 D1FC BNE 0x00600202
这个是8个周期。
0x00600200 681A LDR r2,[r3,#0x00]
0x00600202 681A LDR r2,[r3,#0x00]
0x00600204 2A00 CMP r2,#0x00
0x00600206 D1FB BNE 0x00600200
但这个是9个周期。
0x00600200 681A LDR r2,[r3,#0x00]
0x00600202 2A00 CMP r2,#0x00
0x00600204 681A LDR r2,[r3,#0x00]
0x00600206 D1FB BNE 0x00600200
¹ 在没有中断发生的情况下,测量位低的时间。
² 最初由编译器生成的代码将r12作为目标寄存器,并且这会在循环中增加4个字节的代码,导致1个周期的开销。
³ 给出的数字是使用一个被认为是周期精确的实时STIce模拟器及其读取寄存器地址的模拟触发器功能获得的。之前我尝试了在循环中设置断点的“状态”计数器,但结果取决于断点的位置。单步执行甚至更糟:它总是给出LDR的4个周期,而实际上有时可以减少到3个周期。