#include "ili9341.h" #include "cmsis_os.h" #include "uart4.h" #include #include /* * ILI9341 TFT LCD 驱动 * STM32H750 + SPI1 + DMA * * 本版本相对上一版的主要改动: * 1. DMA buffer 不再用裸指针指向硬编码地址,改用链接器 section, * 避免和其他默认分配(heap/栈/其他静态变量)物理重叠。 * —— 需要在 .ld 文件里配合添加一个 section,见文件末尾说明。 * 2. DMA 完成/错误的等待方式从"HAL_GetTick() 忙等"改成 * "RTOS 信号量 + 超时",真正让出 CPU,而不是空转。 * 3. ISR 回调只做最少的事(释放信号量/记录错误码),所有 * 恢复逻辑、日志打印都放在任务上下文里做,符合 * "中断里越短越好"的原则。 * 4. 增加了 HAL_SPI_ErrorCallback,之前只处理了 TxCplt, * 一旦发生 SPI 总线错误(比如 CRC/Overrun/ModeFault), * 原来的代码只能傻等 100ms 超时,现在能立刻被唤醒并记录。 * 5. 调试日志走 Print_DMA(非阻塞 UART DMA 发送),不占用 * 本来就紧张的 SPI/DMA 时间片。 */ extern SPI_HandleTypeDef hspi1; static uint16_t lcd_width = ILI9341_WIDTH; static uint16_t lcd_height = ILI9341_HEIGHT; /* LCD 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) /* * LVGL buffer 使用 20 行: * 320 * 20 * 2 = 12800 bytes */ #define LCD_DMA_MAX_PIXELS (320U * 20U) #define LCD_DMA_BUF_SIZE (LCD_DMA_MAX_PIXELS * 2U) /* * DMA buffer 放到链接器专属 section 里,而不是裸写绝对地址。 * 需要在 .ld 文件的 MEMORY 区里确认 D2 SRAM(0x30000000 起) * 已经声明为一个可用 region(很多 CubeIDE 模板里叫 RAM_D2), * 并在 SECTIONS 里加: * * .dma_buffer (NOLOAD) : * { * . = ALIGN(32); * *(.dma_buffer) * . = ALIGN(32); * } >RAM_D2 * * 这样链接器会知道这块内存已经被占用,不会再把 heap/栈/其他 * 全局变量分配到这里,从根本上排除"覆盖冲突"的可能性。 * 32 字节对齐是为了配合 D-Cache clean-by-address 的对齐要求。 */ #if defined(__GNUC__) #define LCD_DMA_BUF_ATTR __attribute__((section(".dma_buffer"), aligned(32))) #else #define LCD_DMA_BUF_ATTR #warning "非 GNU 工具链,请自行确认 lcd_dma_buf 被放置在 DMA 可访问、且不会被其他数据覆盖的内存区域" #endif LCD_DMA_BUF_ATTR static uint8_t lcd_dma_buf[LCD_DMA_BUF_SIZE]; /* ---- 状态与同步 ---- */ static volatile uint8_t lcd_dma_busy = 0; static volatile uint32_t lcd_dma_start_tick = 0; static volatile uint32_t lcd_spi_last_error = 0; static osSemaphoreId_t lcd_dma_sem = NULL; /* ---- 非阻塞调试日志:走 Print_DMA,忙的时候直接丢弃这条日志 ---- */ static char lcd_dbg_line[96]; static void LCD_DebugLogf(const char *fmt, ...) { if (UART_IsBusy()) { return; /* 调试串口正忙,宁可丢日志也不要阻塞关键路径 */ } va_list args; va_start(args, fmt); vsnprintf(lcd_dbg_line, sizeof(lcd_dbg_line), fmt, args); va_end(args); Print_DMA(lcd_dbg_line); } static void LCD_DMA_EnsureSemaphore(void) { if (lcd_dma_sem == NULL) { lcd_dma_sem = osSemaphoreNew(1, 0, NULL); } } /* ========================= 基础 SPI 写命令/数据(阻塞,保持不变) ========================= */ static void ILI9341_WriteCommand(uint8_t cmd) { LCD_CS_LOW(); LCD_DC_CMD(); HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY); LCD_CS_HIGH(); } static void ILI9341_WriteData(uint8_t *data, uint16_t size) { LCD_CS_LOW(); LCD_DC_DATA(); HAL_SPI_Transmit(&hspi1, data, size, HAL_MAX_DELAY); LCD_CS_HIGH(); } static void ILI9341_WriteData8(uint8_t data) { LCD_CS_LOW(); LCD_DC_DATA(); HAL_SPI_Transmit(&hspi1, &data, 1, HAL_MAX_DELAY); LCD_CS_HIGH(); } static void ILI9341_WriteData16(uint16_t data) { uint8_t buf[2]; buf[0] = (uint8_t)(data >> 8); buf[1] = (uint8_t)(data & 0xFF); LCD_CS_LOW(); LCD_DC_DATA(); HAL_SPI_Transmit(&hspi1, buf, 2, HAL_MAX_DELAY); LCD_CS_HIGH(); } static void ILI9341_Reset(void) { LCD_RST_HIGH(); HAL_Delay(20); LCD_RST_LOW(); HAL_Delay(20); LCD_RST_HIGH(); HAL_Delay(120); } static void ILI9341_SetAddressWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { uint8_t data[4]; ILI9341_WriteCommand(0x2A); data[0] = (uint8_t)(x0 >> 8); data[1] = (uint8_t)(x0 & 0xFF); data[2] = (uint8_t)(x1 >> 8); data[3] = (uint8_t)(x1 & 0xFF); ILI9341_WriteData(data, 4); ILI9341_WriteCommand(0x2B); data[0] = (uint8_t)(y0 >> 8); data[1] = (uint8_t)(y0 & 0xFF); data[2] = (uint8_t)(y1 >> 8); data[3] = (uint8_t)(y1 & 0xFF); ILI9341_WriteData(data, 4); ILI9341_WriteCommand(0x2C); } /* ========================= 初始化 ========================= */ void ILI9341_Init(void) { LCD_CS_HIGH(); LCD_BL_ON(); LCD_DMA_EnsureSemaphore(); ILI9341_Reset(); ILI9341_WriteCommand(0x01); HAL_Delay(120); ILI9341_WriteCommand(0xEF); { uint8_t data[] = {0x03, 0x80, 0x02}; ILI9341_WriteData(data, sizeof(data)); } ILI9341_WriteCommand(0xCF); { uint8_t data[] = {0x00, 0xC1, 0x30}; ILI9341_WriteData(data, sizeof(data)); } ILI9341_WriteCommand(0xED); { uint8_t data[] = {0x64, 0x03, 0x12, 0x81}; ILI9341_WriteData(data, sizeof(data)); } ILI9341_WriteCommand(0xE8); { uint8_t data[] = {0x85, 0x00, 0x78}; ILI9341_WriteData(data, sizeof(data)); } ILI9341_WriteCommand(0xCB); { uint8_t data[] = {0x39, 0x2C, 0x00, 0x34, 0x02}; ILI9341_WriteData(data, sizeof(data)); } ILI9341_WriteCommand(0xF7); ILI9341_WriteData8(0x20); ILI9341_WriteCommand(0xEA); { uint8_t data[] = {0x00, 0x00}; ILI9341_WriteData(data, sizeof(data)); } ILI9341_WriteCommand(0xC0); ILI9341_WriteData8(0x23); ILI9341_WriteCommand(0xC1); ILI9341_WriteData8(0x10); ILI9341_WriteCommand(0xC5); { uint8_t data[] = {0x3E, 0x28}; ILI9341_WriteData(data, sizeof(data)); } ILI9341_WriteCommand(0xC7); ILI9341_WriteData8(0x86); ILI9341_WriteCommand(0x36); ILI9341_WriteData8(0x48); ILI9341_WriteCommand(0x3A); ILI9341_WriteData8(0x55); ILI9341_WriteCommand(0xB1); { uint8_t data[] = {0x00, 0x18}; ILI9341_WriteData(data, sizeof(data)); } ILI9341_WriteCommand(0xB6); { uint8_t data[] = {0x08, 0x82, 0x27}; ILI9341_WriteData(data, sizeof(data)); } ILI9341_WriteCommand(0xF2); ILI9341_WriteData8(0x00); ILI9341_WriteCommand(0x26); ILI9341_WriteData8(0x01); ILI9341_WriteCommand(0xE0); { uint8_t data[] = { 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00 }; ILI9341_WriteData(data, sizeof(data)); } ILI9341_WriteCommand(0xE1); { uint8_t data[] = { 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F }; ILI9341_WriteData(data, sizeof(data)); } ILI9341_WriteCommand(0x11); HAL_Delay(120); ILI9341_WriteCommand(0x29); HAL_Delay(20); ILI9341_SetRotation(0); } /* ========================= 方向设置 ========================= */ void ILI9341_SetRotation(uint8_t rotation) { uint8_t madctl; rotation %= 4; switch (rotation) { 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; } ILI9341_WriteCommand(0x36); ILI9341_WriteData8(madctl); } /* ========================= 基础绘图(阻塞,保持不变) ========================= */ void ILI9341_DrawPixel(uint16_t x, uint16_t y, uint16_t color) { if (x >= lcd_width || y >= lcd_height) { return; } ILI9341_SetAddressWindow(x, y, x, y); ILI9341_WriteData16(color); } void ILI9341_FillRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) { uint32_t pixels; uint8_t buffer[128]; uint16_t chunk_pixels; if (x >= lcd_width || y >= lcd_height) { return; } if ((x + w) > lcd_width) { w = lcd_width - x; } if ((y + h) > lcd_height) { h = lcd_height - y; } pixels = (uint32_t)w * h; for (uint16_t i = 0; i < sizeof(buffer); i += 2) { buffer[i] = (uint8_t)(color >> 8); buffer[i + 1] = (uint8_t)(color & 0xFF); } ILI9341_SetAddressWindow(x, y, x + w - 1, y + h - 1); LCD_CS_LOW(); LCD_DC_DATA(); while (pixels > 0) { chunk_pixels = pixels > 64U ? 64U : (uint16_t)pixels; HAL_SPI_Transmit(&hspi1, buffer, chunk_pixels * 2U, HAL_MAX_DELAY); pixels -= chunk_pixels; } LCD_CS_HIGH(); } void ILI9341_FillScreen(uint16_t color) { ILI9341_FillRect(0, 0, lcd_width, lcd_height, color); } uint16_t ILI9341_Color565(uint8_t r, uint8_t g, uint8_t b) { return ((r & 0xF8U) << 8) | ((g & 0xFCU) << 3) | (b >> 3); } /* ========================= LVGL 阻塞区域刷新(保持不变) ========================= */ void ILI9341_WriteArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t *pixels) { uint32_t total_pixels; uint32_t sent_pixels = 0; static uint8_t tx_buf[512]; if (w == 0 || h == 0 || pixels == NULL) { return; } total_pixels = (uint32_t)w * h; ILI9341_SetAddressWindow(x, y, x + w - 1, y + h - 1); LCD_CS_LOW(); LCD_DC_DATA(); while (sent_pixels < total_pixels) { uint32_t remain = total_pixels - sent_pixels; uint32_t chunk_pixels = remain > 256U ? 256U : remain; for (uint32_t i = 0; i < chunk_pixels; i++) { uint16_t color = pixels[sent_pixels + i]; tx_buf[i * 2U] = (uint8_t)(color >> 8); tx_buf[i * 2U + 1] = (uint8_t)(color & 0xFF); } HAL_SPI_Transmit(&hspi1, tx_buf, chunk_pixels * 2U, HAL_MAX_DELAY); sent_pixels += chunk_pixels; } LCD_CS_HIGH(); } /* ========================= LVGL DMA 区域刷新:重写版 ========================= */ static void LCD_CleanDCache(void *addr, uint32_t size) { #if (__DCACHE_PRESENT == 1U) if ((SCB->CCR & SCB_CCR_DC_Msk) != 0U) { uint32_t start_addr = (uint32_t)addr & ~31U; uint32_t end_addr = ((uint32_t)addr + size + 31U) & ~31U; SCB_CleanDCache_by_Addr((uint32_t *)start_addr, end_addr - start_addr); } #else (void)addr; (void)size; #endif } static void ILI9341_SPI_WaitIdle(void) { uint32_t tick_start; #if defined(SPI_FLAG_TXC) tick_start = HAL_GetTick(); while (__HAL_SPI_GET_FLAG(&hspi1, SPI_FLAG_TXC) == RESET) { if ((HAL_GetTick() - tick_start) > 20U) { break; } } #elif defined(SPI_FLAG_EOT) tick_start = HAL_GetTick(); while (__HAL_SPI_GET_FLAG(&hspi1, SPI_FLAG_EOT) == RESET) { if ((HAL_GetTick() - tick_start) > 20U) { break; } } #else for (volatile uint32_t i = 0; i < 1000U; i++) { __NOP(); } #endif __DSB(); } void ILI9341_WriteArea_DMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t *pixels, ILI9341_DmaDoneCallback done_cb, void *user_data) { uint32_t total_pixels; uint32_t byte_len; if (w == 0 || h == 0 || pixels == NULL) { if (done_cb != NULL) { done_cb(user_data); } return; } total_pixels = (uint32_t)w * h; if (total_pixels > LCD_DMA_MAX_PIXELS) { /* 超出单次 DMA buffer 容量,退化为阻塞传输 */ ILI9341_WriteArea(x, y, w, h, pixels); if (done_cb != NULL) { done_cb(user_data); } return; } LCD_DMA_EnsureSemaphore(); if (lcd_dma_busy) { /* * 正常情况下,LVGL 在上一次 flush 没有调用 flush_ready * 之前不会发起新的 flush,所以走到这里说明上一次传输 * 大概率已经出了问题(信号量没有被正确释放)。 * 这里做一次强制恢复,而不是继续等待,避免连锁卡死。 */ LCD_DebugLogf("[LCD] WARN busy-reenter, force recover\r\n"); HAL_SPI_Abort(&hspi1); LCD_CS_HIGH(); lcd_dma_busy = 0; } lcd_dma_busy = 1; lcd_spi_last_error = 0; /* 组包到 DMA buffer */ for (uint32_t i = 0; i < total_pixels; i++) { uint16_t color = pixels[i]; lcd_dma_buf[i * 2U] = (uint8_t)(color >> 8); lcd_dma_buf[i * 2U + 1] = (uint8_t)(color & 0xFF); } byte_len = total_pixels * 2U; LCD_CleanDCache(lcd_dma_buf, byte_len); ILI9341_SetAddressWindow(x, y, x + w - 1U, y + h - 1U); lcd_dma_start_tick = HAL_GetTick(); LCD_CS_LOW(); LCD_DC_DATA(); if (HAL_SPI_Transmit_DMA(&hspi1, lcd_dma_buf, (uint16_t)byte_len) != HAL_OK) { LCD_CS_HIGH(); lcd_dma_busy = 0; LCD_DebugLogf("[LCD] ERR DMA start failed\r\n"); if (done_cb != NULL) { done_cb(user_data); } return; } /* * 真正的关键改动:这里不再用 HAL_GetTick() 忙等,而是让任务 * 通过信号量挂起,把 CPU 让给调度器;DMA 完成中断 * (HAL_SPI_TxCpltCallback)或错误中断(HAL_SPI_ErrorCallback) * 会释放这个信号量把任务唤醒。100ms 超时依旧作为兜底。 */ if (osSemaphoreAcquire(lcd_dma_sem, 100) != osOK) { LCD_DebugLogf("[LCD] ERR DMA timeout, aborting\r\n"); HAL_SPI_Abort(&hspi1); } else if (lcd_spi_last_error != 0U) { LCD_DebugLogf("[LCD] ERR SPI err=0x%08lX\r\n", (unsigned long)lcd_spi_last_error); lcd_spi_last_error = 0U; } ILI9341_SPI_WaitIdle(); LCD_CS_HIGH(); lcd_dma_busy = 0; if (done_cb != NULL) { done_cb(user_data); } } void ILI9341_DMA_Watchdog(uint32_t timeout_ms) { if (!lcd_dma_busy) { return; } uint32_t elapsed = HAL_GetTick() - lcd_dma_start_tick; if (elapsed > timeout_ms) { LCD_DebugLogf("[LCD] WDG force recover after %lums\r\n", (unsigned long)elapsed); HAL_SPI_Abort(&hspi1); LCD_CS_HIGH(); lcd_dma_busy = 0; /* 如果此时正好有任务卡在 WriteArea_DMA 里等信号量,唤醒它 */ if (lcd_dma_sem != NULL) { osSemaphoreRelease(lcd_dma_sem); } } } /* * SPI1 TX DMA 完成回调(ISR 上下文)。 * 只做最必要的事:释放信号量。不打印日志、不调用 LVGL。 */ void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi->Instance == SPI1) { if (lcd_dma_sem != NULL) { osSemaphoreRelease(lcd_dma_sem); } } } /* * SPI 错误回调(ISR 上下文)。 * 之前的版本没有实现这个回调,一旦发生总线错误(Overrun/ * ModeFault/CRC 等),只能傻等 100ms 超时才能恢复。 * 现在记录错误码并立即释放信号量唤醒等待的任务, * 真正的日志打印和处理放到任务上下文(WriteArea_DMA 里)去做。 */ void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) { if (hspi->Instance == SPI1) { lcd_spi_last_error = hspi->ErrorCode; if (lcd_dma_sem != NULL) { osSemaphoreRelease(lcd_dma_sem); } } }