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

  • 每次发送1字节,加适当延时,保证发送相邻两字节之间的间隔尽量短 。
  • 目前我采用的就是第二种方法,第一种方法就留给大家验证并开发吧(其实就是我懒) 。来看一下我的现实代码:
    void hi_spi_delay(void){volatile unsigned int tmp = 0;while (tmp++ < 30);}void spi_write_byte(unsigned char dat){unsigned short spi_data = https://www.huyubaike.com/biancheng/0;spi_data = dat;ssp_writew(SSP_DR, spi_data);hi_spi_delay();}【基于PL022 SPI 控制器 海思3516系列芯片SPI速率慢问题深入分析与优化】这个延迟函数一定要根据编译器、CPU主频、SPI时钟频率等实际测量后进行调整 。经过我的反复调整和测量 , 最终把循环计数设置为了30,来看一下示波器抓到的波形
    基于PL022 SPI 控制器 海思3516系列芯片SPI速率慢问题深入分析与优化

    文章插图
    间隔 65ns,也就是每字节耗时 225us,大约相当于 36MHz 的SPI时钟频率 。
    改成其他值行不行呢?这是我的测量结果
    • 30 可以保证在50MHz时钟下 , 每两字节之间间隔 65ns,
    • 32 可以保证在50MHz时钟下,每两字节之间间隔 80ns , 
    • 35 可以保证在50MHz时钟下,每两字节之间间隔 100ns,但是,重点来了!循环计数小于29或不加延时的间隔是 60ns,似乎达到了某种限制 , 具体原因没找到,我担心有丢数据的风险,所以将循环计数设置为了30 , 这个值也正好是可以明显观察到延时函数有效的最小值,SPI 速率虽然没有真正达到理论值,但是对于目前的使用场景来说已经足够了 。
    前面说了,延时函数一定要根据实际情况进行修改,确保每字节之间有一定间隔 。假设你换了海思的另外一款芯片,或者使用了其他基于 ssp-pl022 控制器的芯片,也遇到了类似的 SPI 速率低的问题 , 但手头没有示波器进行测量怎么办?假设你所使用的芯片提供了精确到10ns的延时函数,直接拿来用就行 。没有这样的函数没关系,可以做一个简单的估算,估算结果与实际肯定有偏差,但不管怎么说这个数值也算比较靠谱的 。我们来一步一步分析这个延时函数如何实现 。
    首先是 volatile 关键字,开发调试期间为了方便分析问题,编译优化选项往往设置为 -O0,不论加不加这个关键字都没问题,但正式程序的编译优化选项一般都会设置为-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.

    推荐阅读