Files
motor_ili9341/SystemDrivers/ILI9341.c
T
zhy200308 1b8f0f806b ILI9341: 修复每帧多耗~5ms的空等 + SPI 10MHz->20MHz,解决移动错位
日志实测定位:单块 flush 耗时~9.7ms,远超面板扫描周期(~16ms)导致扫描
在写入途中'套圈',方块被撕成多个错位副本。两处根因:
- LCD_SPI_WaitShiftEmpty 轮询 TXC,但 HAL 在 EOT 后关闭 SPI,TXC 恒0,
  每帧白等满 5ms 超时。HAL 的 EOT/阻塞传输已保证数据移出,直接删除。
- SPI 仅 10MHz(pll1_q40M/4),0.8us/byte。改 prescaler 4->2 = 20MHz。
合计单块 flush ~9.7ms -> ~2.3ms,远小于扫描周期,错位应显著消除。
spi.c 与 .ioc(本就=_2) 对齐。
2026-07-08 12:18:47 +08:00

436 lines
15 KiB
C
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.
#include "ili9341.h"
#include "uart4.h" /* Debug_Printf:刷新日志 */
/*
* ILI9341 驱动 —— 全异步 DMA 重写版
* ------------------------------------------------------------------
* 相比旧版的根本性改动:
* [方向] 旧版在 disp_flush() 里做软件转置 + MADCTL 原生方向,
* 任何坐标/尺寸的细微不一致都会在移动物体处留下残影。
* 新版用 MADCTL 直接把面板设为横屏,LVGL 坐标 == 面板坐标,
* 刷新区域与 LVGL 的失效区域严格 1:1,残留问题消失。
* [同步] 旧版发起 DMA 后用信号量阻塞等待,出错就 Abort,一旦
* SPI 状态机被打乱就会级联白屏。新版 FlushArea_DMA 启动
* DMA 后立即返回,完成/出错都由中断回调收尾,无信号量、
* 无阻塞、时序自同步。
* [内存] DMA 源缓冲一律位于 AXI SRAM(.lcd_ram 段)DMA1 可访问;
* 发送前做 D-Cache clean,保证 DMA 读到的是最新数据。
* [恢复] 出错回调 + 看门狗都会把 CS 拉高、状态复位、并触发完成
* 回调,任何单次异常都不会让刷新管线永久停摆。
*/
extern SPI_HandleTypeDef hspi1;
/* ================= GPIO 控制 ================= */
#define LCD_CS_LOW() HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_RESET)
#define LCD_CS_HIGH() HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_SET)
#define LCD_DC_CMD() HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_RESET)
#define LCD_DC_DATA() HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_SET)
#define LCD_RST_LOW() HAL_GPIO_WritePin(LCD_RST_PORT, LCD_RST_PIN, GPIO_PIN_RESET)
#define LCD_RST_HIGH() HAL_GPIO_WritePin(LCD_RST_PORT, LCD_RST_PIN, GPIO_PIN_SET)
#define LCD_BL_ON() HAL_GPIO_WritePin(LCD_BL_PORT, LCD_BL_PIN, GPIO_PIN_SET)
#define LCD_BL_OFF() HAL_GPIO_WritePin(LCD_BL_PORT, LCD_BL_PIN, GPIO_PIN_RESET)
static uint16_t lcd_width = ILI9341_WIDTH;
static uint16_t lcd_height = ILI9341_HEIGHT;
/* ================= 刷新状态(DMA 传输 + 任务内同步等待) =================
* 设计取舍:DMA 只用来加速一块像素的搬运,等待/收尾/回调全部放在
* 任务上下文里做(同步)。中断只负责把 lcd_xfer_done 置 1,极短。
* 这样彻底消除“ISR 里调用 lv_disp_flush_ready / 在 ISR 里 Abort /
* 完成被收尾两次”等竞态——这些正是快速移动物体出现残留的隐患。 */
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)
{
LCD_CS_LOW();
LCD_DC_CMD();
HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
LCD_CS_HIGH();
}
static void LCD_WriteData(const uint8_t *data, uint16_t size)
{
LCD_CS_LOW();
LCD_DC_DATA();
HAL_SPI_Transmit(&hspi1, (uint8_t *)data, size, HAL_MAX_DELAY);
LCD_CS_HIGH();
}
static void LCD_WriteData8(uint8_t data)
{
LCD_WriteData(&data, 1);
}
/* 设定地址窗口 [x0..x1] x [y0..y1],并进入 RAM 写(0x2C)。
* 结束后 CS 处于高电平,DC 为命令态。 */
static void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
uint8_t buf[4];
LCD_WriteCmd(0x2A);
buf[0] = (uint8_t)(x0 >> 8); buf[1] = (uint8_t)(x0 & 0xFF);
buf[2] = (uint8_t)(x1 >> 8); buf[3] = (uint8_t)(x1 & 0xFF);
LCD_WriteData(buf, 4);
LCD_WriteCmd(0x2B);
buf[0] = (uint8_t)(y0 >> 8); buf[1] = (uint8_t)(y0 & 0xFF);
buf[2] = (uint8_t)(y1 >> 8); buf[3] = (uint8_t)(y1 & 0xFF);
LCD_WriteData(buf, 4);
LCD_WriteCmd(0x2C);
}
static void LCD_Reset(void)
{
LCD_RST_HIGH(); HAL_Delay(20);
LCD_RST_LOW(); HAL_Delay(20);
LCD_RST_HIGH(); HAL_Delay(120);
}
/* ================= 初始化 ================= */
void ILI9341_Init(void)
{
LCD_CS_HIGH();
LCD_BL_ON();
LCD_DWT_Init();
LCD_Reset();
LCD_WriteCmd(0x01); /* Software reset */
HAL_Delay(120);
/* 厂商推荐上电时序 */
LCD_WriteCmd(0xEF); { const uint8_t d[] = {0x03,0x80,0x02}; LCD_WriteData(d,sizeof d); }
LCD_WriteCmd(0xCF); { const uint8_t d[] = {0x00,0xC1,0x30}; LCD_WriteData(d,sizeof d); }
LCD_WriteCmd(0xED); { const uint8_t d[] = {0x64,0x03,0x12,0x81}; LCD_WriteData(d,sizeof d); }
LCD_WriteCmd(0xE8); { const uint8_t d[] = {0x85,0x00,0x78}; LCD_WriteData(d,sizeof d); }
LCD_WriteCmd(0xCB); { const uint8_t d[] = {0x39,0x2C,0x00,0x34,0x02}; LCD_WriteData(d,sizeof d); }
LCD_WriteCmd(0xF7); LCD_WriteData8(0x20);
LCD_WriteCmd(0xEA); { const uint8_t d[] = {0x00,0x00}; LCD_WriteData(d,sizeof d); }
LCD_WriteCmd(0xC0); LCD_WriteData8(0x23); /* Power control 1 */
LCD_WriteCmd(0xC1); LCD_WriteData8(0x10); /* Power control 2 */
LCD_WriteCmd(0xC5); { const uint8_t d[] = {0x3E,0x28}; LCD_WriteData(d,sizeof d); }
LCD_WriteCmd(0xC7); LCD_WriteData8(0x86); /* VCOM control 2 */
LCD_WriteCmd(0x3A); LCD_WriteData8(0x55); /* Pixel format = 16bit/pixel */
LCD_WriteCmd(0xB1); { const uint8_t d[] = {0x00,0x18}; LCD_WriteData(d,sizeof d); }
LCD_WriteCmd(0xB6); { const uint8_t d[] = {0x08,0x82,0x27}; LCD_WriteData(d,sizeof d); }
LCD_WriteCmd(0xF2); LCD_WriteData8(0x00);
LCD_WriteCmd(0x26); LCD_WriteData8(0x01); /* Gamma curve */
LCD_WriteCmd(0xE0); { const uint8_t d[] = {0x0F,0x31,0x2B,0x0C,0x0E,0x08,0x4E,0xF1,0x37,0x07,0x10,0x03,0x0E,0x09,0x00}; LCD_WriteData(d,sizeof d); }
LCD_WriteCmd(0xE1); { const uint8_t d[] = {0x00,0x0E,0x14,0x03,0x11,0x07,0x31,0xC1,0x48,0x08,0x0F,0x0C,0x31,0x36,0x0F}; LCD_WriteData(d,sizeof d); }
LCD_WriteCmd(0x11); /* Sleep out */
HAL_Delay(120);
LCD_WriteCmd(0x29); /* Display on */
HAL_Delay(20);
ILI9341_SetRotation(1); /* 默认横屏 320x240,硬件旋转 */
}
/* ================= 方向(硬件 MADCTL 旋转) =================
* 若烧录后发现镜像/上下颠倒,只需换用同一行注释里给出的备选 MADCTL:
* 横屏可选 0x28 / 0xE8,竖屏可选 0x48 / 0x88。
*/
void ILI9341_SetRotation(uint8_t rotation)
{
uint8_t madctl;
switch (rotation & 0x03)
{
case 0: madctl = 0x48; lcd_width = 240; lcd_height = 320; break; /* 竖屏 */
case 1: madctl = 0x28; lcd_width = 320; lcd_height = 240; break; /* 横屏(默认) */
case 2: madctl = 0x88; lcd_width = 240; lcd_height = 320; break; /* 竖屏翻转 */
default: madctl = 0xE8; lcd_width = 320; lcd_height = 240; break; /* 横屏翻转 */
}
LCD_WriteCmd(0x36);
LCD_WriteData8(madctl);
}
/* ================= 阻塞式基础绘图(初始化清屏用) ================= */
uint16_t ILI9341_Color565(uint8_t r, uint8_t g, uint8_t b)
{
return (uint16_t)(((r & 0xF8U) << 8) | ((g & 0xFCU) << 3) | (b >> 3));
}
void ILI9341_FillRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color)
{
uint8_t line[64 * 2]; /* 一次最多推 64 像素 */
uint32_t pixels;
if (x >= lcd_width || y >= lcd_height) return;
if ((uint32_t)x + w > lcd_width) w = lcd_width - x;
if ((uint32_t)y + h > lcd_height) h = lcd_height - y;
if (w == 0 || h == 0) return;
for (uint16_t i = 0; i < sizeof(line); i += 2)
{
line[i] = (uint8_t)(color >> 8);
line[i + 1] = (uint8_t)(color & 0xFF);
}
LCD_SetWindow(x, y, x + w - 1, y + h - 1);
LCD_CS_LOW();
LCD_DC_DATA();
pixels = (uint32_t)w * h;
while (pixels)
{
uint16_t chunk = pixels > 64U ? 64U : (uint16_t)pixels;
HAL_SPI_Transmit(&hspi1, line, (uint16_t)(chunk * 2U), HAL_MAX_DELAY);
pixels -= chunk;
}
LCD_CS_HIGH();
}
void ILI9341_FillScreen(uint16_t color)
{
ILI9341_FillRect(0, 0, lcd_width, lcd_height, color);
}
/* ================= D-Cache 维护 ================= */
static void LCD_CleanDCache(const void *addr, uint32_t size)
{
#if defined(__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
if ((SCB->CCR & SCB_CCR_DC_Msk) != 0U)
{
uint32_t start = (uint32_t)addr & ~31U;
uint32_t end = ((uint32_t)addr + size + 31U) & ~31U;
SCB_CleanDCache_by_Addr((uint32_t *)start, (int32_t)(end - start));
}
#else
(void)addr; (void)size;
#endif
}
/* ================= 两种传输实现 =================
* 均为“同步返回”:函数返回时该块像素一定已完整写入面板。
* 返回值:0=成功,1=超时/出错(已尽力恢复)。 */
/* (A) DMA 传输 + 任务内同步等待 */
static uint8_t LCD_Xfer_DMA(const uint16_t *data, uint32_t byte_len)
{
uint32_t t0;
uint8_t fail = 0;
/* DMA 读取前 clean D-Cache,确保面板拿到最新像素 */
LCD_CleanDCache(data, byte_len);
lcd_xfer_done = 0;
lcd_spi_err = 0;
lcd_start_tick = HAL_GetTick();
LCD_CS_LOW();
LCD_DC_DATA();
if (HAL_SPI_Transmit_DMA(&hspi1, (uint8_t *)data, (uint16_t)byte_len) != HAL_OK)
{
lcd_xfer_done = 1;
LCD_CS_HIGH();
return 1;
}
/* 中断只把 lcd_xfer_done 置 1,这里轮询等待;50ms 超时兜底。 */
t0 = HAL_GetTick();
while (lcd_xfer_done == 0U)
{
if ((HAL_GetTick() - t0) > 50U)
{
HAL_SPI_Abort(&hspi1);
if (HAL_SPI_DeInit(&hspi1) == HAL_OK)
{
HAL_SPI_Init(&hspi1);
}
lcd_xfer_done = 1;
lcd_timeout_cnt++;
fail = 1;
break;
}
}
/* HAL 的 TxCplt 由 SPI EOT 触发,此刻数据已全部移出,直接拉高 CS。
* 不再自旋等待 TXC——EOT 之后 HAL 会关闭 SPI,TXC 恒为 0,等待会白等
* 满超时(实测每帧多耗约 5ms,正是移动错位的元凶之一)。 */
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,可一次发完。
* HAL_SPI_Transmit 在 H7 上返回时数据已移出,直接拉高 CS。 */
st = HAL_SPI_Transmit(&hspi1, (uint8_t *)data, (uint16_t)byte_len, 100);
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);
}
uint8_t ILI9341_IsBusy(void)
{
return (lcd_xfer_done == 0U) ? 1U : 0U;
}
/* ================= 看门狗(任务上下文调用) =================
* 同步刷新下,本函数返回时传输一定已完成,看门狗基本用不到;
* 保留作为极端兜底:若发现长时间处于“未完成”,强制恢复 SPI。 */
void ILI9341_ServiceWatchdog(uint32_t timeout_ms)
{
if (lcd_xfer_done != 0U)
{
return;
}
if ((HAL_GetTick() - lcd_start_tick) <= timeout_ms)
{
return;
}
HAL_SPI_Abort(&hspi1);
if (HAL_SPI_DeInit(&hspi1) == HAL_OK)
{
HAL_SPI_Init(&hspi1);
}
LCD_CS_HIGH();
lcd_xfer_done = 1;
}
/* ================= SPI 中断回调(ISR 上下文,极短) ================= */
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI1)
{
lcd_xfer_done = 1; /* 只置标志,收尾/回调全部在任务里做 */
}
}
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI1)
{
lcd_spi_err = hspi->ErrorCode;
lcd_xfer_done = 1; /* 唤醒等待循环,由任务侧决定如何恢复 */
}
}