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

109 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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;② 阻塞式信号量 + 出错 `Abort`,SPI 状态一乱就级联白屏 | ① 缓冲用 `.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 @0x20000000`(DTCM):普通变量/堆栈,**不放** DMA 缓冲。
- `RW_IRAM2 @0x24000000`AXI 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.c``buf_1/buf_2`
> 在 Keil 里若手改内存布局,请始终保证 `.lcd_ram` 落在 AXI SRAM。
---
## 4. 方向/镜像调节
方向完全由 `ILI9341_SetRotation()` 的 MADCTL 决定(默认 `1` = 横屏 320×240
`MADCTL=0x28`)。若烧录后镜像/上下颠倒,只需换 MADCTL:
- 横屏:`0x28``0xE8`
- 竖屏:`0x48``0x88`
换方向后如果分辨率变了,同步修改 `lv_port_disp.c`
`MY_DISP_HOR_RES / MY_DISP_VER_RES`
---
## 5. 参数与调优
- `DISP_BUF_LINES`lv_port_disp.c,默认 20):单块缓冲行数。越大单次 DMA 越长、
刷新越连续;越小越省 RAM。两块共 `320*行数*2*2` 字节。DMA 单次字节数须 < 65535。
- `ILI9341_ServiceWatchdog(180)`freertos.c 任务循环):超时(ms)兜底恢复,建议
150~200ms。正常刷新根本走不到看门狗,它只处理硬件级卡死。
- SPI 速度:`spi.c``BaudRatePrescaler`。残影/花屏排查时可先降速验证接线。
---
## 6. 对外 APIILI9341.h
```c
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_DMA``data` 必须已是面板字节序(由 `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`。