#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(); } }