我试图通过arm-none-eabi-gcc来理解为stm32f103芯片组生成的一些汇编程序,它似乎只运行了我预期的一半速度.我对汇编程序并不熟悉,但是因为如果你想了解你的编译器正在做什么,每个人总是说要阅读asm,我看到我得到了多远.它的一个简单功能:
void delay(volatile uint32_t num) {
volatile uint32_t index = 0;
for(index = (6000 * num); index != 0; index--) {}
}
Run Code Online (Sandbox Code Playgroud)
时钟速度是72MHz,上面的功能给我一个1ms的延迟,但我期望0.5ms(因为(6000*6)/ 72000000 = 0.0005).
汇编程序是这样的:
delay:
@ args = 0, pretend = 0, frame = 16
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
sub sp, sp, #16 stack pointer = stack pointer - 16
movs r3, #0 move 0 into r3 and update condition flags
str r0, [sp, #4] store r0 at location stack pointer+4
str r3, [sp, #12] store r3 at location stack pointer+12
ldr r3, [sp, #4] load r3 with data at location stack pointer+4
movw r2, #6000 move 6000 into r2 (make r2 6000)
mul r3, r2, r3 r3 = r2 * r3
str r3, [sp, #12] store r3 at stack pointer+12
ldr r3, [sp, #12] load r3 with data at stack pointer+12
cbz r3, .L1 Compare and Branch on Zero
.L4:
ldr r3, [sp, #12] 2 load r3 with data at location stack pointer+12
subs r3, r3, #1 1 subtract 1 from r3 with 'set APSR flag' if any conditions met
str r3, [sp, #12] 2 store r3 at location sp+12
ldr r3, [sp, #12] 2 load r3 with data at location sp+12
cmp r3, #0 1 status = 0 - r3 (if r3 is 0, set status flag)
bne .L4 1 branch to .L4 if not equal
.L1:
add sp, sp, #16 add 16 back to the stack pointer
@ sp needed
bx lr
.size delay, .-delay
.align 2
.global blink
.thumb
.thumb_func
.type blink, %function
Run Code Online (Sandbox Code Playgroud)
我已经评论了我相信每条指令的含义.所以我相信.L4部分是延迟函数的循环,它是6个指令长.我确实意识到时钟周期并不总是与指令相同,但由于这是一个很大的差异,并且因为这是一个我想象的循环,并且有效地预测和流水线,我想知道是否有一个可靠的原因,我看到2个时钟每条指令的周期.
背景:在我正在研究的项目中,我需要使用5个输出引脚来控制线性ccd,并且时序要求相当紧凑.绝对频率不会被最大化(我将使引脚的时钟慢于cpu能够的时钟),但相对于彼此的引脚时序很重要.因此,不是使用能力极限并且可能使相对时序复杂化的中断,而是考虑使用循环来提供引脚电压变化事件之间的短延迟(大约100 ns),甚至可以编译展开的汇编程序中的整个部分,因为我有足够的程序存储空间.有一段时间引脚没有变化,在此期间我可以运行ADC来采样信号.
虽然我所询问的奇怪行为不是表演限制,但我宁愿在继续之前理解它.
编辑:从评论,arm技术参考给出指令时间.我已将它们添加到装配中.但它仍然只有9个周期,而不是我期望的12个周期.跳跃本身就是一个循环吗?
TIA,皮特
我想我必须把这个给ElderBug,尽管Dwelch提出了一些可能也非常相关的要点,感谢所有人.从这里开始,我将尝试使用展开的组件来切换相位相差20ns的引脚,然后返回C进行更长时间的等待,并进行ADC转换,然后返回到组装以重复该过程,密切关注组件从gcc输出来粗略了解我的时间是否正常.BTW Elder修改后的wait_cycles函数按预期的那样工作.再次感谢.
Eld*_*Bug 11
首先,在C中执行自旋等待循环是个坏主意.在这里,我可以看到你编译了-O0(没有优化),如果你启用了优化,你的等待时间会短得多(编辑:实际上,你发布的volatile未经优化的代码可能只是结果,但这并不重要).C等待循环不可靠.我维护了一个依赖于这样的函数的程序,每次我们不得不更改编译器标志时,时间都会混乱(幸运的是,有一个蜂鸣器因此而失调,提醒我们更改等待循环).
关于为什么每个周期没有看到1条指令,这是因为某些指令不需要1个周期.例如,bne如果采用分支,则可以采取额外的周期.问题是您可以使用较少的确定性因素,例如总线使用情况.访问RAM意味着使用总线,该总线可能忙于从ROM获取数据或由DMA使用.这意味着像指令STR和LDR可能有延迟.在你的例子中,你有一个STR跟LDR在同一个位置(典型的-O0); 如果MCU没有存储到转发转发,则可能会有延迟.
我为时间做的是使用硬件定时器延迟1μs以上,并使用硬编码的汇编循环来实现真正的短延迟.
对于硬件定时器,您只需要以固定频率设置定时器(如果您希望延迟精确到1μs,周期<1μs),并使用一些简单的代码:
void wait_us( uint32_t us ) {
uint32_t mark = GET_TIMER();
us *= TIMER_FREQ/1000000;
while( us > GET_TIMER() - mark );
}
Run Code Online (Sandbox Code Playgroud)
您甚至可以使用mark参数在某个任务之前设置它,并使用该函数等待之后的剩余时间.示例:
uint32_t mark = GET_TIMER();
some_task();
wait_us( mark, 200 );
Run Code Online (Sandbox Code Playgroud)
对于程序集等待,我将这个用于ARM Cortex-M4(靠近你的):
#define CYCLES_PER_LOOP 3
inline void wait_cycles( uint32_t n ) {
uint32_t l = n/CYCLES_PER_LOOP;
asm volatile( "0:" "SUBS %[count], 1;" "BNE 0b;" :[count]"+r"(l) );
}
Run Code Online (Sandbox Code Playgroud)
这非常短,精确,不受编译器标志和总线负载的影响.您可能需要调整CYCLES_PER_LOOP,但我认为它将与您的MCU具有相同的值(此处为1 + 2 SUBS+BNE).