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:
zhy200308
2026-07-08 11:40:20 +08:00
parent 1273a14bf2
commit fd6c18c4c7
3 changed files with 196 additions and 24 deletions
+132 -24
View File
@@ -1,4 +1,5 @@
#include "ili9341.h"
#include "uart4.h" /* Debug_Printf:刷新日志 */
/*
* 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_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)
@@ -98,6 +127,8 @@ void ILI9341_Init(void)
LCD_CS_HIGH();
LCD_BL_ON();
LCD_DWT_Init();
LCD_Reset();
LCD_WriteCmd(0x01); /* Software reset */
@@ -234,26 +265,15 @@ static void LCD_SPI_WaitShiftEmpty(void)
__DSB();
}
/* ================= LVGL 刷新入口(DMA 加速,任务内同步等待完成) ================= */
/* ================= 两种传输实现 =================
* 均为“同步返回”:函数返回时该块像素一定已完整写入面板。
* 返回值:0=成功,1=超时/出错(已尽力恢复)。 */
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)
/* (A) DMA 传输 + 任务内同步等待 */
static uint8_t LCD_Xfer_DMA(const uint16_t *data, uint32_t byte_len)
{
uint32_t byte_len;
uint32_t t0;
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);
uint8_t fail = 0;
/* DMA 读取前 clean D-Cache,确保面板拿到最新像素 */
LCD_CleanDCache(data, byte_len);
@@ -269,32 +289,120 @@ void ILI9341_FlushArea_DMA(uint16_t x, uint16_t y,
{
lcd_xfer_done = 1;
LCD_CS_HIGH();
if (done_cb) done_cb(user_data);
return;
return 1;
}
/* 任务内同步等待:中断只把 lcd_xfer_done 置 1,这里轮询等待
* 传输一小块通常 1~2ms 即完成;50ms 超时兜底防止硬件卡死。
* 这样每次 flush 返回时,这块像素一定已经完整写入面板——不会出现
* “LVGL 提前复用缓冲 / 擦除区没写完”导致的移动残留。 */
/* 中断只把 lcd_xfer_done 置 1,这里轮询等待50ms 超时兜底。 */
t0 = HAL_GetTick();
while (lcd_xfer_done == 0U)
{
if ((HAL_GetTick() - t0) > 50U)
{
HAL_SPI_Abort(&hspi1);
/* 传输异常,重建 SPI,避免坏状态带入后续帧 */
if (HAL_SPI_DeInit(&hspi1) == HAL_OK)
{
HAL_SPI_Init(&hspi1);
}
lcd_xfer_done = 1;
lcd_timeout_cnt++;
fail = 1;
break;
}
}
LCD_SPI_WaitShiftEmpty();
LCD_CS_HIGH();
return fail;
}
/* (B) 纯阻塞 SPI(完全不用 DMACPU 直推)
* 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);
}