☰
LiteOS驱动SSD1331 RGB888 OLED:从SPI初始化到共享内存传帧
2026/10/17 10:36:00 网站建设 项目流程

这个系列笔记写到第7篇了。前几篇把 HI3556V200 的 Linux+LiteOS 双系统框架跑起来之后,A7 核上的 Linux 负责网络、文件、业务逻辑,M7 核上的 LiteOS 负责实时采集和外设响应,两边通过共享内存通信。这篇要解决的是显示输出:让 LiteOS 侧驱动一块 RGB888 格式的 OLED 屏幕,把 Linux 侧的业务数据显示出来。

说实话,一开始我也纠结过:为什么显示这件事非要放在 LiteOS 侧做,Linux 上驱动屏幕不是更顺手吗?真正把双系统方案跑起来之后才明白,这个选择背后有很实际的理由。这篇文章会从头梳理一遍,从选屏思路、LiteOS 侧的 SPI 初始化、SSD1331 的 RGB888 刷屏机制,到双系统共享内存传帧的完整链路,最后把调试中踩过的坑也一并列出来。手里正在做 HI3556V200、或者类似 A7+M7 双核异构方案的开发者,可以拿这份记录当参考。

1. 这块屏幕为什么归LiteOS管:双系统外设分工的思路

1.1 双核异构不是"抢资源",而是外设按核归属

HI3556V200 里的 A7 和 M7 是典型的 AMP(非对称多处理)架构,两个核跑各自的操作系统,各自执行任务。Linux 负责协议栈、文件系统、上层业务逻辑这些"重活";LiteOS 负责中断响应、数据采集、外设驱动这些"实时活"。AMP 架构下,一个外设到底归哪个核管,在启动阶段就要确定下来,不存在运行期两边互相争抢的情况。

这个归属关系反映到代码上,就是"谁初始化,谁使用"。外设的寄存器物理地址是固定的,A7 和 M7 都能访问,但如果你让两个核同时操作同一个 SPI 控制器,理论层面就可能出现配置互相覆盖的问题。所以初期规划时就要把外设分好:SPI、GPIO、屏幕这类对时序敏感的外设,我直接划给 M7 核的 LiteOS;网络、存储这类依赖 Linux 生态的,留在 A7 核。

显示设备划给 M7 核还有一个隐藏好处:摄像头采集的数据可以就近处理。M7 从 sensor 拿到图像数据之后,可以抽帧、缩放、叠加一些状态信息,然后直接往屏幕刷,中间完全不需要经过 Linux 的系统调用和调度器。

1.2 显示任务放在M7核的实际收益和边界

把显示放 M7 核,最直接的收益是刷新率稳定。Linux 侧驱动屏幕时,一帧数据从应用层走到驱动层,经过系统调用、VFS、驱动框架、中断处理,路径长不说,还容易被其他进程抢占,帧与帧之间间隔忽长忽短,肉眼可见就是卡顿。M7 核上跑 LiteOS,任务优先级是自己定的,SPI 刷屏任务设成高优先级,DMA 搬运数据时 CPU 基本不参与,屏幕刷新节奏就非常稳定。

第二个收益是响应速度快。屏幕要切换页面、刷新菜单项时,往往和按键中断强相关。按键中断直接进 M7,LiteOS 中断处理里把标志位置上,刷屏任务立刻就能响应,延迟是微秒到几十微秒级别。如果走 Linux 侧,中断延迟加调度延迟,手感完全不一样。

不过也要说清楚边界:M7 核不是万能的,别什么显示需求都往上放。如果要跑 Qt、跑复杂的 GUI 动画、或者需要图形加速,那还是得靠 A7 侧 Linux 的高性能计算和 GPU 能力。M7 侧适合的是帧率要求不高、内容相对固定、追求稳定和实时的场景,比如设备状态屏、仪表盘、监控参数显示这类。

2. 选屏与数据通路:为什么走SPI接口的RGB888 OLED

2.1 SPI屏对比MIPI屏:M7侧驱动成本的现实考量

标题里写了 RGB888 OLED,这个选择本身就带信息量。市面上 RGB888 的 OLED 屏主要分两类:一类是 MIPI DSI 接口的,通常来自手机屏供应链,分辨率高、色彩好,但要在 M7 侧驱动起来非常痛苦;另一类是 SPI 接口的小尺寸 OLED,典型代表就是 SSD1331 这颗驱动芯片做的屏,分辨率 96x64,传输 RGB888 像素数据,驱动逻辑简单,特别适合双系统学习项目。

MIPI DSI 屏为什么不适合放在这个场景?MIPI DSI 有 D-PHY 物理层协议,需要配置 lane 数量、差分信号参数、时钟频率,还要区分 command mode 和 video mode 两种工作方式。在 Linux 侧,这些都有现成的驱动框架帮你处理;但在 LiteOS 侧,没有现成框架,基本等于从零写一个 MIPI DSI 控制器驱动,光是 D-PHY 的时序调试就够折腾好几天。

SPI 屏就好办多了。标准四线 SPI(SCLK、MOSI、CS、DC),加一根 RST,最多再加一根 BL。LiteOS 侧只需要完成两件事:配置 SPI 控制器,配置 GPIO。剩下的就是把像素数据按协议送出去。对学习双系统驱动外设来说,这是性价比最高的路径。

2.2 SSD1331的RGB888:18bit色深的24bit传输协议

SSD1331 这颗驱动 IC 常被叫作 RGB OLED 驱动,但严格说它内部显示 RAM 是每像素 18bit,也就是 RGB666,红绿蓝各 6bit。那为什么大家都说是 RGB888?因为它的数据接口传输格式就是每像素 24bit,每个颜色通道占用 8bit,有效数据取高 6 位,剩余低位忽略。

这个细节很重要,直接影响你对"RGB888 对接"的理解。我用的屏是 96x64 分辨率,全屏一帧数据量是 96 x 64 x 3 = 18432 字节。这 18432 字节就是我们常说的 RGB888 帧缓冲,哪怕屏幕实际上只有 26 万色,数据规模也必须按 24bpp 算,否则像素会错位。

明白这层关系,后面写驱动时思路就清晰了:像素格式转换要做,但不用做太复杂——RGB888 直接可以送,RGB565 需要展开成 RGB888 才能送,或者让 SSD1331 处于 16bit 传输模式。

2.3 整条数据链路怎么走:从A7业务逻辑到M7刷屏

整体数据链路是这样的:A7 侧 Linux 的业务逻辑生成要显示的内容,比如传感器数值、开关状态、心跳标识,把内容渲染成 RGB888 像素数据,写入一块预留的物理内存(共享内存);然后置一个"有新帧"的标志位。M7 侧 LiteOS 轮询到这个标志位后,把共享内存里的 RGB888 数据通过 SPI 控制器搬运到屏幕驱动 IC,完成刷新,最后清掉标志位。

这条链路里,A7 侧完全不关心屏幕型号、接口类型、SPI 速率,它只负责往共享内存里写数据和置标志。M7 侧也不关心业务逻辑,只负责把共享内存里的字节流按屏幕时序发出去。这种解耦方式在双系统开发里非常实用,后面换屏幕、换接口,Linux 侧代码基本不用动。

3. LiteOS侧SPI初始化:引脚复用、时钟极性和控制器参数

3.1 引脚复用:在LiteOS里没有设备树,直接写寄存器

在 Linux 侧配置引脚复用,改设备树(Device Tree)就行,比如 pinctrl 节点里指定 function。但 LiteOS 侧没有设备树这套机制,一切靠直接操作寄存器。我一开始习惯性去找设备树的配置,后来才反应过来,在 M7 核上写驱动,最底层的寄存器操作是绕不开的。

具体做法是查芯片手册的引脚复用章节,找到 SPI0 外设对应的 SCLK、MOSI 引脚,把它们的复用寄存器从默认的 GPIO 功能改成 SPI 功能。同时 DC、CS、RST 这些引脚保持 GPIO 输出模式。这一步非常容易漏,漏了之后的现象就是屏幕完全没反应,因为 SCLK 和 MOSI 根本没有连到 SPI 控制器上。

有个小提示:LiteOS 侧访问这些外设寄存器用的是物理地址,和 Linux 侧通过 MMU 映射后的虚拟地址不同。我建议在一开始就把所有用到的外设基地址定义成宏,集中放在一个头文件里,避免代码里到处写魔法数字。后面维护起来会轻松很多。

3.2 SPI控制器四种模式的选择:Mode 0还是Mode 3

SPI 通信有四种模式,区别在于时钟极性(CPOL)和时钟相位(CPHA)的组合。SSD1331 数据手册里明确支持 Mode 0(CPOL=0,CPHA=0)和 Mode 3(CPOL=1,CPHA=1)两种模式,大多数现成的模块默认按 Mode 0 设计。

我在 LiteOS 里配置 SPI 控制器时,先用 Mode 0,采样沿和数据变化沿的关系最直观:空闲时时钟为低,第一个边沿采样数据。如果你的模块怎么调都不出数据,可以试试 Mode 3,这算是一个快速排查手段。不过记得换模式时要同步检查模块原理图的接法,有些模块已经把 DC 引脚和 CS 引脚上拉到固定电平,只能配合一种模式工作。

SPI 速率方面,SSD1331 在 3.3V 供电下通常能跑到 10MHz 以上,但实测中 8MHz 是最稳妥的起步值。速率太低刷屏会明显变慢,速率太高可能因为信号完整性出问题导致花屏。我的做法是先按 8MHz 把整个链路调通,然后再逐步提速,每次提 2MHz,看屏幕有没有噪点。

3.3 CS/DC/RST这些控制脚的处理时序

SPI 屏的驱动不只是 SPI 数据线的事,CS、DC、RST 这三根 GPIO 的时序同样关键。CS 是片选,低有效,每次传输前拉低,传输完拉高。DC 是命令/数据选择线:DC 为低时,SPI 总线上的字节被解释为命令;DC 为高时,被解释为像素数据或参数数据。这个逻辑在刷屏时用得最频繁,写入像素数据前要确保 DC 已经拉高。

RST 是复位脚,上电后需要给一个低脉冲完成硬件复位。规范做法是:拉低至少 20 微秒,再拉高,然后等待 20 毫秒左右让驱动 IC 内部状态稳定,之后才能发初始化命令。有几次屏幕不亮,排查下来就是 RST 低电平时间太短,IC 没完成复位。

还有一根 BL 脚,在一些模块上控制驱动电源,实质是 OLED 的 VCC 使能。如果模块上 BL 默认没接上拉,你得手动拉高才有画面。这类"无声无息"的脚最容易让人走弯路,调试 OLED 不显示时,除了看初始化代码,最好用万用表量一下 BL 和 VCC 的实际电平。

4. SSD1331初始化序列与RGB888窗口刷新机制

4.1 初始化命令序列:每条命令是干什么的

SSD1331 的初始化命令网上有很多版本,不同模块由于 PCB 布局和 VCC 接法不同,参数会略有差异。我没法给你一个"万能数组",但可以把命令的作用讲清楚,这样你拿到任何模块,都能根据数据手册自己调整。

我用的初始化序列大致是这样:

static const uint8_t oled_init_cmds[] = { 0xFD, 0x12, // 解锁 OLED 驱动 IC,默认是锁定状态,不先解锁后面命令无效 0xAE, // 关闭显示,初始化过程中先让屏幕黑着 0xA0, 0x72, // 设置重映射,RGB 顺序、水平地址递增 0xA1, 0x00, // 显示起始行 0xA2, 0x00, // 显示偏移 0xA8, 0x3F, // 多路复用比,1/64 duty 0xAD, 0x8E, // 主配置,选择外部 VCC 0xB0, 0x0B, // 省电模式配置 0xB1, 0x31, // 相位 1/2 周期调整 0xB3, 0xF0, // 显示时钟分频 / 振荡器频率 0xBB, 0x3A, // 预充电电平 0xBE, 0x3E, // VCOMH 电平,这个值影响对比度 0x87, 0x06, // 主电流控制 0x81, 0x91, // 对比度 A 0x82, 0x50, // 对比度 B 0x83, 0x7D, // 对比度 C 0x8A, 0x64, // 预充电速度 0x8B, 0x78, // 预充电电压 0x8C, 0x64, // VCOMH 取消选择电平 0xA4, // 正常显示(非反色) 0xAF // 打开显示,这一步之前屏幕一直是黑的 };

需要特别留意的两条:一是 0xFD 解锁,漏了这条,后面所有命令都会被忽略,屏幕永远黑着;二是 0xAF 显示使能必须放在最后,如果你提前开了显示,初始化过程中可能出现花屏闪动,虽然最终效果不受影响,但在调试时容易误判。

0xAD 这条命令的参数要按模块实际供电方式来选。模块 VCC 由外部电源直接供给,用 0x8E;如果模块内部有升压电路给 OLED 供电,那要用 0x8A 这类内部 VCC 的配置。参数选错通常不会烧屏,但亮度会异常,或者显示非常暗淡。

4.2 窗口设置与像素写入:0x15/0x75/0x5C的配合

SSD1331 的刷屏思路跟很多显示驱动一样,先设窗口,再往窗口里填数据。窗口由两条命令确定:0x15 设置列地址范围,0x75 设置行地址范围。参数分别是两个字节,依次是起始地址和结束地址,比如全屏刷新就写 0x15 0x00 0x5F、0x75 0x00 0x3F(96 列、64 行)。

窗口设好之后,发 0x5C 命令进入写显存模式,之后所有 SPI 数据都会按像素顺序写入显存。数据顺序由重映射命令(0xA0)决定,常见配置下是先从左到右、再从上到下,RGB 三个字节连续排列。

代码实现大概是这样:

static void oled_set_window(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) { uint8_t cmd[6] = { 0x15, x0, x1, 0x75, y0, y1 }; oled_write_cmd_buf(cmd, sizeof(cmd)); } static void oled_flush_rgb888(const uint8_t *buf, uint16_t w, uint16_t h) { oled_set_window(0, 0, w - 1, h - 1); oled_dc_high(); // DC 拉高,进入数据模式 spi_write_buf(SPI0, buf, (uint32_t)w * h * 3); }

这里有个容易被忽略的细节:窗口设置的结束地址虽然是"包含"的(0x00 到 0x5F 实际是 96 个地址),但如果你只刷局部区域,必须确认起始和结束地址都设置正确,否则像素会写到窗口外面去,显示内容整体错位。

4.3 RGB565转RGB888:跨系统格式转换的取舍

实际项目中,Linux 侧生成的业务数据往往不是 RGB888,而是更省内存的 RGB565。比如 framebuffer 默认可能是 16bpp,一个像素两个字节。RGB565 转 RGB888 的经典做法是把 5bit/6bit/5bit 扩展到 8bit,可以直接移位加低位复制,也可以用查表法。

查表法更实用,尤其当颜色变化不频繁时,初始化时把 32 项和 64 项映射表预先算好,转换就是三次查表加三次移位:

static uint8_t r5_table[32], g6_table[64], b5_table[32]; static void init_color_tables(void) { for (int i = 0; i < 32; i++) { r5_table[i] = (i * 255 + 15) / 31; b5_table[i] = (i * 255 + 15) / 31; } for (int i = 0; i < 64; i++) { g6_table[i] = (i * 255 + 31) / 63; } } static void rgb565_to_rgb888(uint16_t rgb565, uint8_t *r, uint8_t *g, uint8_t *b) { *r = r5_table[(rgb565 >> 11) & 0x1F]; *g = g6_table[(rgb565 >> 5) & 0x3F]; *b = b5_table[rgb565 & 0x1F]; }

如果你的 Linux 侧数据已经是 RGB888,这步转换就可以直接省掉,共享内存里放什么,M7 侧就搬什么。我的建议是:业务侧尽可能输出 RGB888,省去 M7 侧的转换开销。只有内存带宽吃紧时才考虑 RGB565 压缩存储。

5. Linux到LiteOS的传帧链路:共享内存预留与刷新标志

5.1 预留物理内存:设备树reserved-memory的配置要点

双系统之间传帧数据,最直接的方式是共享物理内存。A7 侧跑着 Linux,有 MMU,访问的是虚拟地址;M7 侧跑着 LiteOS,没有 MMU,直接访问物理地址。所以这块共享内存必须是一段物理地址连续的、在 Linux 里被预留出来的区域,这样才能确保两边看到的是同一段数据。

前端时间我在设备树里预留内存的做法是:在 reserved-memory 节点里划出一块区域,大小给 1MB 足够,96x64x3 的一帧才 18KB,1MB 去掉帧头协议和可能的双缓冲也绰绰有余。示意配置如下(物理地址按你的内存布局调整):

reserved-memory { #address-cells = <2>; #size-cells = <2>; ranges; oled_shm: oled-shm@9F000000 { compatible = "shared-dma-pool"; reg = <0x0 0x9F000000 0x0 0x100000>; no-map; }; };

关键在于no-map属性:它告诉 Linux 这块内存不要做页表映射,也不要被伙伴系统收回,Linux 侧使用它时需要通过内核提供的接口去映射访问。M7 侧则直接把 0x9F000000 这个物理地址映射成自己的帧缓冲地址。

5.2 帧头协议与dirty标志:先跑通轮询再谈中断

共享内存里面不能只放像素数据,还得有一小段控制结构,用来同步状态。我自定义了一个简单的帧头协议,放在共享内存起始位置:

typedef struct { uint32_t magic; // 固定值,比如 0x4F4C4544 ("OLED" 的 ASCII) uint32_t dirty; // 1 表示有新帧待刷新,0 表示已经刷完 uint32_t width; // 帧宽 uint32_t height; // 帧高 uint32_t fmt; // 像素格式,0 表示 RGB888 uint8_t data[]; // 像素数据从这里开始 } oled_frame_t;

M7 侧的 LiteOS 任务就循环检查这个结构里的 dirty 标志。逻辑很简单:读到 dirty 为 1,就把 data 区域的数据通过 SPI 刷到屏幕,刷完后把 dirty 清 0。这里要注意,M7 侧清 0 之后,Linux 侧才能安全地写入下一帧,所以实际上是"生产-消费"模型,dev 侧是生产者,M7 侧是消费者。

轮询方式简单可靠,唯一的缺点是 M7 核会一直空转访问共享内存。我一开始用轮询跑通全流程,验证完显示效果之后再改成中断通知。中断方式就是 Linux 侧写完 dirty 后触发一个 MBOX 中断给 M7,M7 在中断处理里唤醒刷屏任务,省去空转消耗。

5.3 缓存一致性问题:M7读到旧数据的根因

这个坑我调了整整一天,现象非常诡异:Linux 侧明明已经把一帧新数据写进共享内存,dirty 也置 1 了,M7 侧读到的却是旧数据,或者读到一半的数据。一开始怀疑是 SPI 传输问题,后来才发现是 CPU 缓存不一致。

A7 核有 L1/L2 Cache,Linux 侧通过虚拟地址写共享内存时,数据可能还停留在 cache 里,没有真正落到物理内存。M7 核直接访问物理地址,自然读到的是过期的旧内容。解法有两个方向:一是把这块共享内存映射成不可缓存的(在设备树里配nocache之类属性,或者驱动里用ioremap_wc映射),二是每次写入后主动执行 cache clean 操作,确保数据落回物理内存。

推荐方案是直接配成不可缓存,省心。缺点是写入性能略降,但对 18KB 一帧的数据量来说完全无感。如果用可缓存内存,每次置 dirty 之前都要 dma_cache_sync 或者 flush,链路里多一步,容易漏,更容易出并发问题。

6. 实测踩坑记录:花屏、不亮和刷新撕裂逐个排查

6.1 屏幕能亮但花屏:先从SPI时序和re map查起

花屏是最常见的现象,具体表现是屏幕有背光(OLED 没有背光,但会显示雪花状彩色噪点),能感觉到屏幕在工作,但图像完全不对。我在调试中遇到的花屏原因有两个:SPI 模式配置不对,和重映射参数不对。

SPI 模式不对时的典型表现是,初始化命令"看起来"发出去了,但屏幕显示的内容完全是乱的,连纯色测试都无法显示。按 Mode 0 配置后问题解决。如果你的场景用 Mode 3,注意模块手册的说明。

重映射参数不对的表现更细腻:颜色顺序错乱,比如红色区域显示成蓝色,或者刷新方向反了(从右到左、从下到上)。0xA0 命令的每一位分别控制 RGB 顺序、地址增量方向、列/行扫描方向。我用的 0x72 是常见值,但不同模块可能要求不同值,出现颜色错乱时,优先调整这一位。

另外花屏还要检查是不是 SPI 速率太快。我在 16MHz 下看到过细微噪点,降回 12MHz 就正常了,这很可能是 PCB 走线信号完整性问题,跟软件关系不大。

6.2 初始化后不亮:复位时序和电源使能顺序

屏幕完全黑,一点反应没有,这时先把目光从代码转到硬件时序上。我的排查顺序是这样:先确认 VCC 电压,量 BL 脚电平,再看 RST 复位脉冲宽度,最后查初始化数组是不是漏命令。

有一次屏幕不亮的根因就是 RST 低电平时间只有几微秒,驱动 IC 根本没完成复位,后面发什么命令都不响应。改成拉低 20 微秒再拉高,问题立刻消失。还有一次是 BL 脚没拉高,模块的电源回路没建立,这属于硬件上"无声无息"的问题,软件层面上初始化代码怎么调都没用。

另外,如果你用的是 0xAD 0x8E(外部 VCC)配置,但模块实际是内部升压供电,也可能表现为亮度极低或完全不亮。这种情况把 0xAD 的参数换成内部 VCC 的配置试试。调试屏幕不亮时,强烈建议先写一个纯色全屏填充程序,依次填红、绿、蓝,哪个颜色不正常就说明对应通道或供给有问题。

6.3 刷新撕帧与共享内存旧数据:两个隐蔽问题

刷新撕帧的现象是屏幕上画面不稳定,上半屏是新帧,下半屏是旧帧,滚动起来像撕裂。原因在于 M7 侧刷屏期间,Linux 侧已经开始写入下一帧数据了。解决办法是双缓冲:Linux 侧轮流写两块缓冲,M7 侧刷完一块再切下一块,中间加一个"正在刷屏"的标志位,保证不会一边刷一边改。

共享内存读到旧数据的问题前面已经说过根因是缓存一致性,这里补充一个排查技巧:在帧头里放一个自增的帧序号(frame_id),M7 侧打印每次读到的 frame_id,如果发现连续读到相同或乱序的 frame_id,基本可以断定是缓存或同步问题,而 SPI 本身没问题。

我还遇到过一个跟局部刷新有关的错位问题。全屏刷新一切正常,一旦只刷某个窗口,画面就出现错位。后来发现是窗口设置命令里行列范围搞反了,SSD1331 要求先设列地址(0x15)再设行地址(0x75),顺序不能颠倒,两个命令之间的参数也不能越界。

下面用一个表把问题和解决方向汇总一下:

现象根因排查方向
全黑无反应复位时序不对 / BL 未拉高 / 未解锁检查 RST 脉冲、BL 电平、0xFD 解锁命令
花屏噪点SPI 模式错误 / 速率过高切 Mode 0/3,降低 SPI 时钟
颜色错乱重映射参数不对调整 0xA0 命令的 RGB 顺序位
画面撕裂刷屏期间数据被覆盖上双缓冲或加"正在刷屏"标志
读到旧帧CPU 缓存不一致共享内存配成 non-cacheable
局部刷新错位窗口行列范围/命令顺序错误核对 0x15、0x75 参数顺序

7. 刷屏性能实测与后续优化扩展

7.1 一帧RGB888全刷的实际耗时和帧率

整个链路跑通之后,我专门测了一帧全屏刷新的耗时。96x64 的 RGB888 数据量是 18432 字节,SPI 时钟 8MHz 时理论传输时间是 18432 x 8 / 8000000 ≈ 18.4 毫秒。加上初始化窗口命令、片选切换、命令传输这些开销,实测一帧大约 30 毫秒左右,换算下来接近 30fps。

对于这个分辨率的 OLED 屏,30fps 已经能满足大部分状态显示需求,看视频会吃力,但显示数字、图标、菜单绰绰有余。如果后续要提升帧率,优先做两件事:一是把 SPI 时钟提到 12MHz 或 16MHz,二是开启 DMA 传输,让 M7 核在 SPI 搬运期间去处理其他任务,而不是逐字节等待 FIFO。

我用 DMA 之后,M7 核的 CPU 占用大幅下降,刷屏任务基本只做"发起传输、等传输完成中断",瞬间 CPU 占用从接近满载降到可以忽略的水平。这让 M7 核有余力同时处理 sensor 数据采集和简单的状态解析。

7.2 可以继续做的优化:DMA、双缓冲、局部刷新

DMA 是首选的优化方向。SPI DMA 的配置在芯片手册里有专门的描述符结构,LiteOS 侧直接操作 DMA 控制器寄存器就行。配置好源地址(共享内存物理地址)、目的地址(SPI TX 寄存器地址)、传输长度之后,触发一次传输,剩下就是等待中断。

双缓冲也值得做。共享内存里划分两个帧缓冲,Linux 侧写 A 时 M7 刷 B,互换交替。这样可以消灭刷屏等待时间,让 Linux 侧永远不用等 M7 刷完才能写下一帧,整体数据吞吐量翻倍。

局部刷新的价值在于降低传输数据量。如果业务场景是参数一栏栏更新,没必要每次全屏重刷。用 SSD1331 的窗口机制,只刷新变化区域对应的窗口即可。比如温度数值从 25.4 变成 25.5,只需要刷新数字所在的几十个像素,传输量从 18KB 降到几百字节,瞬时刷新率可以很高。

局部刷新的代价是要做脏矩形追踪,计算哪些区域需要刷新,复杂度会高一些。我的建议是先把全屏刷新链路跑稳,再加局部刷新,不要一开始就追求复杂方案。

最后分享一个我整个调试过程里最深刻的体会:双系统外设联调,调试顺序非常重要。先把屏幕在纯 LiteOS 单机环境下跑通,脱离 Linux、脱离共享内存,验证屏幕本身没问题之后,再接双系统传帧。如果直接上来搞共享内存传数据,出了问题你会分不清是 SPI 初始化问题、屏幕参数问题,还是共享内存同步问题。我一个晚上调花屏没进展,第二天把流程拆开,纯 LiteOS 环境下用固定测试图案刷屏,一下子就锁定了问题范围,两个多小时就解决了。这个顺序建议后面的开发者一开始就遵守。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询