重构 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 说明根因/设计/构建验证
This commit is contained in:
+196
-564
@@ -1,418 +1,189 @@
|
||||
#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 时间片。
|
||||
* 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;
|
||||
|
||||
/* 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)
|
||||
/* ================= 异步刷新状态(全部由 volatile 保护) ================= */
|
||||
typedef enum { LCD_IDLE = 0, LCD_BUSY = 1 } lcd_state_t;
|
||||
|
||||
#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)
|
||||
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;
|
||||
|
||||
#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)
|
||||
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 ILI9341_WriteData(uint8_t *data, uint16_t size)
|
||||
static void LCD_WriteData(const uint8_t *data, uint16_t size)
|
||||
{
|
||||
LCD_CS_LOW();
|
||||
LCD_DC_DATA();
|
||||
|
||||
HAL_SPI_Transmit(&hspi1, data, size, HAL_MAX_DELAY);
|
||||
|
||||
HAL_SPI_Transmit(&hspi1, (uint8_t *)data, size, HAL_MAX_DELAY);
|
||||
LCD_CS_HIGH();
|
||||
}
|
||||
|
||||
static void ILI9341_WriteData8(uint8_t data)
|
||||
static void LCD_WriteData8(uint8_t data)
|
||||
{
|
||||
LCD_CS_LOW();
|
||||
LCD_DC_DATA();
|
||||
|
||||
HAL_SPI_Transmit(&hspi1, &data, 1, HAL_MAX_DELAY);
|
||||
|
||||
LCD_CS_HIGH();
|
||||
LCD_WriteData(&data, 1);
|
||||
}
|
||||
|
||||
static void ILI9341_WriteData16(uint16_t data)
|
||||
/* 设定地址窗口 [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[2];
|
||||
uint8_t buf[4];
|
||||
|
||||
buf[0] = (uint8_t)(data >> 8);
|
||||
buf[1] = (uint8_t)(data & 0xFF);
|
||||
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_CS_LOW();
|
||||
LCD_DC_DATA();
|
||||
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);
|
||||
|
||||
HAL_SPI_Transmit(&hspi1, buf, 2, HAL_MAX_DELAY);
|
||||
|
||||
LCD_CS_HIGH();
|
||||
LCD_WriteCmd(0x2C);
|
||||
}
|
||||
|
||||
static void ILI9341_Reset(void)
|
||||
static void LCD_Reset(void)
|
||||
{
|
||||
LCD_RST_HIGH();
|
||||
HAL_Delay(20);
|
||||
|
||||
LCD_RST_LOW();
|
||||
HAL_Delay(20);
|
||||
|
||||
LCD_RST_HIGH();
|
||||
HAL_Delay(120);
|
||||
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();
|
||||
LCD_Reset();
|
||||
|
||||
ILI9341_Reset();
|
||||
|
||||
ILI9341_WriteCommand(0x01);
|
||||
LCD_WriteCmd(0x01); /* Software reset */
|
||||
HAL_Delay(120);
|
||||
|
||||
ILI9341_WriteCommand(0xEF);
|
||||
{
|
||||
uint8_t data[] = {0x03, 0x80, 0x02};
|
||||
ILI9341_WriteData(data, sizeof(data));
|
||||
}
|
||||
/* 厂商推荐上电时序 */
|
||||
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); }
|
||||
|
||||
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);
|
||||
LCD_WriteCmd(0x11); /* Sleep out */
|
||||
HAL_Delay(120);
|
||||
|
||||
ILI9341_WriteCommand(0x29);
|
||||
LCD_WriteCmd(0x29); /* Display on */
|
||||
HAL_Delay(20);
|
||||
|
||||
ILI9341_SetRotation(0);
|
||||
ILI9341_SetRotation(1); /* 默认横屏 320x240,硬件旋转 */
|
||||
}
|
||||
|
||||
/* ========================= 方向设置 ========================= */
|
||||
|
||||
/* ================= 方向(硬件 MADCTL 旋转) =================
|
||||
* 若烧录后发现镜像/上下颠倒,只需换用同一行注释里给出的备选 MADCTL:
|
||||
* 横屏可选 0x28 / 0xE8,竖屏可选 0x48 / 0x88。
|
||||
*/
|
||||
void ILI9341_SetRotation(uint8_t rotation)
|
||||
{
|
||||
uint8_t madctl;
|
||||
|
||||
rotation %= 4;
|
||||
|
||||
switch (rotation)
|
||||
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;
|
||||
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);
|
||||
LCD_WriteCmd(0x36);
|
||||
LCD_WriteData8(madctl);
|
||||
}
|
||||
|
||||
/* ========================= 基础绘图(阻塞,保持不变) ========================= */
|
||||
/* ================= 阻塞式基础绘图(初始化清屏用) ================= */
|
||||
|
||||
void ILI9341_DrawPixel(uint16_t x, uint16_t y, uint16_t color)
|
||||
uint16_t ILI9341_Color565(uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
if (x >= lcd_width || y >= lcd_height)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ILI9341_SetAddressWindow(x, y, x, y);
|
||||
ILI9341_WriteData16(color);
|
||||
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)
|
||||
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;
|
||||
uint8_t buffer[128];
|
||||
uint16_t chunk_pixels;
|
||||
|
||||
if (x >= lcd_width || y >= lcd_height)
|
||||
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)
|
||||
{
|
||||
return;
|
||||
line[i] = (uint8_t)(color >> 8);
|
||||
line[i + 1] = (uint8_t)(color & 0xFF);
|
||||
}
|
||||
|
||||
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_SetWindow(x, y, x + w - 1, y + h - 1);
|
||||
|
||||
LCD_CS_LOW();
|
||||
LCD_DC_DATA();
|
||||
|
||||
while (pixels > 0)
|
||||
pixels = (uint32_t)w * h;
|
||||
while (pixels)
|
||||
{
|
||||
chunk_pixels = pixels > 64U ? 64U : (uint16_t)pixels;
|
||||
|
||||
HAL_SPI_Transmit(&hspi1,
|
||||
buffer,
|
||||
chunk_pixels * 2U,
|
||||
HAL_MAX_DELAY);
|
||||
|
||||
pixels -= chunk_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();
|
||||
@@ -423,293 +194,154 @@ 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)
|
||||
/* ================= D-Cache 维护 ================= */
|
||||
|
||||
static void LCD_CleanDCache(const void *addr, uint32_t size)
|
||||
{
|
||||
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 defined(__DCACHE_PRESENT) && (__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);
|
||||
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;
|
||||
(void)addr; (void)size;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void ILI9341_SPI_WaitIdle(void)
|
||||
/* ================= 异步刷新完成收尾(供 ISR / 看门狗调用) ================= */
|
||||
|
||||
static void LCD_FinishTransfer(void)
|
||||
{
|
||||
uint32_t tick_start;
|
||||
ILI9341_FlushDoneCb cb;
|
||||
void *arg;
|
||||
|
||||
#if defined(SPI_FLAG_TXC)
|
||||
tick_start = HAL_GetTick();
|
||||
/* 关中断内做“取出并清空回调 + 复位状态”,保证只收尾一次,
|
||||
* 杜绝 ISR 与看门狗竞争导致 done_cb 被调用两次。 */
|
||||
uint32_t primask = __get_PRIMASK();
|
||||
__disable_irq();
|
||||
|
||||
while (__HAL_SPI_GET_FLAG(&hspi1, SPI_FLAG_TXC) == RESET)
|
||||
if (lcd_state != LCD_BUSY)
|
||||
{
|
||||
if ((HAL_GetTick() - tick_start) > 20U)
|
||||
{
|
||||
break;
|
||||
}
|
||||
__set_PRIMASK(primask);
|
||||
return;
|
||||
}
|
||||
#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
|
||||
LCD_CS_HIGH();
|
||||
lcd_state = LCD_IDLE;
|
||||
cb = lcd_done_cb;
|
||||
arg = lcd_done_arg;
|
||||
lcd_done_cb = NULL;
|
||||
|
||||
__DSB();
|
||||
__set_PRIMASK(primask);
|
||||
|
||||
if (cb != NULL)
|
||||
{
|
||||
cb(arg);
|
||||
}
|
||||
}
|
||||
|
||||
void ILI9341_WriteArea_DMA(uint16_t x,
|
||||
uint16_t y,
|
||||
uint16_t w,
|
||||
uint16_t h,
|
||||
const uint16_t *pixels,
|
||||
ILI9341_DmaDoneCallback done_cb,
|
||||
/* ================= 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 total_pixels;
|
||||
uint32_t byte_len;
|
||||
|
||||
if (w == 0 || h == 0 || pixels == NULL)
|
||||
if (w == 0 || h == 0 || data == NULL)
|
||||
{
|
||||
if (done_cb != NULL)
|
||||
{
|
||||
done_cb(user_data);
|
||||
}
|
||||
if (done_cb) done_cb(user_data);
|
||||
return;
|
||||
}
|
||||
|
||||
total_pixels = (uint32_t)w * h;
|
||||
|
||||
if (total_pixels > LCD_DMA_MAX_PIXELS)
|
||||
/* 正常情况下 LVGL 在 flush_ready 之前不会再次进入,走到这里说明
|
||||
* 上一次传输异常未收尾:强制恢复后再继续,避免卡死。 */
|
||||
if (lcd_state == LCD_BUSY)
|
||||
{
|
||||
/* 超出单次 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_state = LCD_IDLE;
|
||||
}
|
||||
|
||||
lcd_dma_busy = 1;
|
||||
lcd_spi_last_error = 0;
|
||||
byte_len = (uint32_t)w * h * 2U;
|
||||
|
||||
/* 组包到 DMA buffer */
|
||||
for (uint32_t i = 0; i < total_pixels; i++)
|
||||
{
|
||||
uint16_t color = pixels[i];
|
||||
/* 登记回调并置忙。先置忙再启动,保证完成中断能看到 BUSY。 */
|
||||
lcd_done_cb = done_cb;
|
||||
lcd_done_arg = user_data;
|
||||
lcd_state = LCD_BUSY;
|
||||
lcd_start_tick = HAL_GetTick();
|
||||
|
||||
lcd_dma_buf[i * 2U] = (uint8_t)(color >> 8);
|
||||
lcd_dma_buf[i * 2U + 1] = (uint8_t)(color & 0xFF);
|
||||
}
|
||||
LCD_SetWindow(x, y, x + w - 1U, y + h - 1U);
|
||||
|
||||
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();
|
||||
/* DMA 读取前 clean D-Cache,确保面板拿到最新像素 */
|
||||
LCD_CleanDCache(data, byte_len);
|
||||
|
||||
LCD_CS_LOW();
|
||||
LCD_DC_DATA();
|
||||
|
||||
if (HAL_SPI_Transmit_DMA(&hspi1, lcd_dma_buf, (uint16_t)byte_len) != HAL_OK)
|
||||
if (HAL_SPI_Transmit_DMA(&hspi1, (uint8_t *)data, (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);
|
||||
}
|
||||
|
||||
lcd_state = LCD_IDLE;
|
||||
lcd_done_cb = NULL;
|
||||
if (done_cb) 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);
|
||||
}
|
||||
/* 传输完成 → HAL_SPI_TxCpltCallback;出错 → HAL_SPI_ErrorCallback。 */
|
||||
}
|
||||
|
||||
void ILI9341_DMA_Watchdog(uint32_t timeout_ms)
|
||||
uint8_t ILI9341_IsBusy(void)
|
||||
{
|
||||
if (!lcd_dma_busy)
|
||||
return (lcd_state == LCD_BUSY) ? 1U : 0U;
|
||||
}
|
||||
|
||||
/* ================= 看门狗(任务上下文调用) ================= */
|
||||
|
||||
void ILI9341_ServiceWatchdog(uint32_t timeout_ms)
|
||||
{
|
||||
if (lcd_state != LCD_BUSY)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t elapsed = HAL_GetTick() - lcd_dma_start_tick;
|
||||
|
||||
if (elapsed > timeout_ms)
|
||||
if ((HAL_GetTick() - lcd_start_tick) <= 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);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* 传输疑似卡死:中止 DMA、重建 SPI,再收尾(触发完成回调)。 */
|
||||
HAL_SPI_Abort(&hspi1);
|
||||
|
||||
if (HAL_SPI_DeInit(&hspi1) == HAL_OK)
|
||||
{
|
||||
HAL_SPI_Init(&hspi1);
|
||||
}
|
||||
|
||||
LCD_FinishTransfer();
|
||||
}
|
||||
|
||||
/*
|
||||
* SPI1 TX DMA 完成回调(ISR 上下文)。
|
||||
* 只做最必要的事:释放信号量。不打印日志、不调用 LVGL。
|
||||
*/
|
||||
/* ================= SPI 中断回调(ISR 上下文,尽量短) ================= */
|
||||
|
||||
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
|
||||
{
|
||||
if (hspi->Instance == SPI1)
|
||||
{
|
||||
if (lcd_dma_sem != NULL)
|
||||
{
|
||||
osSemaphoreRelease(lcd_dma_sem);
|
||||
}
|
||||
/* H7 上 TxCplt 由 SPI EOT 触发,此刻数据已全部移出,可直接拉高 CS。 */
|
||||
LCD_FinishTransfer();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
/* 记录并中止,避免把坏状态带入下一帧。收尾同样会触发完成回调,
|
||||
* 让 LVGL 继续,不至于卡在 flushing。 */
|
||||
HAL_SPI_Abort(hspi);
|
||||
LCD_FinishTransfer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user