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,来看一下示波器抓到的波形
文章插图
间隔 65ns,也就是每字节耗时 225us,大约相当于 36MHz 的SPI时钟频率 。
改成其他值行不行呢?这是我的测量结果
- 30 可以保证在50MHz时钟下 , 每两字节之间间隔 65ns,
- 32 可以保证在50MHz时钟下,每两字节之间间隔 80ns ,
- 35 可以保证在50MHz时钟下,每两字节之间间隔 100ns,但是,重点来了!循环计数小于29或不加延时的间隔是 60ns,似乎达到了某种限制 , 具体原因没找到,我担心有丢数据的风险,所以将循环计数设置为了30 , 这个值也正好是可以明显观察到延时函数有效的最小值,SPI 速率虽然没有真正达到理论值,但是对于目前的使用场景来说已经足够了 。
首先是
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.
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 基于vite3+tauri模拟QQ登录切换窗体|Tauri自定义拖拽|最小/大/关闭
- 痞子衡嵌入式:i.MXRT中FlexSPI外设不常用的读选通采样时钟源 - loopbackFromSckPad
- 基于tauri+vue3.x多开窗口|Tauri创建多窗体实践
- 提高工作效率的神器:基于前端表格实现Chrome Excel扩展插件
- 基于雪花算法的增强版ID生成器
- 基于QT和C++实现的翻金币游戏
- Mysql单表访问方法,索引合并,多表连接原理,基于规则的优化,子查询优化
- 基于tauri打造的HTTP API客户端工具-CyberAPI
- 基于纯前端类Excel表格控件实现在线损益表应用
- 知识图谱实体对齐2:基于GNN嵌入的方法