十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

IMX6ULL外接SPI LCD跑QT:从设备树到framebuffer完整指南

IMX6ULL外接SPI LCD跑QT:从设备树到framebuffer完整指南 简介本资源是面向嵌入式Linux初学者与Qt移植实践者的IMX6ULL平台SPI LCD图形界面开发套件聚焦解决ARM Cortex-A7平台下ILI9341驱动的TFT屏MSP2402模组与Qt应用协同显示的核心问题。压缩包共含多个关键文件涵盖适配IMX6ULL的设备树源码.dts、基于Qt 5.x的轻量级界面源码.cpp/.h/.pro、SPI初始化及LCD背光控制脚本以及编译部署说明文档整体大小为9.57MB结构清晰、模块职责明确便于理解硬件抽象层与GUI框架的衔接逻辑。已有278人学习下载适合开展嵌入式Qt入门实验、设备树定制训练及SPI外设驱动调试。读者可直接部署运行示例界面掌握从内核设备树修改、SPI总线配置到Qt渲染后端适配的完整链路获得可复用的移植方法论与排错参考。 手头这块IMX6ULL开发板吃灰有一段时间了最近翻出来想给它配一块小尺寸屏幕跑一个简单的QT界面。本来觉得SPI LCD接上去、内核驱动一开、Qt往fb上一指就完事了结果真正动手才发现链条挺长——SPI设备树、fbtft驱动、framebuffer设备、Qt的linuxfb插件哪一个环节没对上屏幕都只能白屏或者黑屏。这篇文章就是完整过程的梳理包括MSP2402这块ILI9341屏的接线、内核侧点亮到QT界面源码的整个链路最后还会把打包好的源码工程里有什么、怎么复用讲清楚。标题里的“源码.rar”我之前传过一份是给朋友参考用的里面不只包含QT工程还有设备树、内核配置片段和部署脚本。如果你正好也是IMX6ULL的板子想低成本外接一块SPI小屏显示中文状态页那这份记录应该能帮你省下至少一整个周末的排查时间。1. 为什么用SPI小屏跑QT这个需求是怎么被我折腾出来的1.1 一块吃灰开发板引发的场景选择IMX6ULL这颗Cortex-A7内核的处理器在嵌入式Linux圈子里算是非常大众的选择。它的原生产品定位是工业控制、车载终端和物联网网关性能不算强但胜在稳定、外设齐全、资料多而且它内部带了多个ECSPI控制器数量充足其中一个拿来接SPI屏幕完全不影响主功能使用。我手上这块板子主芯片是IMX6ULL底板已经带了百兆网口、串口和USB但唯独没有配屏。之前接过的都是RGB并口LCD或者LVDS屏体积大、线多而且价格也不便宜。手头正好有一块2.4英寸的SPI接口小屏型号MSP2402驱动IC是ILI9341分辨率240x32016位色SPI接口只需要几根信号线就能驱动非常适合做那种“只显示几个状态数据”的小界面。有些朋友可能会问IMX6ULL不是自带LCD控制器吗为什么不用RGB并口屏这个问题的答案是“成本和使用场景”。并口RGB屏的像素数据总线至少16根占用的引脚非常多而且需要额外的背光控制、行场同步信号PCB布局和排线都更复杂。SPI屏只需要SCLK、MOSI、CS、DC、RST五根核心信号线接线简单软件上通过帧缓冲设备驱动Qt的linuxfb插件能直接识别开发效率很高。缺点也很明显刷新带宽不高不适合做复杂动画但做静态菜单、仪表盘、状态页绰绰有余。1.2 源码包里最终交付了哪些东西网上下载的很多“IMX6ULL SPI LCD QT界面源码”要么只有Qt工程要么只有设备树很少有把整条链路串起来的。我整理这份源码的时候目标就是让一个不太熟悉内核移植的人拿到手也能按步骤跑起来。压缩包里主要包含这几块设备树DTS源码包含ECSPI引脚复用、ILI9341节点、背光节点内核配置片段把fbtft和fb_ili9341相关选项列出来Qt工程源码一个可运行的简单界面含中文显示字体配置交叉编译后的部署脚本拷贝二进制、设置环境变量、挂载调试信息README文档记录每一步操作和常见问题排查方法。这个工程不仅仅能在MSP2402上跑其他基于ILI9341的SPI LCD模块也基本通用最多调整一下引脚映射和初始化参数。2. 硬件连接和引脚级确认MSP2402这块屏不能只看排针丝印2.1 MSP2402和ILI9341的关系MSP2402是模块厂商给的型号本质上是一块2.4英寸TFT屏幕模组核心是主控ILI9341。这个IC是市场占有率非常高的TFT LCD控制器240x320分辨率支持SPI、并行等接口内置GRAM、电源电路和伽马校正。因为用得人太多Linux内核里已经有现成的驱动不管是老的fbtft还是新的DRM tinydrm都能直接支持。大部分MSP2402模块会引出8个或6个引脚。8针版本一般是“VCC、GND、CS、RESET、DC、MOSI、SCK、LED”6针版本把CS和DC合到固定电平或者直接省略剩下“GND、VCC、SCK、SDA、RES、DC”。如果模块上没有单独的CS引脚通常是把CS接地表示这个SPI总线上只挂一个设备如果有CS引脚我建议一定要接到控制器上方便做片选管理后面排查问题也容易定位。拿到屏幕先不要急着接线用万用表量一下VCC和GND之间的电阻再确认模块上有没有板载稳压器。有些模块的VCC脚可以接5V因为板上带了AMS1117-3.3有些则只能接3.3V接5V会烧。MSP2402这个模块我自己测的是可以接3.3V供电背光灯串一个限流电阻后也能直接用3.3V但为了保险起见电源部分我还是走的PMIC的3.3V输出没有碰5V。2.2 和IMX6ULL的ECSPI接线IMX6ULL的SPI控制器叫ECSPI每个控制器有4个片选信号。我们平常用的功能引脚都通过IOMUX复用具体选哪个引脚要看你的底板设计。我的板子上ECSPI1引到了扩展排针引脚定义如下你可以对照你的板子原理图调整LCD模块引脚IMX6ULL侧信号说明VCC3.3V电源为LCD模块供电GNDGND必须共地SCLKECSPI1_SCLKSPI时钟由主控发出MOSI / SDAECSPI1_MOSI主机输出数据线MISOECSPI1_MISO部分模块有可用于读寄存器CSECSPI1_SS0 / GPIO片选信号RESETGPIO任意复位信号低电平有效DC / RSGPIO任意数据/命令选择LED / BLGPIO或PWM背光使能/亮度控制如果你的是6针屏排针上可能没有CS和MISO这时候CS直接接GNDMISO空着不接。DC和RESET一定不能省ILI9341通过DC引脚分辨SPI上传的是命令还是像素数据RESET则用来上电复位。背光引脚最好单独用一个GPIO控制我默认在设备树里配置成“上电默认点亮”方便调试。2.3 电气和信号完整性串阻、上拉、供电SPI屏的调试中有一个经常被忽略的问题信号线上要不要串电阻。很多人问“SPI时钟线串电阻一般多大”这个问题没有标准答案取决于驱动端输出阻抗、PCB走线长度和负载电容。以IMX6ULL的ECSPI引脚为例输出的边缘速率挺快如果连接线超过5厘米且又是杜邦线飞线就容易产生过冲和回铃导致SPI时序抖动表现出来就是屏幕花屏或者偶发白屏。我测试下来SCLK和MOSI上各串一个33Ω电阻能明显改善波形如果线材特别长可以提高到47Ω。这不是必须的但如果你用杜邦线飞线而且频率上不去可以先试试串电阻。CS和DC这两个信号因为是电平控制不需要串电阻但建议靠近主控引脚加一个10kΩ上拉避免上电瞬间悬空导致误操作。另外要注意“共地”。SPI是同步通信主从设备之间如果地电位差太大抗干扰能力会很差尤其屏幕背光瞬间启动时电流变化很快地弹会导致SPI数据错误。所以电源线和地线尽量短避免和背光驱动共用一根细长地线。2.4 关于片选硬件片选还是GPIO软件片选IMX6ULL的ECSPI控制器支持硬件自动片选也可以在设备树里把CS引脚配置成GPIO用软件控制片选。两种方式都能用但效果有区别。硬件片选由控制器在每次传输时自动拉低拉高时序精准适合高速传输。软件片选的好处是引脚选择灵活尤其当你需要把CS放到一个原本不是SPI片选功能的GPIO上时就会非常方便。我的源码里用的就是GPIO软件片选因为板子引出的ECSPI1_SS0同时被其他功能占用了直接换GPIO最省事。设备树里配置cs-gpios属性就能实现。你可能要问软件片选会不会慢实际测试在20MHz SPI频率下CE电平切换的延迟并不会明显影响帧率对静态界面来说完全够用。但如果你的屏幕要跑视频或者高频动态刷新建议还是用硬件片选时序余量更大。3. 设备树和内核驱动从SPI总线上“长出”一个/dev/fb03.1 驱动选型fbtft还是内核DRM早期Linux内核中ILI9341这类SPI屏的驱动都收在drivers/staging/fbtft/里提供的是传统帧缓冲接口也就是生成/dev/fb0。Qt的老牌插件linuxfb可以直接映射这个帧缓冲所以fbtft是STM32MP1、IMX6ULL这类入门板子上最常用的方案。新内核里有个更潮的tinydrm驱动基于DRM框架设备节点是/dev/dri/card0适合跑Wayland或者EGLFS。但在IMX6ULL这种性能有限的平台上跑Qt Widgets界面用fbtft足够了配置简单文档也多我最终选择了fbtft。如果你用的内核版本比较新比如5.10以上fbtft可能还在staging里打开对应Kconfig就能用。如果你的BSP是NXP官方老内核比如4.1.15也已经自带fbtft直接在menuconfig里勾上就行。启用fbtft相关的内核配置项我一般是这样勾选Device Drivers --- [*] Staging drivers --- [*] Support for small TFT LCD display modules --- * FB driver for the ILI9341 LCD Controller * Generic FB driver for TFT LCD displays [*] SPI support --- * SPI driver skeleton如果内核里找不到FB_TFT_ILI9341说明没开Staging驱动或者Kconfig里没启用fbtft目录。确认方法就是grep FB_TFT /boot/config-$(uname -r)如果为空需要先把CONFIG_STAGING打开。3.2 设备树节点完整示例拿我的板子举例ECSPI1接的是MSP2402设备树里需要两个部分一个是IOMUX引脚复用配置一个是SPI设备子节点。IOMUX配置放在iomuxc节点下iomuxc { pinctrl_ecspi1_lcd: ecspi1_lcdgrp { fsl,pins MX6UL_PAD_GPIO1_IO00__ECSPI1_SCLK 0x80000000 MX6UL_PAD_GPIO1_IO01__ECSPI1_MOSI 0x80000000 MX6UL_PAD_GPIO1_IO02__ECSPI1_MISO 0x80000000 MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x80000000 MX6UL_PAD_UART3_CTS_B__GPIO1_IO26 0x80000000 MX6UL_PAD_UART3_RTS_B__GPIO1_IO27 0x80000000 MX6UL_PAD_UART2_CTS_B__GPIO1_IO22 0x80000000 ; }; };这里我把SCLK、MOSI、MISO配成了ECSPI功能CS配成GPIO1_IO03DC用GPIO1_IO26RST用GPIO1_IO27背光用GPIO1_IO22。每个板子的引脚选择不一样你只需要把实际用到的引脚按这个模式写进去就行。然后配置ECSPI1节点和屏幕子节点ecspi1 { pinctrl-names default; pinctrl-0 pinctrl_ecspi1_lcd; fsl,spi-num-chipselects 1; cs-gpios gpio1 3 GPIO_ACTIVE_LOW; status okay; display: ili93410 { compatible fbtft,ili9341; reg 0; spi-max-frequency 16000000; reset-gpios gpio1 27 GPIO_ACTIVE_LOW; dc-gpios gpio1 26 GPIO_ACTIVE_LOW; backlight lcd_backlight; rotate 0; bgr 1; fps 30; buswidth 8; debug 0; }; };背光节点单独定义在根节点下/ { lcd_backlight: lcd-backlight { compatible gpio-backlight; gpios gpio1 22 GPIO_ACTIVE_HIGH; default-on; }; };这里有几个属性要注意。compatible固定填fbtft,ili9341驱动匹配就靠它。reg对应片选索引因为我们只有一个设备填0。spi-max-frequency不要一上来就拉满ILI9341标称虽然支持到几十MHz但飞线布线情况下我建议先从8MHz试稳定后再往上加。rotate控制屏幕旋转方向0/90/180/270分别对应四个方向如果画面内容上下颠倒或者左右镜像改这个值就行。bgr用于控制颜色顺序如果发现红蓝色对调改成0或者1各试一次。fps是帧缓冲刷新率不要设太高30左右比较合适高了反而因为带宽不足导致闪烁。3.3 启用内核模块和最基本的点亮验证设备树改好后重新编译设备树并烧写进启动分区。内核模块如果编译成M上电后需要手动modprobe fb_ili9341或者提前把模块放进rootfs开机自动加载。我习惯直接编成*内置这样设备树匹配到节点后驱动会自动加载并注册framebuffer。开机后在终端执行ls /dev/fb* cat /sys/class/graphics/fb0/name如果能看到fb0名字显示fbtft-ili9341之类那么驱动层面已经通了。接着做最直观的点亮测试dd if/dev/urandom of/dev/fb0 bs1024 count100如果屏幕出现随机色点说明像素数据链路没问题。如果屏幕全白但屏幕背光亮大概率是DC、RESET、CS引脚配置错了或者SPI根本没有传输数据。这时候去查内核日志dmesg | grep -i fbtft dmesg | grep -i ili9341很多问题在日志里就能看到比如spi-master spi0: failed to transfer one message这类错误说明SPI传输失败优先检查引脚复用和电气连接。4. Qt交叉编译与linuxfb平台插件让Qt认识这块SPI帧缓冲4.1 为什么用linuxfb而不是eglfsIMX6ULL有2D加速和PXP但没有GPU。如果接的是RGB并口屏本来可以用IMX6ULL自带的LCD控制器配合eglfs或者wayland来做硬件合成会流畅一点。但外接SPI屏的情况下屏幕本质是一个通过fbtft模拟出来的帧缓冲设备没有DRM主节点也没有KMS最合适的方式就是Qt的linuxfb平台插件。linuxfb插件是Qt最早的一套嵌入式后端它不依赖X11、Wayland直接mmap/dev/fb0然后通过QPainter等绘制到帧缓冲中。虽然性能不算高但结构非常清晰很少出幺蛾子。你在项目里通过配置环境变量来告诉Qt用哪个平台插件完全不用改动业务代码。有些朋友问能不能用QML/QuickQML本身跟平台无关但QML渲染依赖OpenGL或者软件渲染SPI屏上跑Quick的软件渲染也不是不行只是性能会比较感人。如果你的界面只是简单的控件布局老老实实用Qt Widgets最省心。我这份源码里的示例就是Widgets工程。4.2 交叉编译Qt时的configure参数IMX6ULL跑Linux一般用arm-linux-gnueabihf交叉编译器。树莓派、BeagleBone这类平台很多人直接装发行版的Qt包但嵌入式板子往往需要自己裁剪。如果你不想从头编译Qt也可以考虑直接用Yocto或Buildroot生成的Qt SDK那样省很多时间但前提是你对这两个构建系统足够熟悉。我自己用Qt 5.15.2源码交叉编译的configure参数供参考./configure \ -prefix /opt/qt5.15.2-arm \ -opensource -confirm-license \ -xplatform linux-arm-gnueabihf-g \ -release -no-opengl \ -no-eglfs -no-xcb -no-wayland \ -linuxfb \ -no-tslib \ -qt-freetype \ -nomake examples -nomake tests说明一下几个关键选项-no-opengl和-no-eglfs都是为了去掉无用的图形后端减小体积。-linuxfb一定要保留这是让Qt有linuxfb平台插件的开关。-qt-freetype是把字体渲染引擎编进Qt这样后续在板子上加载TTF字体时会很方便。-nomake examples和-nomake tests纯粹是为了编译速度。编译完成后安装到本地目录make -j4 make install安装完成后交叉编译你的Qt工程时需要把qmake指向这个新生成的qmakeexport PATH/opt/qt5.15.2-arm/bin:$PATH qmake your_project.pro make出来的二进制就是ARM架构的可执行文件直接拷贝到板子上。4.3 运行环境变量和中文显示字体Qt在板子上运行时首先需要一组环境变量export QT_QPA_PLATFORMlinuxfb export QT_QPA_FB_DEVICE/dev/fb0 export QT_QPA_FB_NO_LIBINPUT1 export QT_QPA_FB_TSLIB0QT_QPA_PLATFORMlinuxfb指定平台插件。QT_QPA_FB_DEVICE指定帧缓冲设备如果你系统里还有HDMI或者LVDS等其他fb可能需要改成/dev/fb0、/dev/fb1可以在应用启动前用cat /proc/fb确认。中文显示是另一个大坑。linuxfb插件默认只编译了很小的字体集如果代码里直接写中文屏幕上大概率是一堆方块。解决办法是把中文字体文件放到板子上然后在代码里用QFontDatabase注册字体。推荐用文泉驿正黑或者思源黑体的TTF精简版体积控制在几MB以内IMX6ULL上加载不会太慢。我源码里的做法是在main函数最前面加载字体#include QFontDatabase int main(int argc, char *argv[]) { QApplication app(argc, argv); QFontDatabase::addApplicationFont(/usr/share/fonts/wqy-zenhei.ttc); QFont font(WenQuanYi Zen Hei, 16); app.setFont(font); MainWindow w; w.showFullScreen(); return app.exec(); }如果你不想在代码里写字体名也可以直接用环境变量QT_QPA_FONTDIR指定字体目录Qt启动时会扫描该目录下的字体文件。5. 写一个可以演示的QT界面工程结构、代码和部署5.1 工程文件组织我打包的QT工程结构很简单没有用复杂的模块化设计原因就是让它更容易看懂imx6ull_lcd_demo/ ├── imx6ull_lcd_demo.pro ├── main.cpp ├── mainwindow.h ├── mainwindow.cpp ├── resources.qrc └── fonts/ └── wqy-zenhei.ttc.pro文件内容QT core gui widgets TARGET imx6ull_lcd_demo TEMPLATE app CONFIG c11 SOURCES main.cpp mainwindow.cpp HEADERS mainwindow.h RESOURCES resources.qrcresources.qrc把字体文件编进可执行文件这只是为了演示方便。实际产品中字体文件放rootfs里独立管理更好避免每次改字体都要重新编译。5.2 main.cpp和主窗口实现main函数我已经贴过一段核心就是注册字体、设置全局字体、全屏显示主窗口。主窗口实现一个很简单但能体现“LCD屏幕刷新能力”的界面首页显示一块状态信息包含一个标签、一个进度条、两个按钮还有一个自绘区域用来测试局部刷新是否正常。mainwindow.h#ifndef MAINWINDOW_H #define MAINWINDOW_H #include QMainWindow class QLabel; class QProgressBar; class QPushButton; class QTimer; class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent nullptr); protected: void paintEvent(QPaintEvent *event) override; private slots: void updateProgress(); private: QLabel *infoLabel; QLabel *stateLabel; QProgressBar *progressBar; QPushButton *btnRefresh; QPushButton *btnQuit; QTimer *timer; int progressValue; }; #endifmainwindow.cpp里用QVBoxLayout把控件纵向排列。这个布局在240x320的竖屏上很合适所有控件都能装得下。#include mainwindow.h #include QVBoxLayout #include QLabel #include QProgressBar #include QPushButton #include QTimer #include QDateTime MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), progressValue(0) { setWindowTitle(IMX6ULL LCD Demo); infoLabel new QLabel(IMX6ULL MSP2402, this); infoLabel-setAlignment(Qt::AlignCenter); stateLabel new QLabel(状态: 运行中, this); stateLabel-setAlignment(Qt::AlignCenter); progressBar new QProgressBar(this); progressBar-setRange(0, 100); progressBar-setValue(0); btnRefresh new QPushButton(刷新数据, this); btnQuit new QPushButton(退出, this); QWidget *central new QWidget(this); QVBoxLayout *layout new QVBoxLayout(central); layout-addWidget(infoLabel); layout-addWidget(stateLabel); layout-addWidget(progressBar); layout-addWidget(btnRefresh); layout-addWidget(btnQuit); setCentralWidget(central); timer new QTimer(this); connect(timer, QTimer::timeout, this, MainWindow::updateProgress); timer-start(500); connect(btnRefresh, QPushButton::clicked, [this]() { stateLabel-setText(状态: 手动刷新 QDateTime::currentDateTime().toString(HH:mm:ss)); }); connect(btnQuit, QPushButton::clicked, qApp, QCoreApplication::quit); } void MainWindow::updateProgress() { progressValue (progressValue 5) % 101; progressBar-setValue(progressValue); stateLabel-setText(QString(状态: 进度 %1%).arg(progressValue)); update(); } void MainWindow::paintEvent(QPaintEvent *event) { Q_UNUSED(event); QPainter painter(this); painter.setPen(QPen(Qt::cyan, 2)); painter.drawLine(0, height() / 2, width(), height() / 2); }paintEvent里画了一条水平线用来直观确认Qt的绘制事件能否正确触发LCD局部刷新。如果你把界面最大化或者全屏显示这条线每次进度更新时重绘就能看出SPI屏的刷新能力有多强。注意要在cpp里包含#include QPainter。这段代码只是为了演示实际项目里可以把自绘区域独立成一个QWidget子类。5.3 在板子上构建和部署交叉编译时先确认qt的qmake路径在PATH里然后在工程目录下执行qmake make生成的二进制是ARM格式可以直接file imx6ull_lcd_demo验证架构。部署到板子上假设rootfs目录是/opt/rootfssudo cp imx6ull_lcd_demo /opt/rootfs/home/root/ sudo cp -r fonts /opt/rootfs/usr/share/fonts/然后在板子上运行export QT_QPA_PLATFORMlinuxfb export QT_QPA_FB_DEVICE/dev/fb0 export QT_QPA_FB_NO_LIBINPUT1 ./imx6ull_lcd_demo如果一切正常屏幕上应该出现标签、进度条和按钮。按钮本身在没触摸屏的情况下没什么用但可以通过鼠标事件或者外部按键来控制退出和刷新。如果你接入了tslib触摸屏环境变量里加上QT_QPA_FB_TSLIB1即可。6. 调优和踩坑白屏、花屏、刷新慢、方块字我一个个排查过6.1 白屏和花屏先从最基础的信号查起用这套方案的点亮调试我遇到最多的就是白屏。白屏说明背光亮了但液晶没有收到有效数据或者初始化没成功。排插顺序我一般按下面这个链路查确认设备树节点有没有被内核解析ls /sys/bus/spi/devices/如果有类似spi0.0的设备节点说明SPI上发现了设备。如果节点都没有检查compatible属性是否匹配、ECSPI控制器status是否为okay。确认fbtft驱动是否加载ls /sys/class/graphics/fb0/name如果不存在dmesg里会有fbtft相关报错。手动给RESET引脚一个低电平脉冲观察模块是否有初始化反应。可以在shell里操作GPIO也可以用示波器抓波形。如果以上都正常很可能是DC引脚接反了。DC引脚高电平时传输的是数据低电平时传输的是命令。如果设置反了初始化序列全乱屏幕也就白屏。花屏通常比白屏相对好定位。花屏说明数据已经传到了屏幕但数据内容或时序不对。优先把spi-max-frequency从16MHz降到8MHz再试如果正常了就去检查信号线上的串阻、飞线长度、共地质量。还有一种情况是bgr属性设置反了红蓝互换会产生“彩色噪点”的视觉效果。ILI9341初始化时还需要注意一个细节模块的rotate设置会改变扫描方向和RGB/BGR顺序MSP2402这个模组默认有些出厂就是BGR顺序Linux里的fbtft驱动如果没做处理显示出来的红色和蓝色会颠倒。直接在设备树里把bgr从0改成1测试即可不用改代码。6.2 刷新速度和QT性能SPI屏不能啥都往上堆用SPI屏跑Qt帧率天然有限。ILI9341在16MHz SPI时钟下满屏240x320像素16位色一帧裸数据大概是153600字节理论刷新时间至少76ms加上控制开销实际跑满30fps都吃力。所以如果界面上有大面积动画、复杂渐变或者频繁整屏重绘刷新就会显得很卡。应对办法有几个。一是降级刷新需求界面尽量做成静态样式用标签和简单图形不要用半透明、阴影、复杂QSS。二是把屏幕的rotate设置为0或者90度减少数据量不会变但扫描方向差异在某些模块上会影响观感流畅度。三是合理使用update()的局部刷性不要调用repaint()强制整屏重绘。我在源码里用QTimer每隔500ms更新一次进度条这个频率在SPI屏上看是流畅的如果缩短到100ms肉眼就能看到进度条一顿一顿地跳这就是带宽瓶颈。如果你需要显示实时曲线或者仪表建议降低采样频率或者只在小区域局部重绘。还有一点容易踩Qt的linuxfb插件默认会在每次事件循环空闲时刷新整个窗口如果你的主窗口是showFullScreen()它可能把整个屏幕都重绘一遍。这时候可以在应用启动前设置QT_QPA_FB_NO_FULLSCENARIO_UPDATE1来限制整屏刷新不过这个变量在不同Qt版本里行为有所差异最好还是通过控制代码里的重绘范围解决。6.3 中文显示成方块字体文件路径没生效在linuxfb这类嵌入式平台Qt不像PC端有系统字体管理服务它默认只带少量ASCII字体。中文如果没加载自定义字体就会显示成方块或者问号。我在实际调试中最常见的失误是字体文件路径写错或者文件名大小写不对。Linux文件名区分大小写比如wqy-zenhei.ttc和WQY-ZenHei.ttc完全不同。调试时可以先用一个最简单的测试工程只显示一个QLabel的中文字符串如果正常再往主界面里加内容。另一个小技巧如果使用QFontDatabase::addApplicationFont()加载字体返回值是字体的ID你可以通过QFontDatabase::applicationFontFamilies(id)获取实际的字体族名称再设置QFont时用这个名字。很多情况下文件名和字体族名不完全一致忽略这一点容易设错。6.4 背光控制和后续扩展设备树里配置的gpio-backlight提供了标准的sysfs背光映射。系统起来后通过以下命令控制亮度echo 255 /sys/class/backlight/lcd-backlight/brightness echo 0 /sys/class/backlight/lcd-backlight/brightness如果你的屏幕支持PWM调光可以改用pwm-backlight配合IMX6ULL的PWM模块实现无级调光。不过对于SPI屏使用GPIO开关背光已经能满足大多数需求PWM方案会增加设备树和驱动复杂度。关于扩展这套方案后续可以走两条路。一条是把MSP2402换成带XPT2046触摸的模块硬件上增加几个引脚驱动上使用ads7846或tsc2007Qt的QPA层通过tslib接收触摸事件就能实现全触摸交互。另一条是直接用IMX6ULL的PXP加速但那种方案通常需要改到DRM/KMS框架不是这篇SPI小屏能承载的范围。如果你只是需要一块小屏显示状态和简单菜单现在这套链路已经是性价比最高的路线了。最后再分享一个打包部署的小技巧把环境变量和启动命令写成一个shell脚本放到板子的/etc/init.d/或者/home/root/下开机后一条命令启动#!/bin/sh export QT_QPA_PLATFORMlinuxfb export QT_QPA_FB_DEVICE/dev/fb0 export QT_QPA_FB_NO_LIBINPUT1 /home/root/imx6ull_lcd_demo 这样每次调试节省几秒钟输环境变量的时间。这个脚本我也放在源码包里了拿走就能用。本文还有配套的精品资源点击获取
返回列表