1273a14bf2
- 中断只置完成标志,等待/收尾/回调全部移到任务上下文 - 去掉 ISR 内 lv_disp_flush_ready、ISR 内 Abort、双重收尾等竞态 - flush 返回时该块像素必已完整写入,LVGL 不会提前复用缓冲 - 拉高 CS 前等待 SPI 移位排空,避免尾部像素被截断 - 传输 50ms 超时后重建 SPI 自恢复
111 lines
5.5 KiB
Markdown
111 lines
5.5 KiB
Markdown
# 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_done(1~2ms,50ms 超时兜底)
|
||
→ 等 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. 对外 API(ILI9341.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`。
|