Files
motor_ili9341/SystemDrivers/README_ILI9341.md
T
zhy200308 fd6c18c4c7 ILI9341: 增加 DMA/纯阻塞 两种传输 + 每次 flush 日志,便于定位错位/残留
- ILI9341_USE_DMA 开关:1=DMA+同步等待,0=纯阻塞SPI(不用DMA),同一入口
- ILI9341_LOG_EN/LOG_EVERY:每次(或每N次)flush 打印 方式/区域/字节/耗时/
  错误码/累计错误/超时/缓冲地址(判断是否落在AXI SRAM)
- DWT 微秒计时;纯阻塞路径无 D-Cache/DMA 一致性问题,用于对照
- README 增加两种模式对照判读表
2026-07-08 11:40:20 +08:00

156 lines
8.1 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`
---
## 4.5 诊断:DMA vs 纯阻塞 + 日志(排查错位/残留)
`ILI9341.h` 顶部有三个开关:
```c
#define ILI9341_USE_DMA 1 // 1=DMA搬运(+任务内同步等待) 0=纯阻塞SPI(不用DMA)
#define ILI9341_LOG_EN 1 // 1=每次flush打印一行日志(UART4)
#define ILI9341_LOG_EVERY 1U // 每N次flush打印一次(串口太卡就调大,如 8/16)
```
两种传输走**同一个入口** `ILI9341_FlushArea_DMA()`,只是内部实现不同,
所以切换 `ILI9341_USE_DMA` 后其它代码无需改动,直接对比现象与日志即可判断
“错位/残留”到底和 DMA / D-Cache 有没有关系。
日志格式(UART4,115200 或工程设定波特率):
```
[LCD] DMA #1234 (20,115 46x40) len=3680 t=210us fail=0 err=0x0 eC=0 toC=0 buf=0x24000000
│ │ │ │ │ │ │ │ │ │ └ 数据缓冲地址:必须是 0x24xxxxxx(AXI SRAM)
│ │ │ │ │ │ │ │ │ └ 累计超时次数 若是 0x2000xxxx(DTCM) 说明 scatter 没生效
│ │ │ │ │ │ │ │ └ 累计错误次数
│ │ │ │ │ │ │ └ 本次 SPI 错误码(HAL ErrorCode)
│ │ │ │ │ │ └ 本次是否失败(1=超时/出错)
│ │ │ │ │ └ 本次传输耗时(微秒, DWT 计时)
│ │ │ │ └ 本次字节数
│ │ │ └ 区域宽x高
│ │ └ 区域左上角(x,y)
│ └ flush 序号
└ 传输方式: DMA / POLL
```
**怎么判断:**
| 现象 | 结论 |
|---|---|
| `POLL` 模式画面**干净**、`DMA` 模式**错位** | 问题在 DMA / D-Cache 一致性(缓冲没 clean 干净、或缓冲不在 AXI) |
| 两种模式**都错位** | 与 DMA 无关,是**面板刷新撕裂**(需 TE 同步)或上层逻辑 |
| 日志里 `buf=0x2000xxxx` | scatter 没生效,缓冲落在 DTCM,DMA 读到错/旧数据 → 先修内存放置 |
| `err=` 非 0 或 `eC` 持续增长 | SPI 总线错误(接线/速率/干扰),先降 `BaudRatePrescaler` 验证 |
| `toC` 增长 | 传输超时,DMA/中断未触发,查 NVIC/时钟 |
| `t=` 远大于预期(如 >20ms) | SPI 太慢/被阻塞,撕裂概率大增 |
> 串口日志是**阻塞发送**,会显著拖慢刷新、改变时序。判断完请把 `ILI9341_LOG_EN`
> 关回 0(或把 `ILI9341_LOG_EVERY` 调大),否则时序失真、也会掩盖真实表现。
## 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`。