RK3568 SPI LCD FrameBuffer驱动开发:从设备树到上屏实践

RK3568 SPI LCD FrameBuffer驱动开发:从设备树到上屏实践 前一阵给一块RK3568板子接了一款2.4寸SPI接口TFT屏需求说得很直接开机就要能显示logo应用层能用一种不折腾的方式画界面。我一开始习惯性地想直接上DRM/KMS那套可看了屏幕规格和项目定位最后反而回到了FrameBuffer加SPI这条“传统”路线。这篇文章把这套驱动开发的完整过程整理出来包括为什么这么选、设备树怎么配、fb_info怎么初始化、刷新机制怎么设计以及几个调试中容易踩的坑希望能给正在RK3568这类平台上做SPI LCD驱动开发的朋友一些参考。这种方案适合谁如果你手里就是一块小尺寸SPI屏UI以静态界面或者简单GUI为主刷新率要求不高又不想为一个屏去把VOP、DRM/KMS、pwm-backlight整条显示链路全配起来那FrameBuffer模式会省掉非常多的精力。当然如果日后要跑视频、做多图层合成那还是得回到DRM/KMS这个取舍我在第一章详细说。1. 方案选型什么情况下你会需要FrameBuffer加SPI LCD1.1 为什么不用DRM/KMS或RGB/MIPI屏RK3568本身支持RGB、MIPI DSI、LVDS、eDP这些显示接口Linux内核里也带了完整的DRM/KMS驱动。那为什么还要折腾SPI屏加FrameBuffer一句话项目需求决定的。我这次要驱动的是一块240x320的SPI屏主控芯片是ST7789V这类成熟方案。这类屏的数据总线只有4根加上控制脚也不过8根左右硬件布线极其简单。刷新率方面SPI全屏刷新在30MHz时钟下满打满算也就每秒20帧出头但我的场景是仪表显示、参数设置页、状态监控这类界面刷新需求大多在局部区域比如一个数字变了、一条曲线增加了根本不需要全屏重绘。如果选择DRM/KMS需要配置VOP绑定、connector、panel节点、backlight等等链路长、依赖多别人接手维护的成本也高。而FrameBuffer方案把整条链路简化为一个驱动、一个buffer、一个刷新循环应用层mmap之后直接写像素就完事调试路径短问题也好定位。1.2 FrameBuffer、SPI、LCD驱动这三者的分工先把概念捋清楚。FrameBuffer是Linux内核里一套经典的显示框架它向上对应用层暴露/dev/fb0设备节点应用通过mmap拿到一块内存往里面填像素数据显示框架负责把这部分内存内容呈现到屏幕。在SPI LCD这个项目里FrameBuffer把“显存”和“屏幕驱动”之间的差异都封装好了你不用care底层怎么通过SPI把数据发出去只需要实现好驱动里的几个回调函数和一个刷新机制。SPI负责解决“数据怎么物理送到液晶控制器”。LCD控制器内部有GRAM我们需要按它的协议先把显示窗口设定好再往GRAM连续写像素数据。这个过程落在驱动上就是把显存里的RGB数据按照屏幕控制器的时序要求组织成SPI事务发送出去。1.3 这个方案的适用边界与风险必须承认FrameBuffer加SPI这套方案有它的适用边界。首先是分辨率我建议别超过480x320再大起来SPI总线带宽就真的是瓶颈了全屏刷新速率会掉到人眼明显感到卡顿的程度。其次是画面内容如果应用层是LVGL或者Qt这类GUI框架通过局部重绘接口调用实际感受到的流畅度完全可用但如果跑视频播放器全屏渲染这就是给自己找罪受。另一个风险在于FrameBuffer框架本身。内核社区已经把它标记为维护状态新特性优先级降低短期内不会删除但长时间停留在旧内核的话你需要确认手里的SDK内核版本中fbmem、fb_ops这套接口还正常可用。在我测试的4.19和5.10内核上API是稳定的直接照常写就行。2. 硬件连线与RK3568 SPI控制器配置2.1 SPI LCD各引脚的作用与接线要点先把屏幕硬件接口弄明白再谈软件。市面上常见的SPI TFT屏引脚大概是这些SCLK时钟、MOSI主发从收数据、CS片选、DC数据/命令选择、RST复位、BL背光、VCC、GND。部分屏还有MISO主收从发引脚LCD在这种应用下通常不需要回传数据但留着可以后续做寄存器回读调试。我这次把DC、RST、BL都接到了GPIO上没有复用SPI控制器的特殊功能。原因很简单RK3568的SPI控制器不额外支持9bit模式而很多MCU屏手册里会说“DC信号可以作为第9个bit发送”这在STM32板子上常见在RK3568上不实用。用GPIO独立控制DC最稳妥驱动程序只需要在发送命令字节前把DC拉低发送像素数据前把DC拉高即可。引脚分配建议这样规划提前避开和调试串口、SD卡、EEPROM的冲突LCD引脚RK3568侧说明SCLKSPI1_CLK如GPIO3_B5控制器时钟输出复用为SPI功能MOSISPI1_MOSI如GPIO3_B6像素数据与命令编码复用为SPI功能CSSPI1_CS0或独立GPIO片选见2.3小节DC/RS任意GPIO如GPIO3_A0数据/命令选择必须为GPIORST任意GPIO如GPIO3_A1复位脚低电平有效BLGPIO或PWM如GPIO3_A2背光开关/亮度控制MISO可留空只用于回读SPI LCD一般不接接线时要注意电平匹配。RK3568的GPIO和SPI控制器都是3.3V电平绝大多数SPI TFT屏也是3.3V逻辑直接互联没有问题。如果板子上存在其他电平的外设在同一条总线上一定要检查是否需要电平转换免得烧坏屏幕。我遇到过一例因为屏幕背光引脚接了5V上拉导致GPIO口高电平永远拉不低的诡异问题排查了很久。2.2 设备树中SPI控制器相关配置在RK3568上使用SPI1作为LCD总线需要在设备树里把SPI控制器打开并配置好引脚复用。不同开发板的引脚定义有差异具体以板卡原理图和SDK中的pinctrl头文件为准。一般写法如下spi1 { status okay; pinctrl-names default; pinctrl-0 spi1m0_pins; assigned-clocks cru SCLK_SPI1; assigned-clock-rates 50000000; };assigned-clock-rates这一步很多人会漏掉。SPI控制器的时钟源如果不显式限定SDK里的默认设置可能跑不到你想要的高频率或者频率是某个奇怪的数值。我习惯把SPI控制器最高时钟放在50MHz然后在具体lcd子设备节点里用spi-max-frequency限制在30MHz或者40MHz留一点裕量。SPI总线速率不是越高越好线材、回路杂散电容、屏幕控制器本身的能力都可能限制实际稳定频率。2.3 硬件片选还是软件片选热词里也提到了“spi硬件片选与软件片选”这是SPI LCD开发里的一个经典选择题。硬件片选就是SPI控制器在传输时自动拉低CS脚传输结束自动拉高时序精确CPU不用管。缺点有两个一是RK3568的SPI控制器CS引脚编号有限如果你把CS配置为某个mux组后面对应的GPIO功能就没了二是当系统中还有其他SPI设备时片选切换逻辑是固定的你没法灵活控制。软件片选则是把CS配置成普通GPIO由驱动在spi_message发送前后手动置低置高。优点是灵活任意GPIO都能当CS缺点是CS的拉低和拉高操作与SPI时钟相位之间的配合要格外小心尤其在启用DMA的情况下。CS拉低后要等一个很小的延时让从设备稳定下来再启动SPI传输传输完成后也不能立即拉高CS总线末尾的时钟沿还在收尾要等spi_transfer完成回调触发后再拉高。我这次的屏幕是板上唯一的SPI从设备直接用硬件片选代码和设备树都简洁。如果你的项目有多个SPI从设备共线建议单独写一个逻辑分析仪确认时序不要上来就软件片选否则调试起来很痛苦。3. 设备树编写与GPIO配置细节3.1 SPI子设备节点完整示例SPI控制器配好后还需要在控制器节点下挂一个spi设备子节点这个节点的compatible会匹配到我们编写的SPI LCD驱动。设备树里我把分辨率、旋转方向、GPIO定义都放在节点属性中驱动里用device_property_read_*系列接口读取这样一个小屏换另一个小屏时往往只改设备树就行驱动不用动。以SPI1为例完整的LCD子节点长这样spi1 { status okay; pinctrl-names default; pinctrl-0 spi1m0_pins; assigned-clocks cru SCLK_SPI1; assigned-clock-rates 50000000; spi_lcd: spi-lcd0 { compatible mycomp,spi-lcd; reg 0; spi-max-frequency 30000000; dc-gpios gpio3 RK_PA0 GPIO_ACTIVE_HIGH; reset-gpios gpio3 RK_PA1 GPIO_ACTIVE_LOW; backlight-gpios gpio3 RK_PA2 GPIO_ACTIVE_HIGH; pinctrl-names default; pinctrl-0 spi_lcd_gpios; width-mm 36; height-mm 48; }; };其中reg 0表示挂在这个SPI控制器下的第0个片选对应CS0。width-mm、height-mm这两个属性对普通FrameBuffer应用不是必需的但某些GUI框架会读取它们做DPI计算我的LVGL界面就用到了。3.2 DC、RST、BL引脚的设备树表达GPIO属性里的flags要与硬件实际接法对应。reset-gpios我标的是GPIO_ACTIVE_LOW因为屏幕复位逻辑是低电平复位。驱动里执行复位时先调用gpiod_set_value_cansleep(rst, 1)将其拉高延时后拉低再通过gpiod_set_value_cansleep(rst, 0)保持低电平至少10ms最后再拉高并等待120ms以上这是绝大多数LCD控制器的复位时序要求。dc-gpios用GPIO_ACTIVE_HIGH是因为DC信号是电平含义而非“有效电平”含义高电平代表数据低电平代表命令。这里即使定义为ACTIVE_HIGH驱动里实际上还是要按照逻辑值去操作依赖的是gpiod_get_value和gpiod_set_value的语义需要注意别把flags和“数据/命令赋值”混淆了。背光引脚如果只做开关GPIO足够如果想调节亮度建议接PWM通道并用pwm-backlight设备节点管理而不是直接驱动里不断调占空比。RK3568的PWM引脚和GPIO可能复用同一物理引脚配置时注意在pinctrl中切到PWM功能。我在这块板子上背光就是简单开关所以先用GPIO凑合了正式产品里亮度调节几乎必然需要PWM。3.3 设备树加载后的验证方法设备树写完后内核启动日志里如果SPI控制器和子设备都能正常识别会输出类似“spi1.0: mycomp,spi-lcd”这样的信息。我通常用三个手段确认设备树生效一是查看/sys/class/spi_master/spi1/spi1.0路径是否存在。如果存在说明SPI控制器和子设备节点被正确枚举了并且该路径下能看到max_speed_hz和modalias等属性。二是检查/sys/kernel/debug/gpio里对应GPIO是否申请成功。drivers申请gpio后这里会显示占用者信息如果显示“SPI LCD reset”之类的标签说明驱动已经获取了GPIO控制权。三是用i2c-tools里的spidev_test思路先不加载正式驱动仅用spidev测试节点直接发一串0xAA数据出来用逻辑分析仪抓SCLK和MOSI波形确认SPI控制器本身没有问题。一旦SPI控制器这个基础通信环节确认OK后面屏幕不亮就一定是驱动或者设备树配置的问题排查范围大大缩小。4. FrameBuffer驱动核心实现4.1 从spi_driver到fb_info驱动主结构怎么搭驱动整体是标准的SPI设备驱动。module_spi_driver注册一个spi_driverprobe函数在设备树匹配成功后执行。probe里做的事情按顺序是解析设备树属性、申请GPIO、复位LCD、发送初始化序列、分配显存、填充fb_info、注册framebuffer、启动后台刷新线程。这里最关键的是fb_info和LCD显存屏幕之间的对应关系。我在probe里分配了一个fb_info结构再通过dma_alloc_coherent分配一块物理连续的内存作为显存这块内存既会被mmap给应用层写像素也会被刷新线程读取并通过SPI发送出去。模块卸载时顺序正好相反先停止刷新线程再unregister_framebuffer然后释放显存释放GPIO关闭屏幕最后释放fb_info。我曾因为卸载顺序写反导致刷新线程还在访问已被释放的显存模块卸载直接把内核挂死重启后清零才恢复。4.2 显存分配与fb_info初始化显存大小由分辨率、像素格式决定。240x320、RGB565格式每个像素2字节显存大小就是240x320x2 153600字节。通常我再多分一页方便后续做整页对齐。fb_info需要设置两个关键结构体fb_var_screeninfo描述可变参数fb_fix_screeninfo描述固定参数。固定参数里smem_start和smem_len必须用dma_alloc_coherent返回的物理地址和长度screen_base指向虚拟地址。可变参数里xres、yres、bits_per_pixel、红色绿色蓝色掩码偏移等必须写清楚。RGB565在fb_var_screeninfo中的标准表达是fbi-var.bits_per_pixel 16; fbi-var.red.offset 11; fbi-var.red.length 5; fbi-var.green.offset 5; fbi-var.green.length 6; fbi-var.blue.offset 0; fbi-var.blue.length 5;如果你写错了颜色布局应用层显示出来的画面颜色通道会互串最常见的现象就是“蓝色变大红、红色变蓝色”这种让人怀疑屏线接反的颜色错乱。fb_ops也至少要提供fb_fillrect、fb_copyarea、fb_imageblit三个回调我直接用内核自带的cfb_fillrect、cfb_copyarea、cfb_imageblit它们是软件绘制函数负责更新显存内容。因为LSB/大小端颜色顺序与屏幕Controller的要求差异cfb开头的函数有时还要配合fb_setcolreg一起实现否则控制台上显示的颜色不对。4.3 刷新机制拯救FrameBuffer方案的关键设计这部分是整个驱动最核心的地方。fb_ops只在应用程序调用ioctl(FBIOPUT_VSCREENINFO)、画矩形、拷贝区域时才被调用而大量应用是直接mmap显存后写数据比如LVGL和Qt的fbdev插件它们写完像素后并不会通知fb_ops。所以驱动必须自己有一个持续的刷新机制。我采用“后台线程扫描显存差异”的做法这是SPI类FrameBuffer驱动里最常见的方案。流程如下分配一块shadow buffer大小与显存相同初始清零。后台线程每隔20ms以上跑一轮。将screen_base虚拟地址中内容与shadow buffer逐字节比较找出差异区域。如果完全没有差异继续休眠等待下一轮。如果存在差异计算一个包含所有变化区域的包围矩形通过SPI发送set_window命令和像素数据。发送完成后把screen_base内容整体拷贝回shadow buffer作为下一次比较的基准。这个机制的优点是兼容任何应用层的写显存方式不需要应用配合。代价是每次都要遍历整块显存240x320的显存也才153600字节一次memcmp在几十微秒级别完全可以接受。SPI发送过程中要注意SPI帧的最大传输长度限制。有些SPI控制器单次DMA传输上限是64KB我们把整个包围矩形的像素一次性pack成一条spi_message时会超限。稳妥的做法是将矩形按行拆分成多个spi_transfer每行作为一条独立传输先发送set_window然后把该行的像素数据按顺序送进几个spi_transfer中通过spi_sync_transfer一次性提交。4.4 初始化序列与像素格式处理驱动probe阶段的LCD初始化序列是屏幕点亮的关键。这个序列通常由屏幕控制器型号决定比如ST7789V和ILI9341的初始化命令不完全兼容建议直接以屏幕厂商提供的初始化代码为准不要想当然套用网上的模板。我的做法是把初始化序列定义成一个命令表格每个条目包含一条命令、参数个数和参数数组static const struct lcd_init_cmd st7789v_init[] { {0x11, 0, {}}, // Exit Sleep {0x36, 1, {0x00}}, // MADCTL: RAM访问方向 {0x3A, 1, {0x05}}, // COLMOD: 16bit/pixel {0x21, 0, {}}, // Inversion ON {0x29, 0, {}}, // Display ON {0x35, 0, {}}, // Tearing ON可选 };初始化完成后驱动还要考虑像素格式的转换。屏幕控制器设置为RGB565格式那么显存里的数据直接就是RGB565字节流不需要转换。但这里有一件事容易被忽视SPI数据发送时的字节序。ST7789V这类控制器接收RGB565时每个像素要先发高字节再发低字节而RK3568是小端CPU内存中一个16位像素的低字节在前。所以从显存读取uint16_t像素后需要做个byte swap或者直接把像素按大端字节序列发送static inline void lcd_prepare_pixel_data(u16 pixel, u8 *data) { data[0] (pixel 8) 0xff; data[1] pixel 0xff; }如果不做这一步屏上每个像素的高低字节颠倒看到的画面就是细密噪点一样的颜色串扰这类问题光看照片很难判断是时序问题还是字节序问题建议先在驱动里用一个纯色测试命令确认。对于是否启用SPI DMA我的建议是先把CPU模式跑通把整条链路验证好再考虑性能优化。早期我直接上DMA结果CS时序和GPIO DC引脚配合不好屏幕闪得像波浪动画。改成CPU模式逐行发送后虽然CPU占用率上去了但同步逻辑简单清晰背景问题定位更快。DMA模式的正确打开方式是先查RK3568 SPI控制器DMA支持情况配置好DMA通道和DMA引擎再处理CS与DMA传输的同步问题这部分能占掉一半驱动开发时间。5. 编译部署与上屏验证5.1 内核模块与设备树编译驱动开发阶段我建议先以内核模块方式编译迭代速度快。Makefile里可以利用SDK已经编译好的内核头文件指定交叉编译工具链即可obj-m : spi_lcd_fb.o KERNELDIR ? /path/to/kernel-source CROSS_COMPILE ? aarch64-linux-gnu- ARCH ? arm64 all: make -C $(KERNELDIR) ARCH$(ARCH) CROSS_COMPILE$(CROSS_COMPILE) M$(PWD) modules设备树则要编进dtb再替换启动分区里的dtb文件。注意.mod和.dev符号可能因为内核配置差异而不同直接用SDK里提供的编译环境最省事。设备树编译命令一般是dtc或者make dtbs具体看SDK提供的构建方式。替换dtb后重新启动如果设备树节点正常应该会在/sys/class/spi_master/spi1/spi1.0下看到设备。如果没出现优先检查spi控制器节点status是否为okay以及pinctrl引脚是否冲突。5.2 应用层挂载测试模块加载成功后/dev/fb0应该出现。先用fbset看分辨率和色深是否正确fbset -i mode 240x320-0 geometry 240 320 240 320 16 timings 0 0 0 0 0 0 0 rgba 5/11,6/5,5/0,0/16 endmode如果fbset看到的rgba参数和驱动设置一致说明fb_var_screeninfo正确。此时用dd可以直接验证屏幕能否显示纯色比如往fb0写满0x00可以显示黑屏写满0xFF则显示白屏dd if/dev/zero of/dev/fb0 bs1024 count300很多显示问题用这种方法就能初步判断。写完纯色后屏幕应该出现相应的纯色画面。如果纯色都不对问题大概率在SPI传输或者初始化序列如果纯色对但图形程序错乱重点查显存大小、滚动参数或颜色格式。5.3 结合LVGL等GUI框架的接入方式应用层接入GUI框架通常不需要写驱动接口只要求框架能够打开/dev/fb0。以LVGL为例使用官方fbdev端口时在lv_conf.h里打开LV_USE_FBDEV设置LV_COLOR_DEPTH为16初始化函数里调用lv_fbdev_create传入设备路径/dev/fb0。关键点在于LVGL的刷新策略。默认情况下LVGL会维护自己的脏矩形列表然后通过fbdev端口把脏区域拷贝到显存对应位置。由于我们的驱动后台线程会持续比较整个显存差异这种增量写入可以自动转化为SPI局部刷新效率很高。如果你发现LVGL重绘很慢通常不是SPI传输问题而是lv_conf.h里刷新缓冲区太小或者LV_COLOR_16_SWAP没有打开文档里称为LV_COLOR_16_SWAP1配置。这个配置项的意思是LVGL输出RGB565像素时自动完成高低字节交换与驱动里准备的像素格式完全匹配颜色显示就正了。6. 常见问题与排查技巧6.1 花屏、白屏与数据错位先看最让人头大的花屏问题。现象一般是屏幕上出现彩色噪点、水平条纹、奇怪的重叠画面。我遇到这种问题第一步先用逻辑分析仪抓SCLK和MOSI波形确认发送的字节序列是否和预期一致。如果你没有逻辑分析仪也可以用SPI回环法把MOSI和MISO短接发送一串已知数据看能否回读一致。如果SPI数据本身正确花屏大概率是初始化命令序列不对。很多屏幕型号相近但厂商定制不同初始化寄存器差异很大。比如同样是ST7789有的屏需要Inversion ON有的需要OFF写反了画面颜色就会反色看上去像褪色的底片。这种问题建议直接核对屏厂提供的手册或初始化代码逐条对照。有时花屏也会表现为“画面偏移了几行”这通常是set_window命令里对行列坐标的定义与驱动使用的不一致或者是RGB数据字节序搞反了。用一张红绿蓝三色渐变色图片测试颜色分布能很快暴露问题。6.2 刷新慢与性能优化默认的全屏刷新方案240x320x2字节就是153600字节假设SPI速率32MHz理论时间已经达到每秒20帧左右。但后台线程还有显存diff比较、SPI message组装、互斥锁开销实际全屏刷新往往只有12到15FPS。如果UI界面是动态刷新的这个速度确实会感觉掉帧。性能优化优先级我建议这样排第一确认SPI实际时钟频率是否达到预期。查看/sys/class/spi_master/spi1/spi1.0/max_speed_hz如果低于设备树配置说明时钟树配置有问题assigned-clock-rates没生效。第二做局部刷新。利用显存diff只发送变化区域。这个优化对仪表盘、菜单界面效果立竿见影变化区域往往只是几个数字一次刷新的数据量从15万字节降到几百字节刷新率轻松突破60FPS。第三启用SPI DMA。CPU模式每字节一个循环CPU占用率高启用DMA后发送数据不再占用CPU核心主控可以同时做GUI计算整体体验提升明显。DMA配置复杂建议放到基础功能验证后再做。6.3 驱动加载崩溃与资源释放问题驱动加载后立即系统崩溃的常见原因有三个一是fb_info中fb_var_screeninfo的xres_virtual或yres_virtual配置错误导致fbcon或者应用访问越界二是drivers/probe里请求GPIO失败后没有正确退出导致后续访问无效指针三是dma_alloc_coherent返回NULL但没有检查后续把NULL地址传给了fb_info。调试驱动崩溃的手段主要是抓崩溃栈。如果崩溃栈出现在fbmem.c的fb_read或者fb_write中十有八九是smem_start或者screen_base有问题如果崩溃栈在我们自己的刷新线程里就要重点检查线程生命周期和显存缓冲区释放顺序。我习惯在probe成功和remove开始时各打印一行日志确认模块加载卸载的进入和退出顺序是否正常。6.4 常用排查命令清单调试过程中我把这些命令用得很频繁整理成一张速查表方便复现检查排查对象命令作用FrameBuffer设备cat /proc/fb查看已注册fb设备分辨率色深fbset -i查看fb_var_screeninfo实际参数SPI设备节点ls /sys/class/spi_master/spi1/确认spi1.0子设备是否枚举SPI最大频率cat /sys/class/spi_master/spi1/spi1.0/max_speed_hz校验SPI速率配置GPIO占用cat /sys/kernel/debug/gpio检查DC/RST/BL被哪个驱动占用SPI寄存器devmem 0xfe610000 32读取SPI控制器寄存器状态DMA配置cat /sys/kernel/debug/dmaengine/summary查看DMA通道占用内核日志dmesg -w观察probe/remove及异常打印这里列出的地址0xfe610000只是举例不同SDK里SPI1对应基地址可能不同需要对照芯片手册或设备树中spi1节点的reg属性确认。读取SPI寄存器能直接看到当前时钟分频、模式、传输状态对定位SPI不工作的问题特别有效。7. 写在最后一些实操经验与避坑心得整个项目里的关键体会有几条第一先在MCU上把屏幕跑通再碰Linux驱动。如果你手头有STM32或者ESP32的例程先在Linux板卡上不加驱动用gpio模拟的方式把屏点亮一次确认硬件接线、初始化序列、像素格式都没问题再写正式的FrameBuffer驱动。这一步能排除掉至少一半的软硬件混淆问题。第二设备树里GPIO的active-low标记很坑一定要对照原理图确认。DC引脚标记为active-high、reset标记为active-low本身没错但有些开发板的电平逻辑和你想象的不一样比如它已经把信号反相了你再用“复位拉低”就变成永远不复位。用gpget命令直接读取GPIO状态一秒钟就能分辨。第三屏幕上电时序非常关键。有的屏对VCC和RST的上电顺序有要求VCC必须先稳定RST才能拉高否则初始化会失败而且失败的重现性很差时好时坏。我这次还把背光打开放到了初始化序列之后避免屏幕还在复位的时候就亮起来观感也会好很多。第四FrameBuffer和SPI的组合在遇到GUI局部刷新需求时非常能打。如果你不想碰DRM/KMS那套复杂链路又想在小尺寸屏上做像样的图形界面这套方案仍然是我个人比较推荐的选择。调试工具齐全、参考资料多、问题定位路径清晰唯一需要忍受的就是刷新率和复杂图层能力的上限。反正对我这个仪表盘项目来说已经够用了。最后再分享一个调试小技巧驱动里预留一个只有一行的printf来打印当前SPI每帧传输字节数和耗时用cat /proc/lcd_stats这样的节点暴露给用户态。性能是不是瓶颈看一下这个数字立刻清楚不用反复猜测。这套方法的性价比远高于对着示波器猜。