Files
motor_ili9341/SystemDrivers/ILI9341.c
T
zhy200308 e4d61b19f0 重构 ILI9341+LVGL 驱动:异步 DMA + 硬件旋转,修复残影与白屏
- 硬件 MADCTL 旋转,删除软件转置,LVGL 坐标与面板 1:1,消除移动残影
- 全异步 DMA:flush 启动即返回,完成/出错由 SPI 中断收尾,去掉信号量阻塞
- DMA 缓冲经 .lcd_ram 段强制放入 AXI SRAM,规避 DTCM 不可被 DMA 访问导致的白屏
- LV_COLOR_16_SWAP=1,LVGL 直出面板字节序,省去逐像素交换/拷贝
- 看门狗超时重建 SPI 自恢复,单次异常不再级联长时间白屏
- 定制 scatter(motor_ili9341_lcd.sct) 并接入 uvprojx
- 新增 README_ILI9341.md 说明根因/设计/构建验证
2026-07-08 11:02:42 +08:00

348 lines
12 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"
/*
* 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;
/* ================= 异步刷新状态(全部由 volatile 保护) ================= */
typedef enum { LCD_IDLE = 0, LCD_BUSY = 1 } lcd_state_t;
static volatile lcd_state_t lcd_state = LCD_IDLE;
static volatile uint32_t lcd_start_tick = 0;
static ILI9341_FlushDoneCb lcd_done_cb = NULL;
static void *lcd_done_arg = NULL;
/* ================= 阻塞式低层写命令/数据(仅用于初始化与设窗口) ================= */
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_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
}
/* ================= 异步刷新完成收尾(供 ISR / 看门狗调用) ================= */
static void LCD_FinishTransfer(void)
{
ILI9341_FlushDoneCb cb;
void *arg;
/* 关中断内做“取出并清空回调 + 复位状态”,保证只收尾一次,
* 杜绝 ISR 与看门狗竞争导致 done_cb 被调用两次。 */
uint32_t primask = __get_PRIMASK();
__disable_irq();
if (lcd_state != LCD_BUSY)
{
__set_PRIMASK(primask);
return;
}
LCD_CS_HIGH();
lcd_state = LCD_IDLE;
cb = lcd_done_cb;
arg = lcd_done_arg;
lcd_done_cb = NULL;
__set_PRIMASK(primask);
if (cb != NULL)
{
cb(arg);
}
}
/* ================= 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;
if (w == 0 || h == 0 || data == NULL)
{
if (done_cb) done_cb(user_data);
return;
}
/* 正常情况下 LVGL 在 flush_ready 之前不会再次进入,走到这里说明
* 上一次传输异常未收尾:强制恢复后再继续,避免卡死。 */
if (lcd_state == LCD_BUSY)
{
HAL_SPI_Abort(&hspi1);
LCD_CS_HIGH();
lcd_state = LCD_IDLE;
}
byte_len = (uint32_t)w * h * 2U;
/* 登记回调并置忙。先置忙再启动,保证完成中断能看到 BUSY。 */
lcd_done_cb = done_cb;
lcd_done_arg = user_data;
lcd_state = LCD_BUSY;
lcd_start_tick = HAL_GetTick();
LCD_SetWindow(x, y, x + w - 1U, y + h - 1U);
/* DMA 读取前 clean D-Cache,确保面板拿到最新像素 */
LCD_CleanDCache(data, byte_len);
LCD_CS_LOW();
LCD_DC_DATA();
if (HAL_SPI_Transmit_DMA(&hspi1, (uint8_t *)data, (uint16_t)byte_len) != HAL_OK)
{
LCD_CS_HIGH();
lcd_state = LCD_IDLE;
lcd_done_cb = NULL;
if (done_cb) done_cb(user_data);
return;
}
/* 传输完成 → HAL_SPI_TxCpltCallback;出错 → HAL_SPI_ErrorCallback。 */
}
uint8_t ILI9341_IsBusy(void)
{
return (lcd_state == LCD_BUSY) ? 1U : 0U;
}
/* ================= 看门狗(任务上下文调用) ================= */
void ILI9341_ServiceWatchdog(uint32_t timeout_ms)
{
if (lcd_state != LCD_BUSY)
{
return;
}
if ((HAL_GetTick() - lcd_start_tick) <= timeout_ms)
{
return;
}
/* 传输疑似卡死:中止 DMA、重建 SPI,再收尾(触发完成回调)。 */
HAL_SPI_Abort(&hspi1);
if (HAL_SPI_DeInit(&hspi1) == HAL_OK)
{
HAL_SPI_Init(&hspi1);
}
LCD_FinishTransfer();
}
/* ================= SPI 中断回调(ISR 上下文,尽量短) ================= */
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI1)
{
/* H7 上 TxCplt 由 SPI EOT 触发,此刻数据已全部移出,可直接拉高 CS。 */
LCD_FinishTransfer();
}
}
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI1)
{
/* 记录并中止,避免把坏状态带入下一帧。收尾同样会触发完成回调,
* 让 LVGL 继续,不至于卡在 flushing。 */
HAL_SPI_Abort(hspi);
LCD_FinishTransfer();
}
}