Files
motor_ili9341/SystemDrivers/ILI9341.c
T
2026-07-08 10:30:07 +08:00

715 lines
17 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 "cmsis_os.h"
#include "uart4.h"
#include <stdarg.h>
#include <stdio.h>
/*
* 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 SRAM0x30000000 起)
* 已经声明为一个可用 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);
}
}
}