#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(完全不用 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,可一次发完。 * 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; /* 唤醒等待循环,由任务侧决定如何恢复 */ } }