如何在ARM上添加150个周期的延迟?

时间:2015-04-07 08:19:17

标签: arm bare-metal

更改Raspberry Pi上GPIO引脚的上拉/下拉电阻需要在根据规格启用和解除时钟信号后等待150个周期。做一点时间并没有伤害,但使用计时器等待的时间会更长,因此我不想这样做。所以我有这个简单的繁忙循环:

for (int i = 0; i < 150; ++i) { asm volatile (""); }

0:   e3a03096        mov     r3, #150        ; 0x96
4:   e2533001        subs    r3, r3, #1
8:   1afffffd        bne     4 <foo+0x4>

循环150次,执行300条指令。没有指令缓存,没有分支预测肯定超过150个周期。但是一旦打开这个循环,那个循环就会比我认为的150个循环更快,更快。

那么在启用或不启用指令缓存和分支预测的情况下,如何等待接近150个周期? 注意:最坏的情况可能是2个函数,delay_no_cache()和delay_cache()

这不是How to delay an ARM Cortex M0+ for n cycles, without a timer?的重复,因为指令缓存和分支预测完全抛出了时序。 Raspberry Pi(ARMv6)和Raspberry Pi2(ARMv7)之间的时序也不同。

是否有人知道执行时间(有和没有缓存),如果有人插入DMB,DSB(我猜那些将是NOP,因为没有访问ram)或ISB指令进入循环?这会阻止缓存启用时的失控效应吗?

2 个答案:

答案 0 :(得分:1)

我对Raspberry PI 2运行延迟()进行了一些测量,测量了因子为10的1-100000000次循环,并计算了从经过时间开始的循环次数。这表明没有缓存只需要通过一个空循环就足以实现150个循环的延迟(这只是sub + bcs)。单个NOP(序列为150)总共需要32个(总共5030个)循环而没有高速缓存,1.5个循环(总共226.5个)。 orr; add;和; mov; orr; add;和; mov;循环还显示了CPU的流水线操作和超标量,每个操作码只需0.15个字符。得到一个井时循环不太好。

总之,我必须放弃,只使用基于计时器的延迟。没有缓存的情况实际上比使用缓存需要150个周期的循环更快。

void delay(uint32_t count) {
    uint32_t a = 0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0, h = 0;
    while(count--) {
// branch icache dcache cycles/loop
// no     no     no     ~507
// no     no     yes      43.005
// no     yes    no        1.005
// no     yes    yes       1.005
// yes    no     no     ~507
// yes    no     yes      43.005
// yes    yes    no        1.005
// yes    yes    yes       1.005
// asm ("");

// branch icache dcache cycles/loop
// no     no     no     ~750
// no     no     yes      67.500
// no     yes    no       16.500
// no     yes    yes      16.500
// yes    no     no     ~750
// yes    no     yes      67.500
// yes    yes    no       16.500
// yes    yes    yes      16.500
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");
// asm ("nop");

// branch icache dcache cycles/loop
// no     no     no     ~505
// no     no     yes      43.500
// no     yes    no        1.500
// no     yes    yes       1.500
// yes    no     no     ~505
// yes    no     yes      43.500
// yes    yes    no        1.500
// yes    yes    yes       1.500
 asm ("orr %0, %0, %0" : "=r" (a) : "r" (a));
 asm ("add %0, %0, %0" : "=r" (b) : "r" (b));
 asm ("and %0, %0, %0" : "=r" (c) : "r" (c));
 asm ("mov %0, %0" : "=r" (d) : "r" (d));
 asm ("orr %0, %0, %0" : "=r" (e) : "r" (e));
 asm ("add %0, %0, %0" : "=r" (f) : "r" (f));
 asm ("and %0, %0, %0" : "=r" (g) : "r" (g));
 asm ("mov %0, %0" : "=r" (h) : "r" (h));

// branch icache dcache cycles/loop
// no     no     no     ~1010
// no     no     yes       85.005
// no     yes    no        18.000
// no     yes    yes       18.000
// yes    no     no     ~1010
// yes    no     yes       85.005
// yes    yes    no        18.000
// yes    yes    yes       18.000
// isb();

// branch icache dcache cycles/loop
// no     no     no     ~5075
// no     no     yes      481.501
// no     yes    no       141.000
// no     yes    yes      141.000
// yes    no     no     ~5075
// yes    no     yes      481.501
// yes    yes    no       141.000
// yes    yes    yes      141.000
// isb();
// isb();
// isb();
// isb();
// isb();
// isb();
// isb();
// isb();
// isb();
// isb();
    }
}

答案 1 :(得分:0)

您可能需要使用 REPEAT MACRO FUNCTION 来延迟。在运行时使用循环,总会有优化,循环本身也需要时间。您可以迭代NOP的宏150次。没有优化也没有冗余周期。

这是一个重复的宏模板:

#define MACRO_CMB( A , B)           A##B
#define M_RPT(__N, __macro)         MACRO_CMB(M_RPT, __N)(__macro)

#define M_RPT0(__macro)
#define M_RPT1(__macro)             M_RPT0(__macro)   __macro(0)
#define M_RPT2(__macro)             M_RPT1(__macro)   __macro(1)
#define M_RPT3(__macro)             M_RPT2(__macro)   __macro(2)
...
#define M_RPT256(__macro)           M_RPT255(__macro) __macro(255)

您可以像这样定义NOP指令:

#define MY_NOP(__N)                 __asm ("nop");    // or sth like "MOV R0,R0"

然后你可以通过调用它来重复指令150次:

M_RPT(150, MY_NOP);

它真的会被执行150次。

希望这会有所帮助。