RK3568 SPI LCD驱动实战:FrameBuffer模式与调试全解析
2026/9/18 12:48:55 网站建设 项目流程

从RK3568上接一块SPI小屏这件事,网上资料很零散,大多是拿fbtft改一改、能亮就算完。但真到产品化阶段,你会遇到一堆绕不开的问题:初始化时序不对导致花屏、片选策略影响命令发送、刷新帧率上不去、竖屏改横屏不知道改哪里。这篇文章把我自己在RK3568上从零驱动一块ST7789V SPI LCD的完整过程写出来,重点讲FrameBuffer模式下的驱动架构、设备树配置、刷新机制和实际调试中踩过的坑,给正在做类似事情的嵌入式驱动开发工程师一个可以直接参考的路径。

1. 为什么在RK3568上驱动SPI LCD要选择FrameBuffer模式

1.1 RK3568显示链路里没有SPI LCD的位置

RK3568的显示资源其实很丰富,VOP(Video Output Processor)后面挂了MIPI DSI、LVDS、RGB/eDP这些标准显示接口,系统正常显示链路走的是DRM/KMS框架。但你拿到的SPI小屏完全不属于这一套体系,它没有标准视频时序,没有pixel clock,也不需要HSYNC/VSYNC,它就是一个挂在SPI总线上的从设备,需要主机持续往它内部的GRAM里写像素数据。VOP根本不知道这种屏的存在,所以不能像处理RGB屏那样直接接上就完事。

这时候摆在面前的路有三条:一是用内核自带的fbtft框架,二是基于FrameBuffer自己写一个spi_driver,三是强行用DRM的panel-bridge去包一层虚拟显示接口。第三条路工作量最大,而且对DRM内部机制不熟的人很容易被绕晕,除非团队里有人专门搞过,否则不建议碰。

1.2 FrameBuffer模式与DRM/KMS方案的选择逻辑

很多新入行的驱动工程师一听说Linux显示就应该用DRM,看到FrameBuffer就觉得是老古董,这种判断在标准RGB/LVDS屏场景下是正确的,但放在SPI LCD上就不太合适。

我对比一下这两种思路以及fbtft方案的实际体验,用一个表格看更清楚:

方案实现复杂度可控性适用场景
自写FrameBuffer驱动高,所有刷新逻辑自己掌控产品定制、低分辨率SPI屏、LVGL界面
fbtft低,但调试成本高低,很多行为被框架锁死快速验证、个人DIY、不追求深度定制
DRM panel-bridge团队有DRM基础,必须统一走DRM框架的场景

我自己最终选的是自写FrameBuffer驱动。原因很简单,fbtft在一定程度上绑定老内核的初始化路径和一些历史包袱,在RK3568这种比较新的内核上要打补丁,而且我想实现局部刷新、亮度调节、MADCTL旋转这些产品化功能,在fbtft上改远不如自己掌控来得直接。

FrameBuffer模式的核心思想是:把显存看作一块普通内存,用户程序往/dev/fb0里写数据等同于往这块内存写像素,驱动后台用一个刷新机制把内存里的内容搬运到SPI LCD里。LCD显示的是一个静态画面,和VOP定时扫描是两回事。

1.3 这种模式能跑什么应用,不能跑什么应用

搞清楚定位才能选对方案。FrameBuffer模式的SPI LCD适合做这些事:

  • 低分辨率人机界面,比如240x240、320x240的小屏
  • 配合LVGL等GUI库显示菜单、仪表盘、状态信息
  • 调试面板、参数显示、简单的串口屏替代品

不适合做这些事:

  • 视频播放,刷新率上限受SPI总线带宽限制,一般到不了流畅视频的帧率
  • 复杂动画场景,CPU往SPI上持续搬运数据会占用不少算力
  • 需要GPU合成加速的界面,FrameBuffer模式绕开了VOP合成链路,GPU画的画面无法直接命中这块显存

所以做方案选型时,先算清楚这个产品要不要跑动效、视频,再决定是不是用SPI LCD。只是显示静态文字、温湿度、电压电流这类数据,SPI屏方案成本优势非常明显。

2. 先把SPI协议和RK3568 SPI控制器的脾气摸清

2.1 SPI四根线的语义与LCD只收不发的特点

不管接的是ST7789V、ILI9341还是ST7735S,SPI LCD的通信都围绕四根线展开:CLK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。

这里可以类比成对讲机通话:主机按下CS按键表示“我要开始通话了”,CLK相当于节拍器,每打一个拍子双方约定传输一个bit,MOSI是主机说话的内容,MISO是从机回话的内容。LCD屏只需要接收命令和数据,绝大多数情况下不返回数据,所以MISO这根线可以不接,或者留着以后做屏幕ID读取用。

驱动开发中的速率上限由CLK频率决定。RK3568的SPI控制器支持的频率范围很宽,但并不是设备树里写多高就一定能跑多高,还要看PCB走线长度、杜邦线质量、屏幕本身的规格上限。SPI小屏驱动IC通常能跑到30MHz到60MHz,但实际产品里为了信号完整性,我一般控制在20MHz到40MHz之间。

2.2 时钟极性和相位配置错了,黑屏是最轻的后果

SPI协议有四种模式,由CPOL(时钟极性)和CPHA(时钟相位)组合而成。CPOL决定空闲时CLK是高还是低,CPHA决定数据是在时钟上升沿还是下降沿被采样。

大部分SPI TFT LCD驱动IC支持Mode 0(CPOL=0, CPHA=0)或Mode 3(CPOL=1, CPHA=1),具体看数据手册。配置错了最直观的表现就是整个屏幕无显示或者显示乱码,而且这种问题用眼睛看代码很难发现,必须用逻辑分析仪抓波形和手册里的时序图对照。

ST7789V这颗IC我在RK3568上用的是Mode 0,设备树里不需要显式写模式,驱动代码中通过spi_device的mode成员设置:

lcd_spi->mode = SPI_MODE_0;

如果是自己创建spi_device或者使用SPI_DEV_NAME方式匹配,记得在probe里确认这个值,否则默认可能是Mode 0,但如果你的板级配置里被bootloader改过,就会出问题。

2.3 硬件片选和软件片选之争,直接决定你的代码怎么写

这是SPI LCD驱动里最隐蔽的一个坑,值得单独拿出来讲。

RK3568的SPI控制器支持硬件片选,也就是控制器根据spi_message的状态自动拉低或拉高CS引脚。看起来省事,但对LCD驱动来说有一个致命问题:硬件片选在两个spi_transfer之间可能会把CS抬起来一下,即使它们属于同一个spi_message。对多数SPI外设这不是问题,因为它们按字节或按块解析数据,但对LCD来说,CS被认为是“一次事务”的边界,CS抬升了,屏幕驱动IC会认为当前接收过程结束了,这会直接导致命令后面的数据被误解。

举个例子,你想发一个RGB颜色设置命令0x2A,后面跟4字节的窗口坐标数据。如果用硬件片选并且cmd和数据是两次spi_sync调用,CS在两次调用之间必然抬升。屏幕内部状态机很奇怪,有些IC能容忍,有些IC直接废掉整条命令。

解决思路有两个:

  1. 用软件片选,把CS引脚配成普通GPIO,自己控制拉低和拉高,这样可以在连续多次spi_sync之间保持CS始终为低。
  2. 把命令和数据放在同一个spi_message的多个spi_transfer里,但前提是你的SPI控制器在同一个message内部不会抬CS。

我自己实践下来的结论是:用软件片选最稳,而且调试时还能用GPIO手动拉CS来验证时序,对排查问题特别有帮助。设备树里设置cs-gpios属性即可,驱动里的片选操作完全由GPIO子系统接管。

2.4 DC线和RESET、背光引脚的配合时序

SPI LCD除了SPI四根线,通常还有DC(数据/命令选择)、RESET(复位)、BLK(背光)三根控制线。

DC线的作用是告诉屏幕驱动IC,当前SPI总线上传输的字节是命令还是数据。对于ST7789V这类IC,DC为低表示命令,为高表示数据。这个引脚切换的时机非常重要:必须在CS拉低之后、SPI时钟启动之前完成切换,否则第一个字节的采样就错了。

RESET引脚处理也很有讲究。我见过不少人在probe里拉一下RESET就去初始化屏幕,结果屏幕没反应。正确流程一般是:

  • VCC上电稳定后等待至少10ms
  • RESET拉低,保持10ms以上
  • RESET拉高,等待120ms以上
  • 然后发送初始化命令序列

背光引脚最理想是和RESET分开控制,并且不要在上电初始化完成之前点亮背光,否则你会看到屏幕先闪一下白屏,非常影响观感。正确的顺序是:初始化屏幕、清屏、再开背光。

3. 设备树配置:驱动能不能跑起来,第一关在这里

3.1 在RK3568设备树里打开SPI控制器并挂自定义设备

RK3568设备树里SPI控制器节点外设默认状态很可能是disabled,需要手动打开。以SPI2为例,典型配置如下:

&spi2 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&spi2m1_pins>; cs-gpios = <&gpio3 RK_PB0 GPIO_ACTIVE_LOW>; spilcd: spilcd@0 { compatible = "vendor,spi-lcd"; reg = <0>; spi-max-frequency = <40000000>; rotate = <90>; dc-gpio = <&gpio3 RK_PB1 GPIO_ACTIVE_HIGH>; reset-gpio = <&gpio3 RK_PB2 GPIO_ACTIVE_LOW>; backlight-gpio = <&gpio3 RK_PB3 GPIO_ACTIVE_HIGH>; }; };

解释几个关键字段:

  • pinctrl-0指定SPI引脚的复用功能,RK3568同一个SPI控制器可能有多个引脚组,比如spi2m0和spi2m1,要根据原理图选对。
  • cs-gpios指定CS用软件片选,GPIO_ACTIVE_LOW表示片选有效电平是低。
  • compatible是驱动匹配的关键,必须和驱动里的of_match_table一致。
  • spi-max-frequency是SPI总线的最大频率,不是固定频率,驱动里还可以在每次transfer单独设置speed_hz,但不能超过这个值。
  • 自定义的rotate、dc-gpio、reset-gpio、backlight-gpio都是给驱动用的,命名可以自己定,驱动里通过of_property_read_u32和devm_gpiod_get解析。

3.2 pinctrl引脚复用与调试串口冲突的排查

RK3568的引脚复用功能强大但也最容易出错。很多引脚默认被UART占用,比如调试串口通常是UART2,而某些开发板的SPI2引脚恰好和UART2属于同一个引脚组。设备树里如果不把UART2节点disable,就算你在SPI节点里配好了pinctrl,SPI时钟也可能出不来,因为引脚功能已经被占用。

我遇到的一个真实案例:板子启动时内核日志还在正常打印,但SPI引脚就是没有波形。查了半天发现UART2和SPI2共用了部分引脚,UART2设备树节点启用了,pinctrl子系统把引脚占用关系锁死,SPI2的pinctrl申请冲突被忽略。

解决方法是在设备树里把冲突的UART节点改成disabled:

&uart2 { status = "disabled"; };

做这一步的前提是你能接受SPI的功能优先级更高。如果调试串口和SPI冲突,但你的板子还需要串口看日志,那就得重新选SPI控制器或者换引脚组。

3.3 自定义属性:旋转角度、最大SPI频率、亮度等级

设备树里定义私有属性是驱动开发常用的做法,好处是硬件改版时不需要重新编译驱动,改dts就行。

旋转角度我建议直接用度数表示,比如rotate=<0>、<90>、<180>、<270>,驱动probe里根据这个值设置ST7789V的MADCTL寄存器。比驱动里写死成宏要灵活得多。

最大SPI频率用标准的spi-max-frequency即可,不需要自定义。亮度等级如果不是用PWM调光,而是用屏幕内部对比度寄存器或者简单的GPIO占空比,可以在设备树里定义brightness-levels数组,驱动里解析后暴露给用户态。

3.4 验证设备树是否生效的快速方法

配置改完后,怎么确认设备树里生成的spi_device被成功创建了?

开机后执行:

ls /sys/bus/spi/devices/

正常会看到spi2.0这样的目录。然后在/sys/bus/spi/devices/spi2.0/下能看到modalias、of_node等属性。如果没有生成设备节点,大概率是compatible没匹配上,或者父节点status没设为okay。

另外可以加一处调试输出,在驱动的probe函数里打一条printk:

dev_info(&spi->dev, "spi lcd probed, max_speed_hz=%u, mode=0x%x\n", spi->max_speed_hz, spi->mode);

内核日志里看到这行,说明设备树匹配没问题,驱动已经进入probe,接下来才是真正调屏幕的阶段。

4. 驱动代码拆解:从spi_driver注册到像素真正跑到屏上

4.1 显存分配:为什么必须用DMA一致内存

FrameBuffer驱动的核心资源是显存,用户写的像素数据会先落在这块内存里,然后由刷新机制搬运到SPI LCD。显存分配不能用普通kmalloc,因为SPI控制器发起DMA传输时,要求buffer是物理连续的,而且必须保证CPU缓存和DMA看到的数据一致

用dma_alloc_coherent一举两得:

static void *lcd_fb_mem; static dma_addr_t lcd_fb_dma; lcd_fb_mem = dma_alloc_coherent(&spi->dev, xres * yres * 2, &lcd_fb_dma, GFP_KERNEL);

这里申请的是240x240x2字节,约112KB,对应RGB565格式的一整帧。用DMA一致内存后,CPU写入数据通过缓存会被硬件自动处理,不需要每次刷新前手动flush cache。如果图省事用kmalloc,刷新时遇到花屏、残影,很大概率就是cache一致性问题。

4.2 初始化序列:屏幕驱动IC的上电流程图

ST7789V这类IC开机后不会自动进入可显示状态,必须由主机发送一串初始化命令。不同型号的屏初始化序列不一样,但都遵循类似流程:

  • 软件复位(0x01)
  • 退出睡眠模式(0x11)
  • 设置像素格式(0x3A),RGB565时设为0x05
  • 设置扫描方向(0x36),也就是MADCTL
  • 打开显示(0x29)

驱动里我会把初始化命令写成一个表,方便按型号调整:

struct lcd_init_cmd { u8 cmd; u8 len; u8 data[8]; }; static const struct lcd_init_cmd st7789v_init[] = { { 0x01, 0, {} }, // SWRESET { 0x11, 0, {} }, // SLPOUT,退出睡眠 { 0x3A, 1, { 0x05 } }, // COLMOD,RGB565 { 0x36, 1, { 0x00 } }, // MADCTL,方向由rotate决定 { 0x20, 0, {} }, // INVOFF { 0x29, 0, {} }, // DISPON };

命令发送时我封装了两个基础函数,后面所有操作都基于它们。

4.3 维护命令/数据模式,跨过片选细缝

这是整个驱动编码里最需要注意的地方。由于我用了软件片选,所以命令和数据可以分开发送,中间安全地切换DC线:

static void lcd_write_cmd(struct spi_device *spi, u8 cmd) { gpiod_set_value_cansleep(dc_gpio, 0); // DC低,命令模式 gpiod_set_value_cansleep(cs_gpio, 0); // 拉低CS spi_write(spi, &cmd, 1); gpiod_set_value_cansleep(cs_gpio, 1); // 拉高CS } static void lcd_write_data(struct spi_device *spi, const u8 *data, size_t len) { gpiod_set_value_cansleep(dc_gpio, 1); // DC高,数据模式 gpiod_set_value_cansleep(cs_gpio, 0); spi_write(spi, data, len); gpiod_set_value_cansleep(cs_gpio, 1); }

看起来很简单,但要注意几个细节:

  • 必须在spi_write之前把DC切换到位,不能在写操作过程中切换。
  • CS拉低期间不能被打断,所以如果系统里有并发写屏,要给整个CS+DC+SPI发送过程加锁,我用的mutex,因为在工作队列和用户write回调里都可以睡眠。
  • 大块数据传输时,spi_write内部可能被拆成多个transfer,理论上有被并发抢占的风险,加锁可以规避。

如果你的硬件没有把CS配成软件片选,而是硬件CS,那么cmd和data必须放在同一个spi_message里,并且中间不能有让CS抬升的机会。那样代码复杂不少,这也是我坚持软件片选的原因。

4.4 刷新机制:定时整刷与局部刷新两个思路

像素从显存到屏幕的搬运,驱动里可以有多种方式。最简单的是定时整屏刷新:起一个delayed_work,每50ms把整个显存内容通过SPI写到屏幕。

static void lcd_refresh_worker(struct work_struct *work) { /* 把fb显存整体刷新到LCD */ lcd_write_data(spi, lcd_fb_mem, xres * yres * 2); schedule_delayed_work(&refresh_work, msecs_to_jiffies(50)); }

但这种做法在SPI上很浪费,因为240x240x2=115200字节,就算40MHz时钟也要传一阵子。更聪明的做法是局部刷新:检测显存的脏区域,只把变化的那一小块刷过去。

ST7789V支持窗口地址设置,先圈定一个矩形区域,然后只向该区域写像素数据:

static void lcd_set_window(u16 x0, u16 y0, u16 x1, u16 y1) { u8 buf[4]; lcd_write_cmd(0x2A); // CASET,列地址 buf[0] = x0 >> 8; buf[1] = x0 & 0xff; buf[2] = x1 >> 8; buf[3] = x1 & 0xff; lcd_write_data(spi, buf, 4); lcd_write_cmd(0x2B); // RASET,行地址 buf[0] = y0 >> 8; buf[1] = y0 & 0xff; buf[2] = y1 >> 8; buf[3] = y1 & 0xff; lcd_write_data(spi, buf, 4); lcd_write_cmd(0x2C); // RAMWR,开始写GRAM }

有了这个函数,每次刷新只传变化的矩形,处理纯数字界面时效率提升非常明显。比如一个时钟界面,只有中间数字区域变化,刷新数据量可以从115KB降到几KB。

内核的fb_deferred_io机制可以自动帮你检测脏页,但它是基于页面粒度的,对SPI LCD这种按矩形高效刷新的场景,颗粒度不够细。我自己在实际项目中更倾向于在fb_ops的fb_write、fb_imageblit、fb_fillrect回调里做脏矩形标记,然后由后台worker在下一个刷新周期集中处理。这样脏矩形是精确的,效率最高。

5. 实战调试:花屏、黑屏、低帧率问题的完整排查链路

5.1 从我第一次上电就看到随机雪花说起

第一次在RK3568上点亮ST7789V时,屏幕显示的是随机雪花点,没有任何规律。我当时的排查链路是这样的:

第一反应是SPI模式配置问题,于是用逻辑分析仪抓CLK、MOSI、CS、DC四路信号。抓下来发现一个问题:MOSI上确实有数据,但DC线切换时机比我预期的晚,第一个数据字节已经被当成命令发出去了。

原因是我在初始化时先调用了spi_write发送命令字节,然后才去切换DC线,这中间有一段裸奔时间。修复方式就是前面说的,先切DC,再拉CS,再发数据,顺序不能反。

第二件事是检查MADCTL寄存器,发现屏幕内容虽然是正常的,但方向错了。这个不是bug,是旋转参数没生效,后面在设备树里加了rotate属性后解决。

5.2 用逻辑分析仪盯住CLK、MOSI、CS和DC

调试SPI LCD,逻辑分析仪是必备工具,比示波器更直观,能一次性看清多路信号的关系。我的排查顺序是这样的:

  1. 看CS:有没有按预期拉低并保持,软件片选状态下连续多次spi_write之间CS有没有意外抬起。
  2. 看CLK:频率是否接近设定值,如果设了40MHz但实际只有几MHz,说明时钟源或分频配置可能有问题。
  3. 看DC:命令字节前后的DC电平变化是否与数据手册一致,CMD时低、DATA时高。
  4. 看MOSI数据:对照屏幕驱动IC手册里的命令格式,确认字节顺序和格式正确。

如果抓到波形并确认命令格式和手册一致,屏幕还是不亮,基本可以排除驱动IC通信问题,转向检查复位时序、电源、背光这几路。

5.3 帧率到底卡在哪:算一笔账

SPI屏的刷新率瓶颈在SPI总线的带宽,这个问题可以提前算清楚。

以240x240分辨率、RGB565格式为例:

  • 一帧数据量 = 240 x 240 x 2 = 115200字节
  • 如果SPI时钟跑40MHz,传输一帧的时间 = 115200 x 8 / 40000000 ≈ 23ms
  • 对应理论帧率约43fps

但这是纯数据搬运时间,没有算命令开销、CS切换、GPIO操作和调度延迟。实际跑下来,40MHz时钟下定时整刷能稳定在20到25fps已经不错了,CPU占用还不低。

如果你发现实际帧率远低于这个值,优先检查这几件事:

  • SPI实际速率是不是被降频了,比如信号质量差导致内核自动降低速度
  • 每次刷新是不是把整屏数据都搬了一遍,没有利用窗口裁剪
  • 刷新线程是否被高优先级任务抢占
  • 有没有不必要的msleep或忙等

5.4 画面撕裂与闪屏的几个隐藏原因

画面撕裂在SPI屏上比RGB屏更隐蔽,因为SPI屏不像VOP那样有固定的VSYNC机制。

我自己遇到过一次非常诡异的“上部正常下部乱掉”的问题。排查到最后发现是刷新worker在SPI传输数据的过程中,用户程序正在往显存写入新帧,导致SPI搬运的数据一半是旧帧、一半是新帧。解决方案是加一个简单的刷帧锁:刷新开始时把显存数据快照到一个临时dma buffer,或者干脆接受单缓冲并用一个自旋锁保护关键写入口。

闪屏的原因往往和背光时序有关。如果初始化ST7789V之前就把背光打开,上电瞬间屏幕内部状态不确定,会闪一下白屏。另外,屏幕内部电源稳定需要时间,背光开启太早会让用户看到电源纹波带来的闪烁。我的经验是:复位完成后延时200ms,初始化命令发完,清屏,最后开背光

6. 显示效果与产品化细节:旋转、背光、叠加LVGL

6.1 竖屏改横屏:一条MADCTL命令,还是绕不开坐标旋转

SPI LCD做产品经常遇到竖屏改横屏的需求。ST7789V这类IC通过MADCTL寄存器(0x36)可以控制像素扫描方向,本质上就是控制行/列地址的增长方向。

MADCTL寄存器里有一个MY位、一个MX位、一个MV位,组合起来可以实现四种旋转方向。我不建议记住每个组合的二进制值,而是建议在设备树里定义rotate度数,驱动里查表设置:

static u8 lcd_rotate_to_madctl(int rotate) { switch (rotate) { case 90: return MADCTL_MV | MADCTL_MX; case 180: return MADCTL_MY | MADCTL_MX; case 270: return MADCTL_MV | MADCTL_MY; default: return 0; } }

这样产品的机械方向确定后,改一个设备树属性就行。

但有一点要注意:MADCTL只改变扫描方向,不改变FrameBuffer的逻辑分辨率。如果屏幕是240x240正方形,旋转无影响;如果是320x240这类矩形屏,旋转后xres和yres要交换,驱动里分配显存时就要考虑这个因素。

6.2 背光控制从“能亮”到“能调”

简单的SPI屏背光是一个GPIO,只能开和关。产品上如果要做亮度调节,建议用PWM背光。

RK3568上有PWM控制器,设备树里可以这样声明:

backlight: pwm-backlight { compatible = "pwm-backlight"; pwms = <&pwm3 0 50000 0>; brightness-levels = <0 20 40 80 160 255>; default-brightness-level = <3>; status = "okay"; };

内核的pwm-backlight驱动会生成/sys/class/backlight/pwm-backlight/brightness节点,用户态echo数值进去就能调亮度。要注意PWM引脚和SPI引脚可能又有复用冲突,配设备树时先查pinctrl。

如果驱动里想自己控制PWM,可以在probe里获取pwm句柄,按亮度和占空比映射表操作,但绝大多数场景用内核现成的pwm-backlight就够。

6.3 用LVGL驱动/dev/fb0,把UI跑起来

FrameBuffer模式的SPI LCD在现代嵌入式产品里最经典的搭配就是LVGL。LVGL官方的linux framebuffer驱动会直接打开/dev/fb0,然后当成一块普通显示画布使用。

我实际项目中的操作路径是:

  1. 确认/dev/fb0存在,并且文件权限正确
  2. 用户态程序打开/dev/fb0
  3. LVGL初始化时调用lv_linux_fbdev_create(),传入fb0的fd
  4. LVGL内部通过mmap映射显存,然后调用lv_refr_now()刷新

配合局部刷新区,LVGL只需要重绘变化的矩形区域,然后由驱动把脏矩形通过SPI刷到屏幕。这样设计下,一个240x240的界面,刷新率完全够用,CPU占用也低。

6.4 最后的调试建议:把fbcon挂到屏上

所有驱动代码都跑通之后,我强烈建议你在内核启动参数里加上fbcon=map:0,把内核console输出重定向到FrameBuffer上。这样做的好处是,以后驱动调试不需要再接串口看日志,内核启动信息和应用程序的printf信息都会显示在SPI小屏上,非常直观。

我自己的RK3568项目中,就是靠这个方式在屏上看到了内核崩溃栈,快速定位了一个GPIO申请失败导致probe退出的问题。

另外再分享一个实用技巧:调试阶段把/sys/class/graphics/fb0/rotate写一下,看看FB方向切换对你当前的MADCTL配置有没有叠加影响,这样产品改旋转方向时不会在驱动和用户态之间两头踩坑。

这块屏的方案做完之后,后续如果再接到不同型号的SPI LCD,基本上就是换一个初始化命令表、改一下分辨率宏定义的事。剩下的FrameBuffer架构、刷新机制、DMA缓冲、设备树框架都可以复用,这也是为什么值得在这条路上花时间把驱动吃透的原因。

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

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

立即咨询