资讯详情

资讯详情

H743+QSPI接口驱动ST77903:LVGL刷屏带宽优化与避坑实践

简介ST77903是矽创SITRONIX推出的无内置RAM小尺寸LCD驱动IC常见于智能手表等穿戴设备。由于内部不带显存QSPI接口必须保持连续传输主机侧需专门线程负责刷屏这套DEMO基于STM32H743 RT-Thread LVGL QSPI搭建了一套完整可运行的工程。压缩包共2591个文件约13.66MB其中以C/H源码为主另有sconscript构建脚本、icf/ld链接脚本及少量png/gif图像资源目录结构清晰方便按模块裁剪和移植。DEMO内包含3个手表表盘界面覆盖LVGL界面框架接入、RT-Thread多线程调度、QSPI连续刷屏机制、底层驱动初始化等关键实现并可直接在H743上运行验证。已有2585人学习/下载本资源适合嵌入式开发者和穿戴设备软件工程师用于入门ST77903驱动开发或作为QSPI小尺寸LCD刷屏方案的工程参考。1. 为什么是 ST77903QSPI 接口把 LVGL 刷屏带出了新带宽在 H743 上跑 LVGL最让人头疼的画面往往不是控件布局而是一块普通 SPI 屏把动画拖成了 PPT。ST77903 这颗矽创 LCD 驱动 IC 支持的 QSPI 四线接口正好补上了接口带宽不够这块短板四根数据线同时搬运数据配合 H743 的 QUADSPI 外设和内存映射模式用五六个引脚就能拿到接近并行 RGB 接口的刷屏体验。这套 DEMO 把 RT-Thread 的线程调度、LVGL 的图形栈、QSPI 的底层时序完整串了起来拿到手可以直接编译下载看效果也能拆开改成自己的产品和面板。适合正在做 H743 LVGL 显示方案、又不想在屏接口上反复翻车的嵌入式工程师。2. 选型与硬件连接四根数据线换来的带宽账2.1 为什么选 ST77903先算带宽选屏驱动 IC 的核心指标不是分辨率而是每帧裸数据传输耗时。ST77903 支持四线 QSPI 传输和传统 SPI LCD 最大的差别在数据通道宽度SPI 一次只能挪 1 个 bitQSPI 能同时挪 4 个 bit。这个差距在 320×480 这类分辨率下是决定性的。一块 320×480、RGB565 的屏一帧裸数据大约 300KB。20MHz 常规 SPI 全速传输要 120ms 以上这还没算写命令、切行等开销跑 LVGL 动画必然卡成幻灯片同样 20MHz 下QSPI 四线能把传输时间压到 30ms 左右算上命令开销也能跑到 25fps 上下。要是把 QSPI 时钟提到 40MHz一帧写入能压进 15msUI 的跟手程度完全不一样。接口有效数据宽度20MHz 理论带宽320×480×16bit 一帧耗时SPI 单线1 bit2.5 MB/s约 120 msQSPI 四线4 bit10 MB/s约 30 msQSPI 四线 40MHz4 bit20 MB/s约 15 ms有人会问为什么不直接上 RGB 并行接口RGB888 需要 24 根数据线加时钟、同步信号占掉的引脚资源够做一路完整的 SDRAM 了在引脚紧张的 H743 板子上很不划算。QSPI 屏用更少的线换走并行接口的带宽优势而且这套 DEMO 里没有外部 QSPI Flash 参与QSPI 外设直接挂载 LCD 本体硬件链路更短问题也更好定位。2.2 硬件接线H743 QUADSPI 引脚映射ST77903 屏模块一般通过 FPC 软排线引出接口引脚命名通常直接标 QSPI 相关信号。H743 的 QUADSPI 外设按 Bank1 的典型接法如下具体以开发板原理图为准信号MCU 引脚方向说明QUADSPI_BK1_IO0PF6双向QSPI 数据线 0QUADSPI_BK1_IO1PF7双向QSPI 数据线 1QUADSPI_BK1_IO2PF8双向QSPI 数据线 2QUADSPI_BK1_IO3PF9双向QSPI 数据线 3QUADSPI_BK1_CLKPF10输出QSPI 时钟QUADSPI_BK1_NCSPF11输出片选低有效除了 QSPI 六根线最少还需要一根硬件复位引脚屏模块上一般标 RESX 或 RST和一路背光控制。ST77903 的 TE 引脚撕裂同步信号建议也接出来后面刷新优化会用到。电源部分必须按模块手册配置IOVCC 和 VCI 电压不能乱加压背光也不要直接从 GPIO 硬拉大电流该加三极管或 MOS 开关就加。2.3 软件栈配合RT-Thread 管调度LVGL 管界面这套 DEMO 的软件结构比较清晰LVGL 负责 UI 树、控件绘制、事件分发RT-Thread 负责提供任务调度和环境基础。LVGL 的lv_timer_handler()不能裸奔它需要稳定的时间心跳。在 RT-Thread 里可以建一个 UI 线程循环里跑lv_timer_handler()再用rt_thread_mdelay(5)让出 CPUtick 则直接绑到系统 tick 上两边时间基准保持一致。内存开销也在这个软件栈里算一算。LVGL 双缓冲 320×480 分辨率下每 16 位色一行 320 像素约 640 字节如果开 40 行双缓冲要 50KB 左右再加上 LVGL 对象池、demo 控件总体建议给到 120KB 以上堆空间。H743 内部 RAM 足够关键是 RT-Thread 工程里别把堆配小否则 UI 初始化到一半就分配失败看起来就是屏幕半边不刷新。3. 核心模块拆解一块像素从 LVGL 到屏上 GRAM 的完整链路3.1 工程目录结构驱动、UI 与业务分开RT-Thread Studio 生成的工程大致是这个结构ST77903 相关文件都放在 applications 下和系统驱动、LVGL 软件包保持独立project ├── applications │ ├── main.c # 入口初始化顺序在这里 │ ├── st77903_qspi.c # ST77903 底层驱动 │ ├── st77903_qspi.h │ ├── lv_port_disp.c # LVGL 显示适配层 │ └── lv_port_disp.h ├── drivers │ └── qspi ├── board ├── packages │ └── LVGL └── rt-thread调用关系从 main 出发先st77903_qspi_init()初始化屏再lv_init()启动 LVGL最后lv_port_disp_init()注册刷新回调。业务界面代码继续往上层放驱动文件不掺 UI 逻辑后面要换屏或换分辨率只动底层驱动和移植层的几个宏。3.2 QSPI 初始化先用间接模式把命令写进去H743 的 QUADSPI 有两种工作模式。初始化阶段用间接模式CPU 通过寄存器逐个发命令显示运行阶段切到内存映射模式把 ST77903 的 GRAM 直接映射到 MCU 地址空间。QSPI 的 HAL 初始化核心参数如下static void MX_QUADSPI_Init(void) { hqspi.Instance QUADSPI; hqspi.Init.ClockPrescaler 4; /* AHB 200MHz / (41) 40MHz */ hqspi.Init.FifoThreshold 4; /* FIFO 阈值 4 字节 */ hqspi.Init.SampleShifting QSPI_SAMPLE_SHIFTING_NONE; hqspi.Init.FlashSize 19; /* 2^19 512KB覆盖屏的 GRAM */ hqspi.Init.ChipSelectHighTime QSPI_CS_HIGH_TIME_5_CYCLE; hqspi.Init.ClockMode QSPI_CLOCK_MODE_0; hqspi.Init.FlashID QSPI_FLASH_ID_1; hqspi.Init.DualFlash QSPI_DUALFLASH_DISABLE; HAL_QSPI_Init(hqspi); }参数说明ClockPrescaler决定 QSPI 时钟频率分频值越大越慢越稳定白屏排查时优先把它调大FlashSize是容量地址位宽按 2 的幂计算这个 DEMO 里没有外部 Flash填的是 ST77903 内部 GRAM 的大小ChipSelectHighTime控制片选释放后的高电平持续时间太短会导致连续命令间隔不够表现为偶发写不进命令。3.3 ST77903 初始化序列复位、SLPOUT、COLMODST77903 是 MIPI DBI 风格的驱动 IC命令集和常见屏驱同源。底层命令发送用间接模式的 QSPI 命令结构体拼出来static void st77903_cmd(uint8_t cmd, uint8_t *data, uint32_t len) { QSPI_CommandTypeDef qcmd {0}; qcmd.InstructionMode QSPI_INSTRUCTION_1_LINE; /* 命令段走单线 */ qcmd.Instruction cmd; qcmd.AddressMode QSPI_ADDRESS_NONE; qcmd.DataMode (len 0) ? QSPI_DATA_4_LINES : QSPI_DATA_NONE; qcmd.NbData len; HAL_QSPI_Command(hqspi, qcmd, 1000); if (len 0) { HAL_QSPI_Transmit(hqspi, data, len, 1000); } }注意InstructionMode是单线、DataMode是四线两种线宽在同一帧命令里共存这是 QSPI LCD 和 QSPI Flash 驱动的明显区别。初始化序列一般按下面这个顺序走static void st77903_init_sequence(void) { st77903_hw_reset(); /* RESX 拉低 10ms 再拉高 */ st77903_cmd(0x01, NULL, 0); /* SWRESET 软件复位 */ rt_thread_mdelay(120); st77903_cmd(0x11, NULL, 0); /* SLPOUT 退出休眠 */ rt_thread_mdelay(120); st77903_cmd(0x3A, (uint8_t[]){0x55}, 1); /* COLMOD RGB565 16bit */ st77903_cmd(0x36, (uint8_t[]){0x00}, 1); /* MADCTL 默认扫描方向 */ st77903_cmd(0x29, NULL, 0); /* DISPON 打开显示 */ }参数说明0x01 软件复位后必须等待序列要求给足延时再做后续命令0x3A 配 0x55 表示 RGB565这个值和 LVGL 的LV_COLOR_DEPTH 16必须对上否则颜色直接错乱0x36 是方向控制寄存器屏反过来显示或者需要旋转 90° 时改它的值比重写坐标计算简单得多。屏厂一般会给一份完整的寄存器初始化表比这个公共序列长得多实际项目里把那张表按同样的命令发送方式灌进去即可。3.4 显存映射把 GRAM 搬到 0x90000000初始化完成、显示开启后切到内存映射模式这是让刷屏性能起飞的关键一步static void st77903_enter_memory_mapped(void) { QSPI_MemoryMappedTypeDef mem {0}; mem.TimeOutActivation QSPI_TIMEOUT_COUNTER_DISABLE; mem.TimeOutPeriod 0; HAL_QSPI_MemoryMapped(hqspi, mem, 1000); /* 成功后ST77903 的 GRAM 映射到 0x90000000 起始地址 */ }这个模式下直接向 0x90000000 地址写数据等效于向屏发 RAMWR 命令写显存。H743 的 QSPI 映射区按 MCU 地址总线访问memcpy就能刷一整块颜色数据不再需要逐包拼命令CPU 负担大幅下降。切模式有个顺序问题要先记住执行HAL_QSPI_MemoryMapped()之后系统再调用HAL_QSPI_Command()就会出错因为外设已经切走了。需要回间接模式时得先HAL_QSPI_Abort()再重新初始化。很多二次开发的人在这里翻车后面避坑部分会细说。3.5 LVGL 移植flush_cb 里的逐行拷贝LVGL 的显示适配集中在lv_port_disp_init()和刷新回调里。双缓冲配置如下#define LCD_HOR_RES 320 #define LCD_VER_RES 480 #define LCD_BUF_SIZE (LCD_HOR_RES * 40) /* 40 行像素 */ static lv_disp_draw_buf_t draw_buf; static lv_color_t buf1[LCD_BUF_SIZE]; static lv_color_t buf2[LCD_BUF_SIZE]; void lv_port_disp_init(void) { lv_disp_draw_buf_init(draw_buf, buf1, buf2, LCD_BUF_SIZE); static lv_disp_drv_t disp_drv; lv_disp_drv_init(disp_drv); disp_drv.hor_res LCD_HOR_RES; disp_drv.ver_res LCD_VER_RES; disp_drv.flush_cb disp_flush_cb; disp_drv.draw_buf draw_buf; lv_disp_drv_register(disp_drv); }刷新回调负责把 LVGL 绘制好的一块区域搬到液晶屏。因为已经进入映射模式直接按行偏移算地址拷贝static void disp_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_p) { uint32_t y; uint32_t line_bytes (area-x2 - area-x1 1) * sizeof(lv_color_t); for (y area-y1; y area-y2; y) { uint32_t offset (y * LCD_HOR_RES area-x1) * sizeof(lv_color_t); memcpy((void *)(0x90000000 offset), (uint8_t *)color_p (y - area-y1) * line_bytes, line_bytes); } lv_disp_flush_ready(drv); }逻辑说明LVGL 回传的area是要刷新的矩形区域color_p是该区域左上角的像素首地址。回调按行把区域内的像素拷贝到 GRAM 的对应偏移一次刷一个矩形比整屏重绘快得多。line_bytes用的是sizeof(lv_color_t)这是 RGB565 下的 2 字节如果换成 RGB888这里也要跟着变。映射模式绕开了 CASET/RASET 窗口命令直接按地址写所以整个链路里不需要再维护窗口坐标。4. 编译烧录跑通从 RT-Thread Studio 建工程到整屏渐变4.1 新建工程并配置 QSPI 引脚打开 RT-Thread Studio新建 STM32H743 系列工程选一个基于 H743 的 BSP 作为起点。工程生成后第一步是确认 QSPI 的 GPIO 复用是否配置好。PF6 到 PF11 这六根引脚要设置为 AF10QUADSPI复用功能推挽输出、高速、带上拉。最省事的方式是先用 CubeMX 配一遍引脚和外设生成初始化代码后再把MX_QUADSPI_Init()搬回 RT-Thread 工程。注意 CubeMX 生成的代码默认假设了 HSE 时钟H743 板子的外部晶振一般是 25MHz如果 board 文件里配的是 8MHz整个系统时钟都会偏QSPI 的 40MHz 也全乱套。所以先把串口 printf 调通确认 RT-Thread 启动日志里的节拍正常再继续做屏。4.2 引入 LVGL 软件包并调整配置在工程配置里打开 RT-Thread Settings在软件包中心找到 LVGL勾选后同步到工程。版本建议先用 v8 系列资料多demo 齐全v9 接口有调整移植层要跟着改。LVGL 包几个关键配置颜色深度设置为 16 bit与 ST77903 的 RGB565 对应打开 demo 示例默认会带lv_demo_widgets()等演示界面内存管理使用 LVGL 自带的内存池池大小建议配置到 64KB 以上勾选软件定时器GPU 加速等选项按需开开了不用反而占资源。把st77903_qspi.c、lv_port_disp.c放进 applications 目录后在 main 里按顺序初始化。RT-Thread 的 main 线程栈默认可能只有 4096 字节LVGL demo 跑起来后栈压力大建议把主线程栈调到 8192 或 16384堆配置也一并核对别让 LVGL 对象分配时直接返回 NULL。4.3 编译烧录与验证工程编译通过后通过 SWD 调试器下载到板子。上电后串口应该先看到 RT-Thread 的启动 logo然后跟着 LVGL 的版本打印和 demo 初始化日志。如果一切正常屏幕会显示 LVGL 官方的 widget 示例界面滑动、进度条、图表都能动起来。验证链路是否畅通一个小技巧是手动刷全屏渐变。初始化完成后直接向映射地址写一段颜色渐变的 buffer如果渐变从上到下连续、无跳变说明 QSPI 时钟和行偏移都对如果出现斜向撕裂或者颜色断层优先检查ClockPrescaler和MADCTL。确认屏的底子没问题再跑 LVGL demo这时候出现任何花色都往 LVGL 层去查。5. 避坑记录白屏、花屏、撕裂和 HardFault 的排查顺序5.1 白屏加串口无报错先把 QSPI 时钟分频调大现象初始化日志正常打印LVGL 也报准备好了但屏幕一直白屏或者灰屏用手电照能看到隐约有图像残影。原因白屏且日志正常最常见的是 QSPI 时钟频率超过了 ST77903 的承受范围。初始化序列本身可能已经错了但屏驱芯片对关键命令的时序有容忍度只有写显存这种持续高速传输时才暴露。另一种常见原因是板子电平不匹配IOVCC 接错电压会让屏逻辑接口直接不工作。解决先把ClockPrescaler从 4 改到 8让 QSPI 时钟降到 22MHz 左右重新烧录。如果白屏变正常说明就是时钟过快。确认能用后再把分频往小调一档一档提直到出现花屏再回退一档这就是这套板子能稳定跑的最高时钟。我一般会顺手在初始化末尾加一个读 ID 函数读回来全 0xFF 就别再往上调时钟了。5.2 颜色错乱红色显示成蓝色先查 RGB/BGR 交换现象LVGL 控件能正常显示但所有颜色都偏色红色和蓝色完全交换绿色正常。原因ST77903 的MADCTL寄存器里有一个 BGR 位决定像素接口按 RGB 还是 BGR 解释。屏厂模块出厂时可能已经把 BGR 位设过了LVGL 那边又按默认的 RGB565 解释两边一叠加就出了红蓝互换。解决两个地方都能改。底层的改法是初始化序列里给 0x36 寄存器加一字节0x08或0xC8把 BGR 位置 1具体按扫描方向组合上层改法是打开 LVGL 的LV_COLOR_16_SWAP宏让 LVGL 在输出前交换高低字节。我习惯先改屏侧寄存器这样 LVGL 层保持标准配置后面换 UI 代码不用跟着动。改完烧录看红色控件是否恢复正常一次只改一边别两边同时动否则又变成另一种错乱。5.3 刷新撕裂动画时出现水平横带现象控件拖动时屏幕中间有一条不断移动的撕裂带越靠近屏幕下部越明显静止时完全正常。原因刷新回调在 ST77903 的 GRAM 写入和屏内部的行扫描不同步。LVGL 把新一帧画进 buffer 的时候屏还在上一帧的扫描中两帧画面同时显示就撕裂了。单缓冲时最容易出现双缓冲只能缓解不能根除。解决把 ST77903 的 TE 引脚接到 H743 的输入捕获或外部中断引脚初始化序列里开启 TEON0x35等待 TE 信号进入 V-blanking 窗口后再调lv_disp_flush_ready()。这样 LVGL 只在屏不扫描的时候换帧。如果板子没接 TE退而求其次是让刷新回调里整帧一次性写完避免分多次小区域刷新也能明显减少撕裂。5.4 HardFault进入映射模式后乱调 HAL_QSPI_Command现象程序在 LVGL 跑起来后随机进入 HardFault_Handler有时刚进 demo 就崩有时跑几个小时后才崩错误调用栈指到 memcpy。原因映射模式激活期间HAL_QSPI_Command 会去改 QUADSPI 的配置寄存器这会把正在工作的外设打断触发总线错误。另外如果向映射区写的地址越过了FlashSize配置的地址范围也会直接 HardFault。320×480 的屏偏移算到末尾时刚好卡在 512KB 边界上一不小心就越界。解决强制约定一个规则——映射模式和间接模式严格互斥驱动层封装一个st77903_switch_mode()函数所有要发命令的地方先退出映射模式发完再切回来。HAL_QSPI_Command()调用前统一走这个开关。另外把FlashSize按 2^19 算好不要给 480×800 这种大分辨率屏留不够。从那以后我每次调显示驱动都先检查一遍映射模式下有没有人偷偷调命令函数。5.5 整屏渐变显示但 LVGL 控件不刷新检查 flush_ready 是否漏调用现象手动写渐变能正常显示但 LVGL 创建控件后屏幕永远停在第一帧再不动画。原因disp_flush_cb里拷贝完数据后没有调用lv_disp_flush_ready(drv)LVGL 一直认为上一帧还没发完后续帧全部排队等待表现为界面卡死在第一帧。这个坑在把裸机 LVGL 例程改成 RT-Thread 线程版时特别容易踩因为拷贝动作移到了线程里flush_ready 也跟着丢在了原函数外面。解决确保刷新回调的所有返回路径都执行lv_disp_flush_ready(drv)。如果有 QSPI 传输失败提前 return 的分支也要在那里先 flush_ready。我把这个调用放在回调最末尾统一执行任何中间跳转都要经过它。检查方法简单粗暴在回调里加一个计数器跑 demo 看串口计数是否持续增长。6. 二次开发改分辨率、加触摸和再把帧率往上顶一截6.1 改分辨率和加触摸的联动点把 320×480 换成 480×800 的型号时要同步改四处LCD_HOR_RES、LCD_VER_RES、FlashSize按新分辨率重算、以及初始化序列里的窗口相关寄存器。触摸板一般是 SPI 或 I2C 接口的 GT911 或 XPT2046 方案接好中断引脚后在 LVGL 里注册一个indev驱动read_cb里把触摸坐标填进lv_indev_data_t顺手调lv_indev_drv_update()就行。这里最容易忽略的是触摸坐标的旋转方向要和屏的MADCTL方向一致否则 UI 朝上触摸却是横着的。6.2 性能优化三板斧第一QSPI 时钟分频逐步调低从 40MHz 往 50MHz 试每档都要整帧渐变换色验证稳定性第二把 LVGL buffer 从 40 行加到 80 行减少刷新回调次数第三数据搬运交给 DMA2Ddisp_flush_cb里用 DMA2D 的 memory-to-memory 模式把color_p拷到 0x90000000CPU 只在启动 DMA 和等中断时占用传输过程可以干别的线程。DMA2D 地址要求 4 字节对齐映射区地址没问题LVGL 的 buffer 对齐需要留意不行就在 lv_conf.h 里开LV_ATTRIBUTE_MEM_ALIGN。6.3 一个收尾习惯这套工程我后来在别的项目上也复用了几次最大的习惯是任何一次新屏上板都强制走一遍最低时钟 → 读 ID → 整屏渐变 → LVGL 刷图 → 加触摸的顺序每一步都在串口打日志确认一步再走下一步。这样做能在一分钟之内定位到底是时钟、命令、方向还是适配层的问题省掉了很多玄学排查时间。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →