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 增加两种模式对照判读表
This commit is contained in:
+132
-24
@@ -1,4 +1,5 @@
|
|||||||
#include "ili9341.h"
|
#include "ili9341.h"
|
||||||
|
#include "uart4.h" /* Debug_Printf:刷新日志 */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ILI9341 驱动 —— 全异步 DMA 重写版
|
* ILI9341 驱动 —— 全异步 DMA 重写版
|
||||||
@@ -42,6 +43,34 @@ static volatile uint8_t lcd_xfer_done = 1; /* 1=空闲/已完成 */
|
|||||||
static volatile uint32_t lcd_spi_err = 0; /* 最近一次 SPI 错误码 */
|
static volatile uint32_t lcd_spi_err = 0; /* 最近一次 SPI 错误码 */
|
||||||
static volatile uint32_t lcd_start_tick = 0;
|
static volatile uint32_t lcd_start_tick = 0;
|
||||||
|
|
||||||
|
/* ---- 诊断统计 ---- */
|
||||||
|
static uint32_t lcd_flush_cnt = 0; /* flush 总次数 */
|
||||||
|
static uint32_t lcd_err_cnt = 0; /* SPI 错误次数 */
|
||||||
|
static uint32_t lcd_timeout_cnt = 0; /* 传输超时次数 */
|
||||||
|
|
||||||
|
/* ================= DWT 微秒计时(用于日志里的传输耗时) ================= */
|
||||||
|
static void LCD_DWT_Init(void)
|
||||||
|
{
|
||||||
|
#if defined(DWT) && defined(CoreDebug)
|
||||||
|
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
|
||||||
|
DWT->CYCCNT = 0;
|
||||||
|
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t LCD_Micros(uint32_t start_cyc)
|
||||||
|
{
|
||||||
|
#if defined(DWT)
|
||||||
|
uint32_t now = DWT->CYCCNT;
|
||||||
|
uint32_t diff = now - start_cyc; /* 32 位自然回绕 */
|
||||||
|
uint32_t cyc_per_us = SystemCoreClock / 1000000U;
|
||||||
|
return (cyc_per_us != 0U) ? (diff / cyc_per_us) : diff;
|
||||||
|
#else
|
||||||
|
(void)start_cyc;
|
||||||
|
return 0U;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/* ================= 阻塞式低层写命令/数据(仅用于初始化与设窗口) ================= */
|
/* ================= 阻塞式低层写命令/数据(仅用于初始化与设窗口) ================= */
|
||||||
|
|
||||||
static void LCD_WriteCmd(uint8_t cmd)
|
static void LCD_WriteCmd(uint8_t cmd)
|
||||||
@@ -98,6 +127,8 @@ void ILI9341_Init(void)
|
|||||||
LCD_CS_HIGH();
|
LCD_CS_HIGH();
|
||||||
LCD_BL_ON();
|
LCD_BL_ON();
|
||||||
|
|
||||||
|
LCD_DWT_Init();
|
||||||
|
|
||||||
LCD_Reset();
|
LCD_Reset();
|
||||||
|
|
||||||
LCD_WriteCmd(0x01); /* Software reset */
|
LCD_WriteCmd(0x01); /* Software reset */
|
||||||
@@ -234,26 +265,15 @@ static void LCD_SPI_WaitShiftEmpty(void)
|
|||||||
__DSB();
|
__DSB();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ================= LVGL 刷新入口(DMA 加速,任务内同步等待完成) ================= */
|
/* ================= 两种传输实现 =================
|
||||||
|
* 均为“同步返回”:函数返回时该块像素一定已完整写入面板。
|
||||||
|
* 返回值:0=成功,1=超时/出错(已尽力恢复)。 */
|
||||||
|
|
||||||
void ILI9341_FlushArea_DMA(uint16_t x, uint16_t y,
|
/* (A) DMA 传输 + 任务内同步等待 */
|
||||||
uint16_t w, uint16_t h,
|
static uint8_t LCD_Xfer_DMA(const uint16_t *data, uint32_t byte_len)
|
||||||
const uint16_t *data,
|
|
||||||
ILI9341_FlushDoneCb done_cb,
|
|
||||||
void *user_data)
|
|
||||||
{
|
{
|
||||||
uint32_t byte_len;
|
|
||||||
uint32_t t0;
|
uint32_t t0;
|
||||||
|
uint8_t fail = 0;
|
||||||
if (w == 0 || h == 0 || data == NULL)
|
|
||||||
{
|
|
||||||
if (done_cb) done_cb(user_data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte_len = (uint32_t)w * h * 2U;
|
|
||||||
|
|
||||||
LCD_SetWindow(x, y, x + w - 1U, y + h - 1U);
|
|
||||||
|
|
||||||
/* DMA 读取前 clean D-Cache,确保面板拿到最新像素 */
|
/* DMA 读取前 clean D-Cache,确保面板拿到最新像素 */
|
||||||
LCD_CleanDCache(data, byte_len);
|
LCD_CleanDCache(data, byte_len);
|
||||||
@@ -269,32 +289,120 @@ void ILI9341_FlushArea_DMA(uint16_t x, uint16_t y,
|
|||||||
{
|
{
|
||||||
lcd_xfer_done = 1;
|
lcd_xfer_done = 1;
|
||||||
LCD_CS_HIGH();
|
LCD_CS_HIGH();
|
||||||
if (done_cb) done_cb(user_data);
|
return 1;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 任务内同步等待:中断只把 lcd_xfer_done 置 1,这里轮询等待。
|
/* 中断只把 lcd_xfer_done 置 1,这里轮询等待;50ms 超时兜底。 */
|
||||||
* 传输一小块通常 1~2ms 即完成;50ms 超时兜底防止硬件卡死。
|
|
||||||
* 这样每次 flush 返回时,这块像素一定已经完整写入面板——不会出现
|
|
||||||
* “LVGL 提前复用缓冲 / 擦除区没写完”导致的移动残留。 */
|
|
||||||
t0 = HAL_GetTick();
|
t0 = HAL_GetTick();
|
||||||
while (lcd_xfer_done == 0U)
|
while (lcd_xfer_done == 0U)
|
||||||
{
|
{
|
||||||
if ((HAL_GetTick() - t0) > 50U)
|
if ((HAL_GetTick() - t0) > 50U)
|
||||||
{
|
{
|
||||||
HAL_SPI_Abort(&hspi1);
|
HAL_SPI_Abort(&hspi1);
|
||||||
/* 传输异常,重建 SPI,避免坏状态带入后续帧 */
|
|
||||||
if (HAL_SPI_DeInit(&hspi1) == HAL_OK)
|
if (HAL_SPI_DeInit(&hspi1) == HAL_OK)
|
||||||
{
|
{
|
||||||
HAL_SPI_Init(&hspi1);
|
HAL_SPI_Init(&hspi1);
|
||||||
}
|
}
|
||||||
lcd_xfer_done = 1;
|
lcd_xfer_done = 1;
|
||||||
|
lcd_timeout_cnt++;
|
||||||
|
fail = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LCD_SPI_WaitShiftEmpty();
|
LCD_SPI_WaitShiftEmpty();
|
||||||
LCD_CS_HIGH();
|
LCD_CS_HIGH();
|
||||||
|
return fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (B) 纯阻塞 SPI(完全不用 DMA,CPU 直推)
|
||||||
|
* CPU 经缓存读数据,无 DMA/D-Cache 一致性问题;用于对照排查。 */
|
||||||
|
static uint8_t LCD_Xfer_Poll(const uint16_t *data, uint32_t byte_len)
|
||||||
|
{
|
||||||
|
HAL_StatusTypeDef st;
|
||||||
|
|
||||||
|
lcd_xfer_done = 1; /* 阻塞方式无“在途”状态 */
|
||||||
|
lcd_spi_err = 0;
|
||||||
|
lcd_start_tick = HAL_GetTick();
|
||||||
|
|
||||||
|
LCD_CS_LOW();
|
||||||
|
LCD_DC_DATA();
|
||||||
|
|
||||||
|
/* byte_len 上限 = 单块缓冲字节数 < 65535,可一次发完 */
|
||||||
|
st = HAL_SPI_Transmit(&hspi1, (uint8_t *)data, (uint16_t)byte_len, 100);
|
||||||
|
|
||||||
|
LCD_SPI_WaitShiftEmpty();
|
||||||
|
LCD_CS_HIGH();
|
||||||
|
|
||||||
|
return (st == HAL_OK) ? 0U : 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================= LVGL 刷新入口(按开关选择传输方式,带日志) ================= */
|
||||||
|
|
||||||
|
void ILI9341_FlushArea_DMA(uint16_t x, uint16_t y,
|
||||||
|
uint16_t w, uint16_t h,
|
||||||
|
const uint16_t *data,
|
||||||
|
ILI9341_FlushDoneCb done_cb,
|
||||||
|
void *user_data)
|
||||||
|
{
|
||||||
|
uint32_t byte_len;
|
||||||
|
uint8_t fail;
|
||||||
|
uint32_t cyc0;
|
||||||
|
uint32_t us;
|
||||||
|
|
||||||
|
if (w == 0 || h == 0 || data == NULL)
|
||||||
|
{
|
||||||
|
if (done_cb) done_cb(user_data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte_len = (uint32_t)w * h * 2U;
|
||||||
|
|
||||||
|
LCD_SetWindow(x, y, x + w - 1U, y + h - 1U);
|
||||||
|
|
||||||
|
#if defined(DWT)
|
||||||
|
cyc0 = DWT->CYCCNT;
|
||||||
|
#else
|
||||||
|
cyc0 = 0U;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if (ILI9341_USE_DMA)
|
||||||
|
fail = LCD_Xfer_DMA(data, byte_len);
|
||||||
|
#else
|
||||||
|
fail = LCD_Xfer_Poll(data, byte_len);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
us = LCD_Micros(cyc0);
|
||||||
|
|
||||||
|
lcd_flush_cnt++;
|
||||||
|
if (fail || lcd_spi_err) lcd_err_cnt++;
|
||||||
|
|
||||||
|
#if (ILI9341_LOG_EN)
|
||||||
|
if ((lcd_flush_cnt % ILI9341_LOG_EVERY) == 0U)
|
||||||
|
{
|
||||||
|
/* 一行一条,便于两种方式对比:
|
||||||
|
* 方式 / 序号 / 区域 / 字节数 / 传输耗时us / 是否失败 / SPI错误码 /
|
||||||
|
* 累计错误 / 累计超时 / 数据缓冲地址(判断是否落在 0x24xxxxxx=AXI SRAM) */
|
||||||
|
Debug_Printf("[LCD] %s #%lu (%u,%u %ux%u) len=%lu t=%luus fail=%u err=0x%lX "
|
||||||
|
"eC=%lu toC=%lu buf=0x%08lX\r\n",
|
||||||
|
#if (ILI9341_USE_DMA)
|
||||||
|
"DMA ",
|
||||||
|
#else
|
||||||
|
"POLL",
|
||||||
|
#endif
|
||||||
|
(unsigned long)lcd_flush_cnt,
|
||||||
|
(unsigned)x, (unsigned)y, (unsigned)w, (unsigned)h,
|
||||||
|
(unsigned long)byte_len,
|
||||||
|
(unsigned long)us,
|
||||||
|
(unsigned)fail,
|
||||||
|
(unsigned long)lcd_spi_err,
|
||||||
|
(unsigned long)lcd_err_cnt,
|
||||||
|
(unsigned long)lcd_timeout_cnt,
|
||||||
|
(unsigned long)(uint32_t)data);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
(void)us;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (done_cb) done_cb(user_data);
|
if (done_cb) done_cb(user_data);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,25 @@ extern "C" {
|
|||||||
* 不会级联导致长时间白屏。
|
* 不会级联导致长时间白屏。
|
||||||
* ============================================================ */
|
* ============================================================ */
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
* 诊断/调试开关
|
||||||
|
* ILI9341_USE_DMA : 1 = 用 DMA 搬运像素(+任务内同步等待)
|
||||||
|
* 0 = 纯阻塞 SPI(完全不用 DMA,CPU 直推)
|
||||||
|
* 两种方式共用同一入口 ILI9341_FlushArea_DMA(),
|
||||||
|
* 切换后对比日志即可判断“错位/残留”是否与 DMA 相关。
|
||||||
|
* ILI9341_LOG_EN : 1 = 每次 flush 打印一行日志(走 UART4 Debug_Printf)
|
||||||
|
* ILI9341_LOG_EVERY: 每多少次 flush 打印一次(1=每次;调大可减轻串口负担)
|
||||||
|
* ============================================================ */
|
||||||
|
#ifndef ILI9341_USE_DMA
|
||||||
|
#define ILI9341_USE_DMA 1
|
||||||
|
#endif
|
||||||
|
#ifndef ILI9341_LOG_EN
|
||||||
|
#define ILI9341_LOG_EN 1
|
||||||
|
#endif
|
||||||
|
#ifndef ILI9341_LOG_EVERY
|
||||||
|
#define ILI9341_LOG_EVERY 1U
|
||||||
|
#endif
|
||||||
|
|
||||||
/* 面板原生(未旋转)分辨率 */
|
/* 面板原生(未旋转)分辨率 */
|
||||||
#define ILI9341_NATIVE_WIDTH 240U
|
#define ILI9341_NATIVE_WIDTH 240U
|
||||||
#define ILI9341_NATIVE_HEIGHT 320U
|
#define ILI9341_NATIVE_HEIGHT 320U
|
||||||
|
|||||||
@@ -69,6 +69,51 @@ STM32H7 的坑:**DMA1/DMA2 无法访问 DTCM(0x20000000)**,能访问 AXI SRA
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 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. 参数与调优
|
## 5. 参数与调优
|
||||||
|
|
||||||
- `DISP_BUF_LINES`(lv_port_disp.c,默认 20):单块缓冲行数。越大单次 DMA 越长、
|
- `DISP_BUF_LINES`(lv_port_disp.c,默认 20):单块缓冲行数。越大单次 DMA 越长、
|
||||||
|
|||||||
Reference in New Issue
Block a user