Files
motor_ili9341/SystemDrivers/README_ILI9341.md
T
zhy200308 1273a14bf2 ILI9341: 刷新改为 DMA+任务内同步等待,消除移动残留竞态
- 中断只置完成标志,等待/收尾/回调全部移到任务上下文
- 去掉 ISR 内 lv_disp_flush_ready、ISR 内 Abort、双重收尾等竞态
- flush 返回时该块像素必已完整写入,LVGL 不会提前复用缓冲
- 拉高 CS 前等待 SPI 移位排空,避免尾部像素被截断
- 传输 50ms 超时后重建 SPI 自恢复
2026-07-08 11:16:57 +08:00

111 lines
5.5 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**;② 传输超时后重建 SPI 自恢复,单次异常不级联 |
| 刷新时序/竞态 | 中断里 `lv_disp_flush_ready`、ISR 内 `Abort`、完成被收尾两次等竞态,会丢失“擦除”写入 → 移动物体残留 | **DMA 加速 + 任务内同步等待**:中断只置完成标志,等待/收尾/回调全在任务里做;flush 返回时该块像素一定已完整写入,杜绝提前复用缓冲/擦除未写完 |
| 字节序靠 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
→ 任务内轮询等待 lcd_xfer_done1~2ms50ms 超时兜底)
→ 等 SPI 移位排空 → CS 高 → done_cb(=lv_disp_flush_ready)
→ [DMA 完成] SPI1 中断 → HAL_SPI_TxCpltCallback → 仅置 lcd_xfer_done=1
→ [出错] HAL_SPI_ErrorCallback → 记录错误 + 置 lcd_xfer_done=1,任务侧恢复
→ [兜底] 超时(或 ServiceWatchdog) → Abort + 重建 SPI
```
关键不变量:`flush_cb` **返回时该块像素一定已完整写入面板**LVGL 不会提前复用
缓冲,“擦除”写入不会丢失。中断只做“置标志”一件事,不在中断里调用 LVGL、不在
中断里 `Abort`,从根上消除竞态。
---
## 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`。