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

海思3516系列芯片SPI速率慢问题深入分析与优化(基于PL022 SPI 控制器)
我在某个海思主控的项目中需要使用SPI接口来驱动一块液晶屏 , 液晶屏主控为 st7789,分辨率 240x240,图像格式 RGB565 。
查阅海思相关手册可知,Hi3516EV200 的 SPI 最高速率为 50MHz,理论上每秒钟可以发送 50M/8=6.25MB 数据 。假设我需要在屏幕上以30fps的速率全屏实时显示摄像头的预览画面,每秒的数据量为 240*240*2*30=3456000B=3375KB=3.296MB,假设 SPI 工作在阻塞模式 , 则 cpu 使用率为 3.296/6.25*100%=52.7%,看起来还不错 。如果我想进一步降低cpu使用率还可以降低预览图分辨率 , 降低帧率 , 比如我可以按 240*180 20fps 的规格显示缩略图,那么cpu使用率就可以降到 240*180*2*20/1024/1024/6.25=26.4%
上面这些都是我在写代码前的理论分析,真实效果当然需要写程序简单测试一下 。测试代码内容大致如下:使用原生linux提供的 SPI 接口,即/dev/spidevX.X , 主程序中一个死循环没有sleep,使用不同的颜色不停地刷全屏,看一秒钟能达到多少次,也就是刷新率能达到多少帧 。
实际测试结果 , 每秒大约5次!肉眼可见的慢!能很明显看到刷屏的时候是从上到下覆盖过来的!
这么慢的速率就算以240*180的分辨率来刷新预览画面也只能达到7、8帧的水平,何况cpu使用率是100%,其他业务也没办法正常运行,所以spi的速率必须优化 。
第一步当然是用逻辑分析仪或示波器抓实际波形,观察是时钟信号没有达到50MHz,还是有其他地方浪费时间 。对于逻辑分析仪而言 , 要考虑采样率是否足够,例如逻辑分析仪的采样率是100MHz , 理论上可以采集50MHz的信号,实际采集50MHz的信号很可能采集不准;示波器也一样,示波器需要考虑的是带宽,一般入门级示波器都有100MHz带宽,问题不大 。对了,别拿DIY的示波器来捣乱 。
这张图就是我通过示波器抓到的海思 SPI 时钟线上的波形,问题非常明显 , 发送的两个字节之间间隔了 1.656us!开发过任意单片机的spi并实际抓过波形的朋友应该都知道,spi的时钟几乎是连续,正常情况下绝对不可能间隔这么长 。

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

文章插图
再检查一下spi的时钟有没有达到理论的50MHz,8个clk共计160ns,没问题,达到了理论速率 。
基于PL022 SPI 控制器 海思3516系列芯片SPI速率慢问题深入分析与优化

文章插图
题外话:为什么50MHz的方波在示波器上显示为正弦波?因为我这款示波器的带宽只有100MHz,50MHZ方波信号本身没有超过示波器带宽的上限 , 但是它的2n+1次谐波分量远远高于示波器带宽 。所以想要勉强看到50MHz方波的波形 , 那么示波器至少要能采集到3次谐波信号,也就是150MHz的信号,这就需要一个200MHz带宽的示波器(我买不起?。?
简单计算一下,发送一个字节需要 0.16+1.656=1.816us,也就是1秒钟可以发送 1s/1.816us=549450B=536.6KB 的数据,这个数据量甚至不足理论值的十分之一 , 换算下来确实1秒钟也只能刷5屏 。所以接下来的目标就是找出到底是什么原因让发送的两个字节之间占用了足足1.6us 。
首先我的发送代码中没有加任何延时函数,所以这1.6us的延时只能来自于linux内核spi驱动 。
查看linux内核spi相关源码(提前安装好完整的海思开发环境,并下载对应版本linux内核源码,打海思linux源码patch)linux-4.9.y\drivers\spi\spi-pl022.c由源码可知海思的spi使用的是 ARM PrimeCell SSP (PL022) 控制器,这些驱动代码应该非常成熟了,不会存在这么明显的问题,大概看了看源码也没发现啥耗时的地方 , 之后专门以 ssp-pl022 和 delay 作为关键字google一番也没有发现类似的问题 。思索了一下,我决定放弃修改内核驱动,首先内核代码很成熟,基本轮不到我这种小角色去debug,其次每次修改都要重新编译并烧录内核,太麻烦 。所以我决定自己重写一个spi内核模块ko驱动 。
非常幸运 , 在海思SDK的 drv.tgz\drv\extdrv\ssp-st7789 中正好有源码 , 连芯片型号都一样,基于这个代码做一些简单修改就能用 。
经过我的一番修改,实际测试下来发现这个ko模块的性能比linux内核spi的性能有一点点提升 , 延时从1.656us降低到了1.6us以内 , 基本等于没有优化,这里就不放截图了 。
这个代码就比较简单 , 读起来也不费劲 , 大概看了一下还是能找到一点优化的地方 , 注意看

推荐阅读