单片机IO最大翻转速率一直是我很感兴趣的一个要点,一方面可以体现出单片机内部电路水平,另一方面也反映了单片机的实际代码执行速度。
一般情况下我们都认为C语言是效率最高的高级语言,但到底有多高,其实是个很玄学的问题。nop延时就是很好的证明,明明理论上是单周期命令,已经深入到汇编层面了,但72M主频的单片机,nop72M次消耗的时间,远超预期的1S,可能会到10S,也就是说我们的代码实际执行速度比理论要慢得多,由于单片机内部原理过于复杂我也不了解,无法解释。只能通过一些测试来获得表面数据来获得一种直观的感受。
以前没学过汇编,翻转IO测试最快的就是用C语言操作寄存器(GPIOX->ODR或者GPIOX->BSRR),当时测出的结果大约是42M。
这阵子学linux的时候了解了下汇编,于是就想先在这上面练练手,顺便探究一下IO的极限波形和单片机的极限运行效率大概是多少。随手找了个air32的工程,初始化PA0推挽输出50M,汇编部分,R0存放GPIOA->ODR寄存器地址,R1存放1,R2存放0,只需要连续把R1赋值给R0,R2赋值给R0循环即可。
AREA TEST,CODE,READONLY;
EXPORT blink;
blink
LDR R0,=0x4001080C;
loop
LDR R1,=1;
STR R1,[R0];
LDR R1,=0;
STR R1,[R0];
...省略...
LDR R1,=1;
STR R1,[R0];
LDR R1,=0;
STR R1,[R0];
B loop;
END;
可能有的人就要问了,“B loop”已经实现了死循环,为什么中间还要复制大片,两句代码不就行了吗?
因为B本身也是一个语句,也需要运行时间,所以一次循环内翻转的次数越多,示波器上越多的会显示出真实的最大翻转速率。
![](https://img-blog.csdnimg.cn/ba1c57684fde4598bc4751af413fccd1.bmp)
示波器打PA0波形如上,这里得出一个结论和一个问题,结论是:翻转IO口这件事上,C语言直接操作寄存器的代码运行效率和汇编没什么区别。问题是,代码中高电平和低电平的持续时间是相同的,但单片机内部电路决定,IO的上升时间一定比下降时间要长,可是这里实际高电平持续时间比低电平持续时间更长。比较反常,笔者学识有限无法得出答案,希望有大神评论区解释一下。
可能有网友也看出来了,以上汇编代码其实不是最理想的,经过群友提醒后,我把代码又优化了一下。
AREA TEST,CODE,READONLY;
EXPORT blink;
blink
LDR R0,=0x4001080C;
LDR R1,=1;
LDR R2,=0;
loop
STR R1,[R0];
STR R2,[R0];
...省略...
STR R1,[R0];
STR R2,[R0];
B loop;
END;
把0和1状态分别存入两个寄存器,就可以在loop中不间断写入外部寄存器而不用再花费时间去赋值了。
![](https://img-blog.csdnimg.cn/817b77bc41a047b78a7dac7c56c674fa.bmp)
这里又遇到了一个问题,拉高拉低一个周期的汇编代码从4句减少到2句,但实际翻转频率并没有受影响。 而高电平持续时间变成了理想的,高电平短于低电平,因为IO拉高比拉低需要的时间更长,符合预期。
这里再插入一个热知识科普,STM32的IO初始化时配置的“speed”(2M,10M,50M),实际不会影响翻转频率,改变的是“压摆率”,也就是波形上升和下降的速度。也就是说,IO输出频率是可以超过设置的“速度”频率的,只是这时候波形可能就接近正弦波了,因为上升和下降都很慢,还没上升到VCC内部就拉低了。
![](https://img-blog.csdnimg.cn/9a665d904309407981732c2d407505aa.bmp)
![](https://img-blog.csdnimg.cn/15eec69ad4804539b67627493b24a6b0.bmp)
2M的波形可以很直观的看出来,频率其实依然可以跑到42M,只是波形还没来得及上升就下降了,导致高电平远低于理想的VCC。至于10M为什么意外的好,只能说我也很意外,不知道。可能是因为air32的生产工艺遥遥领先吧。