基于PL022 SPI 控制器 海思3516系列芯片SPI速率慢问题深入分析与优化( 四 )

-O2-Os-O3,这种情况下编译器会直接把这个函数优化掉,所以必须加 volatile 关键字 。
确定好你所使用的编译器和编译优化参数,有的芯片厂商会提供多个版本的编译器,或者后来编译器更新了,编译器不同可能会导致代码行为不同,编译优化参数不同也会导致代码行为不同,假设最终发布代码使用-Os,那么测试期间也使用-Os,总之确定好这两点,在之后的开发过程中不要更改 。
确定好你的延时函数的循环怎么写,包括但不限于
while (tmp++ < 30);tmp = 30while(1) {if(tmp-- == 0){break;}};tmp = 30while (tmp--);for (tmp=0; tmp<30; tmp++);无论怎么写都能达到延时的作用,有的编译器可能非常聪明,发现循环中什么都没做,最终这4种写法都被优化成了相同的汇编代码,但有的编译器可能不会,总之你不能保证编译器会把他们优化成相同的汇编代码,所以确定了延时函数的写法以后在之后的开发过程中不要更改 。
下一步将循环计数设置为比较大的数,例如十万,一百万,执行这个函数并计算耗时,不论是用秒表,还是用 gettimeofday,或者是 time 命令,总之最终目的是算出1个循环耗时多少 。假设我测量出循环百万次耗时5.3ms,那么循环一次耗时就是5.3ns 。向上取整按一次循环耗时6ns计算,为什么这样做?自己想 。
假设 SPI 的时钟速率是50MHz,发送数据位宽是 8bit,则发送1次耗时 1s / 50MHz * 8 = 160ns,延时循环的次数为 160 / 6 = 26.6,向上取整为27次 。考虑到函数调用的耗时、读写寄存器的耗时等,实际两次发送之间的间隔肯定比它略长,但无论怎么说,27 就是我们估算出来的延时循环计数 。
当然还有更靠谱一点的估算方法 。目前我的延时函数及对应的汇编代码如下:
void hi_spi_delay(void){volatile unsigned int tmp = 0;while (tmp++ < 30);}Dump of assembler code for function hi_spi_delay:0x00010454 <+0>:mov r3, #00x00010458 <+4>:sub sp, sp, #80x0001045c <+8>:str r3, [sp, #4]0x00010460 <+12>: ldr r3, [sp, #4]0x00010464 <+16>: cmp r3, #290x00010468 <+20>: add r3, r3, #10x0001046c <+24>: str r3, [sp, #4]0x00010470 <+28>: bls 0x10460 <hi_spi_delay+12>0x00010474 <+32>: add sp, sp, #80x00010478 <+36>: bxlrEnd of assembler dump.循环体对应的就是这5句
0x00010460 <+12>: ldr r3, [sp, #4]0x00010464 <+16>: cmp r3, #290x00010468 <+20>: add r3, r3, #10x0001046c <+24>: str r3, [sp, #4]0x00010470 <+28>: bls 0x10460 <hi_spi_delay+12>我所使用的芯片采用Cortex-A7架构,我实在没找到它的指令周期的文档,这里用Cortex-A9的替代一下,ARM官网文档如下 https://developer.arm.com/documentation/ddi0388/f/Instruction-Cycle-Timings?lang=en ,跳转指令的周期是不确定的,其他4个指令的都是单周期指令,循环中大多情况都是跳转,只有最后一次是不跳转,我们也按单周期计算好了 。由此可知,循环一次需要5个周期,CPU的主频是900MHz,假设CPU主频固定,不超频,也不降频进入低功耗模式,那么循环一次耗时 1s / 900MHz * 5 = 5.55ns,这可以说是一个很精确的值了,同样的条件下可以算出需要的循环次数为 160 / 5.55 = 28.8,向上取整为29次 。是不是和我目前使用的循环计数30基本一致?
好了,到此为止开发过程中遇到的问题还有我自己的疑惑都讲完了,有问题欢迎大家讨论 。
如果你有朋友正好在海思工作,请将本文转发给他,我不知道海思其他产品线的芯片会不会也有这个问题 。我这样写的代码多少有些随意,希望海思官方能优化一下 SPI 的速率问题 。

经验总结扩展阅读