Files
motor_ili9341/SystemDrivers/README_ILI9341.md
T
zhy200308 e4d61b19f0 重构 ILI9341+LVGL 驱动:异步 DMA + 硬件旋转,修复残影与白屏
- 硬件 MADCTL 旋转,删除软件转置,LVGL 坐标与面板 1:1,消除移动残影
- 全异步 DMA:flush 启动即返回,完成/出错由 SPI 中断收尾,去掉信号量阻塞
- DMA 缓冲经 .lcd_ram 段强制放入 AXI SRAM,规避 DTCM 不可被 DMA 访问导致的白屏
- LV_COLOR_16_SWAP=1,LVGL 直出面板字节序,省去逐像素交换/拷贝
- 看门狗超时重建 SPI 自恢复,单次异常不再级联长时间白屏
- 定制 scatter(motor_ili9341_lcd.sct) 并接入 uvprojx
- 新增 README_ILI9341.md 说明根因/设计/构建验证
2026-07-08 11:02:42 +08:00

5.2 KiB
Raw Blame History

ILI9341 + LVGL 驱动说明(重写版)

STM32H750 · SPI1 · DMA1_Stream2 · LVGL v8

本次重写的目标:彻底解决移动物体残留/拖影长时间运行白屏, 并让 SPI/DMA 时序自同步、可稳定构建运行。


1. 问题根因与对应改动

旧版问题 根因 新版做法
移动方块留下残影/线条 disp_flush() 里做软件 90° 转置,坐标/尺寸与 LVGL 失效区域不严格一致,且多一层 rot_buf→dma_buf 拷贝 硬件旋转:面板 MADCTL 直接设横屏,LVGL 坐标 = 面板坐标,刷新区与失效区严格 1:1,转置整段删除
长时间运行白屏 ① DMA 源缓冲可能被链接器放进 DTCM,而 DMA1 无法访问 DTCM;② 阻塞式信号量 + 出错 AbortSPI 状态一乱就级联白屏 ① 缓冲用 .lcd_ram强制放入 AXI SRAM;② 全异步 DMA,去掉信号量;出错回调 + 看门狗自恢复,单次异常不级联
刷新与 CPU 抢时间 flush_cb 阻塞等待传输完成 flush_cb 启动 DMA 后立即返回,双缓冲边渲染边传,完成中断里 lv_disp_flush_ready()
字节序靠 CPU 逐像素交换 LV_COLOR_16_SWAP=0,驱动里手工打包 LV_COLOR_16_SWAP=1,LVGL 直接产出面板字节序,DMA 直接搬运

备注:旧 ILI9341.h 里关于 “CS=PB12 未配置为输出” 的警告是过时的—— main.h/gpio.c 已把 PB12 配为推挽输出,CS 时序正常,无需改动。


2. 数据通路

LVGL 渲染 → buf_1/buf_2 (.lcd_ram, AXI SRAM, 32B 对齐)
          → disp_flush()  : 裁剪坐标,直接调 ILI9341_FlushArea_DMA()
          → FlushArea_DMA : 设窗口(2A/2B/2C) → clean D-Cache → CS 低 → 启动 SPI DMA,立即返回
          → [DMA 完成] SPI1 中断 → HAL_SPI_TxCpltCallback → CS 高 → lv_disp_flush_ready()
          → [出错]     HAL_SPI_ErrorCallback → Abort → 收尾 → flush_ready(不卡死)
          → [兜底]     任务里 ILI9341_ServiceWatchdog(180) 超时中止+重建 SPI

关键不变量:任意时刻至多一次 DMA 在途LVGL 在 flush_ready 前不会再次 调用 flush_cb)。收尾函数 LCD_FinishTransfer() 在关中断临界区内“取回调+复位 状态”,保证 done_cb 只被调用一次,杜绝 ISR 与看门狗竞争。


3. 内存放置(最关键,务必确认)

STM32H7 的坑:DMA1/DMA2 无法访问 DTCM(0x20000000),能访问 AXI SRAM(0x24000000)。

  • 自定义链接脚本:MDK-ARM/motor_ili9341_lcd.sct
    • RW_IRAM1 @0x20000000DTCM):普通变量/堆栈,不放 DMA 缓冲。
    • RW_IRAM2 @0x24000000AXI SRAM):*(.lcd_ram) 显式放这里 + 其余 RW。
  • 工程已改用该脚本:motor_ili9341.uvprojx<umfTarg>0</umfTarg> + <ScatterFile>.\motor_ili9341_lcd.sct</ScatterFile>
  • 所有交给 DMA 的缓冲都用 LCD_RAM_ATTR= .lcd_ram 段 + 32B 对齐)修饰, 见 lv_port_disp.cbuf_1/buf_2

在 Keil 里若手改内存布局,请始终保证 .lcd_ram 落在 AXI SRAM。


4. 方向/镜像调节

方向完全由 ILI9341_SetRotation() 的 MADCTL 决定(默认 1 = 横屏 320×240 MADCTL=0x28)。若烧录后镜像/上下颠倒,只需换 MADCTL:

  • 横屏:0x280xE8
  • 竖屏:0x480x88

换方向后如果分辨率变了,同步修改 lv_port_disp.cMY_DISP_HOR_RES / MY_DISP_VER_RES


5. 参数与调优

  • DISP_BUF_LINESlv_port_disp.c,默认 20):单块缓冲行数。越大单次 DMA 越长、 刷新越连续;越小越省 RAM。两块共 320*行数*2*2 字节。DMA 单次字节数须 < 65535。
  • ILI9341_ServiceWatchdog(180)freertos.c 任务循环):超时(ms)兜底恢复,建议 150~200ms。正常刷新根本走不到看门狗,它只处理硬件级卡死。
  • SPI 速度:spi.cBaudRatePrescaler。残影/花屏排查时可先降速验证接线。

6. 对外 APIILI9341.h

void     ILI9341_Init(void);                 // 初始化 + 默认横屏
void     ILI9341_SetRotation(uint8_t r);     // 0/1/2/3
void     ILI9341_FillScreen(uint16_t color); // 阻塞,初始化清屏用
void     ILI9341_FillRect(...);              // 阻塞
uint16_t ILI9341_Color565(r,g,b);
void     ILI9341_FlushArea_DMA(x,y,w,h,data,done_cb,user); // 异步,LVGL 用
uint8_t  ILI9341_IsBusy(void);
void     ILI9341_ServiceWatchdog(uint32_t timeout_ms);     // 任务里周期调用

FlushArea_DMAdata 必须已是面板字节序(由 LV_COLOR_16_SWAP=1 保证), 且位于 DMA 可访问内存。


7. 构建与验证清单

  1. Keil 打开工程 → 确认 Options → Linker 使用 motor_ili9341_lcd.sct(本工程已配置)。
  2. 编译无警告地通过;.map 里确认 buf_1/buf_2/.lcd_ram 地址在 0x24xxxxxx
  3. 烧录后:
    • 蓝色方块左右移动无残留线条/拖影
    • 长时间(数十分钟以上)运行不白屏、不冻结
    • 若镜像/颠倒,按第 4 节调 MADCTL。

本仓库环境无法编译/烧录 STM32,以上第 2、3 步需在 Keil + 硬件上确认。 第 3 节的内存放置是唯一强依赖工具链的点,请务必核对 .map