在ARM Cortex M3(类似于STM32F101)上的实时应用中,我需要尽可能紧密地轮询内部外设的寄存器,直到其为零为止。我使用位带访问适当的位。(工作中的)C代码是
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时钟没有定义的相位关系,这是我唯一的准确时序参考。这需要最多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个周期。
如果我对问题的理解正确,则不一定需要减少循环周期,而可以减少随后采样之间的周期数(即LDR指令)。但是每次迭代可以有多个LDR。您可以尝试如下操作:
ldrb r1, [r0]
loop:
cbz r1, out
ldrb r2, [r0]
cbz r2, out
ldrb r1, [r0]
b loop
out:
两个LDRB指令之间的间距会有所不同,因此样本的间距不会均匀。
这可能会稍微延迟退出循环,但是从问题描述中我无法说出它是否重要。
我碰巧可以访问精确到周期的M7模型,并且当过程使您的原始循环稳定时,M7在每个迭代中以3个周期运行(意味着每3个周期LDR),而上面建议的循环以4个周期运行,但是现在有那里有两个LDR(因此,每2个周期LDR)。采样率肯定提高了。
值得赞扬的是,@ Peter Cordes在评论中建议与CBZ一起休息。
诚然,如果您追求的是采样率,M3的速度会更慢,但是仍然值得一试。
您也可以检查LDRB而不是LDR(如上面的代码)是否有任何更改,尽管我不希望如此。
UPD:我还有另一个2-LDR循环版本,该版本在M7上完成了3个周期,您可以尝试一下(也可以通过CBZ中断方便地平衡循环后的路径):
ldr r1, [r0]
loop:
ldr r2, [r0]
cbz r1, out_slow
cbz r2, out_fast
ldr r1, [r0]
b loop
out_fast:
/* NOPs as required */
out_slow:
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句