| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 嵌入式 -> 海思3516系列芯片SPI速率慢问题深入研究与优化(基于PL022 SPI 控制器) -> 正文阅读 |
|
[嵌入式]海思3516系列芯片SPI速率慢问题深入研究与优化(基于PL022 SPI 控制器) |
海思3516系列芯片SPI速率慢问题深入分析与优化(基于PL022 SPI 控制器) 我在某个海思主控的项目中需要使用SPI接口来驱动一块液晶屏,液晶屏主控为 st7789,分辨率 240x240,图像格式 RGB565。 查阅海思相关手册可知,Hi3516EV200 的 SPI 最高速率为 50MHz,理论上每秒钟可以发送 50M/8=6.25MB 数据。假设我需要在屏幕上以30fps的速率全屏实时显示摄像头的预览画面,每秒的数据量为 上面这些都是我在写代码前的理论分析,真实效果当然需要写程序简单测试一下。测试代码内容大致如下:使用原生linux提供的 SPI 接口,即 实际测试结果,每秒大约5次!肉眼可见的慢!能很明显看到刷屏的时候是从上到下覆盖过来的! 这么慢的速率就算以240*180的分辨率来刷新预览画面也只能达到7、8帧的水平,何况cpu使用率是100%,其他业务也没办法正常运行,所以spi的速率必须优化。 第一步当然是用逻辑分析仪或示波器抓实际波形,观察是时钟信号没有达到50MHz,还是有其他地方浪费时间。对于逻辑分析仪而言,要考虑采样率是否足够,例如逻辑分析仪的采样率是100MHz,理论上可以采集50MHz的信号,实际采集50MHz的信号很可能采集不准;示波器也一样,示波器需要考虑的是带宽,一般入门级示波器都有100MHz带宽,问题不大。对了,别拿DIY的示波器来捣乱。 这张图就是我通过示波器抓到的海思 SPI 时钟线上的波形,问题非常明显,发送的两个字节之间间隔了 1.656us!开发过任意单片机的spi并实际抓过波形的朋友应该都知道,spi的时钟几乎是连续,正常情况下绝对不可能间隔这么长。 再检查一下spi的时钟有没有达到理论的50MHz,8个clk共计160ns,没问题,达到了理论速率。 题外话:为什么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) 非常幸运,在海思SDK的 经过我的一番修改,实际测试下来发现这个ko模块的性能比linux内核spi的性能有一点点提升,延时从1.656us降低到了1.6us以内,基本等于没有优化,这里就不放截图了。 这个代码就比较简单,读起来也不费劲,大概看了一下还是能找到一点优化的地方,注意看
在前面的代码中设置过CS片选信号由spi使能信号控制,也就是说,这段代码每次写一个字节都要拉一次CS信号,效率比较低,我将 spi_enable 和 spi_disable 提出来后再次进行测试,速度确实有一定提升,但提升幅度仍然不大,只有大概几百ns的水平,优化效果仍然不理想。 这段代码只剩下两个函数了,ssp_writew 是写寄存器,根本不能优化,只能想办法优化 hi_spi_check_timeout,来看一下这个函数的实现
逻辑非常简单,不停地读取发送FIFO空和SPI忙的标志位,延时1us继续读,直到发送完成且SPI空闲。看到这个 udelay(1) 你现在的想法肯定和我当时的想法一样:第一次读取发现寄存器没有置位,延时1us,第二次读取寄存器置位,退出循环,现在的发送间隔是1.6us,去掉这个延时尽快读取寄存器,应该直接能优化到0.6us以内。 想得美!实际测试去掉这个 udelay(1) 以后优化效果确实挺明显的,延时直接缩短到 1.2us 左右,但是这个延时还是太长了。 现在再来看这个函数,就剩一个读寄存器了,为了保证可靠传输,读标志位肯定不能去掉,这已经最简单了,还能怎么优化呢?我们重新梳理一下:去掉 udelay(1) 后间隔缩短到 1.2us 左右,说明这里循环读了很多次寄存器,不然怎么还有这么长的延时?那要不就加打印看看这里到底循环读取了几次?来来来,竞猜一下这里到底循环了几次?十次以内?百次以内?还是千次以内?答案是一次!没错只有一次! 1次这个答案可以说即在意料之外也在意料之中。 接下赖重点思考这个问题:为什么 ssp_readw(SSP_SR,value) 这样一个简单的读寄存器操作要 1.2us 之久? linux 不同于单片机裸机程序那样可以直接访问寄存器,如果需要读写物理寄存器,在内核态使用 来看一下实际读写寄存器的这两个接口,很简单就是直接读写某个地址处的数据(前提是这个地址必须经过映射)。
首先可以明确一点,读写寄存器的操作必然会经过MMU。 在 u-boot 下可以直接读写物理寄存器,应该不需要这么久,几个CLK就可以完成吧?这一点我没有验证过,有测试过的朋友欢迎分享。 总之,耗时的地方找到了,想办法优化吧。我这里有两种优化思路:
目前我采用的就是第二种方法,第一种方法就留给大家验证并开发吧(其实就是我懒)。
这个延迟函数一定要根据编译器、CPU主频、SPI时钟频率等实际测量后进行调整。经过我的反复调整和测量,最终把循环计数设置为了30,来看一下示波器抓到的波形 间隔 65ns,也就是每字节耗时 225us,大约相当于 36MHz 的SPI时钟频率。 改成其他值行不行呢?这是我的测量结果
前面说了,延时函数一定要根据实际情况进行修改,确保每字节之间有一定间隔。假设你换了海思的另外一款芯片,或者使用了其他基于 ssp-pl022 控制器的芯片,也遇到了类似的 SPI 速率低的问题,但手头没有示波器进行测量怎么办?假设你所使用的芯片提供了精确到10ns的延时函数,直接拿来用就行。没有这样的函数没关系,可以做一个简单的估算,估算结果与实际肯定有偏差,但不管怎么说这个数值也算比较靠谱的。我们来一步一步分析这个延时函数如何实现。 首先是 确定好你所使用的编译器和编译优化参数,有的芯片厂商会提供多个版本的编译器,或者后来编译器更新了,编译器不同可能会导致代码行为不同,编译优化参数不同也会导致代码行为不同,假设最终发布代码使用 确定好你的延时函数的循环怎么写,包括但不限于
无论怎么写都能达到延时的作用,有的编译器可能非常聪明,发现循环中什么都没做,最终这4种写法都被优化成了相同的汇编代码,但有的编译器可能不会,总之你不能保证编译器会把他们优化成相同的汇编代码,所以确定了延时函数的写法以后在之后的开发过程中不要更改。 下一步将循环计数设置为比较大的数,例如十万,一百万,执行这个函数并计算耗时,不论是用秒表,还是用 gettimeofday,或者是 time 命令,总之最终目的是算出1个循环耗时多少。假设我测量出循环百万次耗时5.3ms,那么循环一次耗时就是5.3ns。向上取整按一次循环耗时6ns计算,为什么这样做?自己想。 假设 SPI 的时钟速率是50MHz,发送数据位宽是 8bit,则发送1次耗时 当然还有更靠谱一点的估算方法。目前我的延时函数及对应的汇编代码如下:
循环体对应的就是这5句
我所使用的芯片采用Cortex-A7架构,我实在没找到它的指令周期的文档,这里用Cortex-A9的替代一下,ARM官网文档如下 https://developer.arm.com/documentation/ddi0388/f/Instruction-Cycle-Timings?lang=en ,跳转指令的周期是不确定的,其他4个指令的都是单周期指令,循环中大多情况都是跳转,只有最后一次是不跳转,我们也按单周期计算好了。由此可知,循环一次需要5个周期,CPU的主频是900MHz,假设CPU主频固定,不超频,也不降频进入低功耗模式,那么循环一次耗时 好了,到此为止开发过程中遇到的问题还有我自己的疑惑都讲完了,有问题欢迎大家讨论。 如果你有朋友正好在海思工作,请将本文转发给他,我不知道海思其他产品线的芯片会不会也有这个问题。我这样写的代码多少有些随意,希望海思官方能优化一下 SPI 的速率问题。 |
|
嵌入式 最新文章 |
基于高精度单片机开发红外测温仪方案 |
89C51单片机与DAC0832 |
基于51单片机宠物自动投料喂食器控制系统仿 |
《痞子衡嵌入式半月刊》 第 68 期 |
多思计组实验实验七 简单模型机实验 |
CSC7720 |
启明智显分享| ESP32学习笔记参考--PWM(脉冲 |
STM32初探 |
STM32 总结 |
【STM32】CubeMX例程四---定时器中断(附工 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/25 20:37:09- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |