重构 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:
+3
-1
@@ -13,7 +13,9 @@
|
||||
*====================*/
|
||||
|
||||
#define LV_COLOR_DEPTH 16
|
||||
#define LV_COLOR_16_SWAP 0
|
||||
/* =1:LVGL 直接产出 ILI9341 需要的大端字节序,SPI(8bit)+DMA 可直接搬运,
|
||||
* 无需在 flush 里逐像素交换/拷贝。改动此项必须整体重编 LVGL。 */
|
||||
#define LV_COLOR_16_SWAP 1
|
||||
#define LV_COLOR_SCREEN_TRANSP 0
|
||||
#define LV_COLOR_MIX_ROUND_OFS 0
|
||||
#define LV_COLOR_CHROMA_KEY lv_color_hex(0x00ff00)
|
||||
|
||||
+5
-7
@@ -284,14 +284,12 @@ for(;;)
|
||||
lv_timer_handler();
|
||||
|
||||
/*
|
||||
* DMA 看门狗:兜底极端情况下 DMA 传输既没有触发完成中断
|
||||
* 也没有触发错误中断(硬件卡死/中断丢失)时,强制恢复 SPI
|
||||
* 状态机,避免画面永久冻结。正常情况下 ILI9341_WriteArea_DMA
|
||||
* 内部的信号量超时已经能处理绝大多数异常,这里只是双保险。
|
||||
* timeout_ms 要大于 ILI9341_WriteArea_DMA 内部的 100ms 超时,
|
||||
* 避免两层保护互相打架。
|
||||
* LCD 看门狗:新驱动是全异步的,正常刷新由 SPI 完成/错误中断
|
||||
* 自动收尾,不阻塞本任务。此处只兜底极端情况——一次 DMA 既没
|
||||
* 触发完成中断也没触发错误中断(硬件卡死/中断丢失)时,强制
|
||||
* 中止并重建 SPI,恢复刷新管线,避免长时间白屏/冻结。
|
||||
*/
|
||||
ILI9341_DMA_Watchdog(180);
|
||||
ILI9341_ServiceWatchdog(180);
|
||||
|
||||
osDelay(5);
|
||||
}
|
||||
|
||||
+43
-144
@@ -1,9 +1,18 @@
|
||||
/**
|
||||
* @file lv_port_disp.c
|
||||
*
|
||||
* ILI9341 + LVGL 显示接驳(重写版)
|
||||
* ------------------------------------------------------------------
|
||||
* - 硬件旋转:面板由 MADCTL 直接设为横屏 320x240,这里不再做任何
|
||||
* 软件转置,LVGL 坐标与面板坐标 1:1,移动物体不再留残影。
|
||||
* - 双绘制缓冲 + 异步 DMA:flush_cb 启动 DMA 后立即返回,LVGL 可
|
||||
* 在另一块缓冲上继续渲染;DMA 完成的中断回调里调用
|
||||
* lv_disp_flush_ready(),刷新与传输天然并行、时序自同步。
|
||||
* - 两块绘制缓冲用 LCD_RAM_ATTR 放入 .lcd_ram 段(AXI SRAM),
|
||||
* 保证 DMA 可访问;配合 lv_conf.h 的 LV_COLOR_16_SWAP=1,
|
||||
* LVGL 产出的字节序即面板发送序,DMA 直接搬运,无需拷贝。
|
||||
*/
|
||||
|
||||
/*Copy this file as "lv_port_disp.c" and set this value to "1" to enable content*/
|
||||
#if 1
|
||||
|
||||
/*********************
|
||||
@@ -17,95 +26,52 @@
|
||||
* DEFINES
|
||||
*********************/
|
||||
#ifndef MY_DISP_HOR_RES
|
||||
#define MY_DISP_HOR_RES 320 /* LVGL 逻辑横屏宽度 */
|
||||
#define MY_DISP_HOR_RES 320 /* 横屏宽度,与 MADCTL 一致 */
|
||||
#endif
|
||||
|
||||
#ifndef MY_DISP_VER_RES
|
||||
#define MY_DISP_VER_RES 240 /* LVGL 逻辑横屏高度 */
|
||||
#define MY_DISP_VER_RES 240 /* 横屏高度 */
|
||||
#endif
|
||||
|
||||
/* 面板原生(未旋转)物理分辨率,用于软件转置计算 */
|
||||
#define PANEL_NATIVE_W 240
|
||||
#define PANEL_NATIVE_H 320
|
||||
|
||||
/*
|
||||
* LVGL 局部刷新缓冲区行数。
|
||||
* MY_DISP_HOR_RES * DISP_BUF_LINES * 2 字节 = 单次 flush 的数据量,
|
||||
* 越小单次 SPI/DMA 传输耗时越短,越不容易和面板固定的内部扫描
|
||||
* 周期(约 14~16ms 一帧)撞在一起造成撕裂。8 行是当前验证下来
|
||||
* 比较均衡的取值(320*8*2 = 5120 字节,约 4ms 传输时间)。
|
||||
* 绘制缓冲行数。单块 = MY_DISP_HOR_RES * DISP_BUF_LINES 像素。
|
||||
* 20 行:320*20*2 = 12800 字节/块,两块共 25600 字节,均位于 AXI SRAM。
|
||||
* DMA 单次字节数(12800) < DMA NDTR 上限(65535),安全。
|
||||
*/
|
||||
#define DISP_BUF_LINES 8
|
||||
|
||||
/*
|
||||
* 为什么用单缓冲(Example 1)而不是模板里的双缓冲(Example 2):
|
||||
* 双缓冲的价值在于 flush_cb 立刻返回、LVGL 边渲染下一块边等待
|
||||
* DMA 传输完成。但当前 ILI9341_WriteArea_DMA() 内部是"发起 DMA
|
||||
* 后用信号量阻塞等待传完再返回"(详见 ILI9341.c 里的实现和之前
|
||||
* 的讨论),flush_cb 本身就是同步返回的,双缓冲不会带来任何并行
|
||||
* 收益,只会多占一份 RAM。如果以后把 ILI9341_WriteArea_DMA 改成
|
||||
* 真正的"发起后立即返回、由中断回调里调用 lv_disp_flush_ready",
|
||||
* 再切回模板的双缓冲写法(Example 2)才有意义。
|
||||
*/
|
||||
|
||||
/**********************
|
||||
* TYPEDEFS
|
||||
**********************/
|
||||
#define DISP_BUF_LINES 20
|
||||
|
||||
/**********************
|
||||
* STATIC PROTOTYPES
|
||||
**********************/
|
||||
static void disp_init(void);
|
||||
|
||||
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);
|
||||
|
||||
static void disp_flush_done_cb(void * user_data);
|
||||
|
||||
/**********************
|
||||
* STATIC VARIABLES
|
||||
**********************/
|
||||
|
||||
/* 90 度旋转后的临时缓冲:把 landscape 数据转置成面板原生 portrait 排列。
|
||||
* 大小和绘制缓冲一致,装得下一整条 flush 的像素。 */
|
||||
static uint16_t rot_buf[MY_DISP_HOR_RES * DISP_BUF_LINES];
|
||||
|
||||
/**********************
|
||||
* MACROS
|
||||
**********************/
|
||||
/* 双绘制缓冲,必须放在 DMA 可访问内存(.lcd_ram → AXI SRAM) */
|
||||
static LCD_RAM_ATTR lv_color_t buf_1[MY_DISP_HOR_RES * DISP_BUF_LINES];
|
||||
static LCD_RAM_ATTR lv_color_t buf_2[MY_DISP_HOR_RES * DISP_BUF_LINES];
|
||||
|
||||
/**********************
|
||||
* GLOBAL FUNCTIONS
|
||||
**********************/
|
||||
|
||||
void lv_port_disp_init(void)
|
||||
{
|
||||
/*-------------------------
|
||||
* Initialize your display
|
||||
* -----------------------*/
|
||||
disp_init();
|
||||
|
||||
/*-----------------------------
|
||||
* Create a buffer for drawing
|
||||
*----------------------------*/
|
||||
|
||||
/* Example for 1) 单缓冲:原因见上面 DISP_BUF_LINES 注释 */
|
||||
static lv_disp_draw_buf_t draw_buf_dsc_1;
|
||||
static lv_color_t buf_1[MY_DISP_HOR_RES * DISP_BUF_LINES];
|
||||
lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * DISP_BUF_LINES);
|
||||
|
||||
/*-----------------------------------
|
||||
* Register the display in LVGL
|
||||
*----------------------------------*/
|
||||
static lv_disp_draw_buf_t draw_buf_dsc;
|
||||
lv_disp_draw_buf_init(&draw_buf_dsc, buf_1, buf_2,
|
||||
MY_DISP_HOR_RES * DISP_BUF_LINES);
|
||||
|
||||
static lv_disp_drv_t disp_drv;
|
||||
lv_disp_drv_init(&disp_drv);
|
||||
|
||||
disp_drv.hor_res = MY_DISP_HOR_RES;
|
||||
disp_drv.ver_res = MY_DISP_VER_RES;
|
||||
|
||||
disp_drv.hor_res = MY_DISP_HOR_RES;
|
||||
disp_drv.ver_res = MY_DISP_VER_RES;
|
||||
disp_drv.flush_cb = disp_flush;
|
||||
|
||||
disp_drv.draw_buf = &draw_buf_dsc_1;
|
||||
disp_drv.draw_buf = &draw_buf_dsc;
|
||||
|
||||
lv_disp_drv_register(&disp_drv);
|
||||
}
|
||||
@@ -113,50 +79,19 @@ void lv_port_disp_init(void)
|
||||
/**********************
|
||||
* STATIC FUNCTIONS
|
||||
**********************/
|
||||
|
||||
/*Initialize your display and the required peripherals.*/
|
||||
static void disp_init(void)
|
||||
{
|
||||
/*
|
||||
* 把 ILI9341 的硬件初始化收拢到这里(原来分散在 freertos.c 里)。
|
||||
* 注意:这里只调用 ILI9341_Init(),它内部末尾会自动执行
|
||||
* SetRotation(0)(面板原生方向)。不要再调用
|
||||
* ILI9341_SetRotation(1),横屏旋转已经改成在 disp_flush()
|
||||
* 里用软件转置实现,如果再叠加 MADCTL 的 MV 位会导致画面
|
||||
* 完全错乱。
|
||||
*
|
||||
* 对应地,freertos.c 里原来的:
|
||||
* ILI9341_Init();
|
||||
* ILI9341_SetRotation(1);
|
||||
* ILI9341_FillScreen(ILI9341_BLACK);
|
||||
* 这三行请删掉,改成只调用:
|
||||
* lv_init();
|
||||
* lv_port_disp_init();
|
||||
* (init/rotation/清屏都移到这里统一做)
|
||||
*/
|
||||
/* 硬件初始化 + 清屏都收拢在这里;ILI9341_Init() 末尾已设为横屏,
|
||||
* 不要在别处再调用 ILI9341_SetRotation()。 */
|
||||
ILI9341_Init();
|
||||
ILI9341_FillScreen(ILI9341_BLACK);
|
||||
}
|
||||
|
||||
volatile bool disp_flush_enabled = true;
|
||||
|
||||
/* Enable updating the screen (the flushing process) when disp_flush() is called by LVGL
|
||||
*/
|
||||
void disp_enable_update(void)
|
||||
{
|
||||
disp_flush_enabled = true;
|
||||
}
|
||||
void disp_enable_update(void) { disp_flush_enabled = true; }
|
||||
void disp_disable_update(void) { disp_flush_enabled = false; }
|
||||
|
||||
/* Disable updating the screen (the flushing process) when disp_flush() is called by LVGL
|
||||
*/
|
||||
void disp_disable_update(void)
|
||||
{
|
||||
disp_flush_enabled = false;
|
||||
}
|
||||
|
||||
/*Flush the content of the internal buffer the specific area on the display
|
||||
*You can use DMA or any hardware acceleration to do this operation in the background but
|
||||
*'lv_disp_flush_ready()' has to be called when finished.*/
|
||||
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
|
||||
{
|
||||
if (!disp_flush_enabled)
|
||||
@@ -165,11 +100,10 @@ static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_colo
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t x1 = area->x1;
|
||||
int32_t y1 = area->y1;
|
||||
int32_t x2 = area->x2;
|
||||
int32_t y2 = area->y2;
|
||||
int32_t x1 = area->x1, y1 = area->y1;
|
||||
int32_t x2 = area->x2, y2 = area->y2;
|
||||
|
||||
/* 面板外的区域直接跳过 */
|
||||
if (x2 < 0 || y2 < 0 || x1 >= MY_DISP_HOR_RES || y1 >= MY_DISP_VER_RES)
|
||||
{
|
||||
lv_disp_flush_ready(disp_drv);
|
||||
@@ -181,56 +115,21 @@ static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_colo
|
||||
if (x2 >= MY_DISP_HOR_RES) x2 = MY_DISP_HOR_RES - 1;
|
||||
if (y2 >= MY_DISP_VER_RES) y2 = MY_DISP_VER_RES - 1;
|
||||
|
||||
uint16_t lx0 = (uint16_t)x1;
|
||||
uint16_t ly0 = (uint16_t)y1;
|
||||
uint16_t lw = (uint16_t)(x2 - x1 + 1);
|
||||
uint16_t lh = (uint16_t)(y2 - y1 + 1);
|
||||
|
||||
/*
|
||||
* 90 度顺时针旋转映射:
|
||||
* 物理列 xp = (PANEL_NATIVE_W - 1) - 逻辑行 ly
|
||||
* 物理行 yp = 逻辑列 lx
|
||||
*
|
||||
* !!! 如果烧录后发现镜像/上下颠倒/转向错误,只需要调整这
|
||||
* 几行的正负号,不用改架构。备选公式:
|
||||
* a) xp = ly; yp = (MY_DISP_HOR_RES-1) - lx;
|
||||
* b) xp = (PANEL_NATIVE_W-1) - ly; yp = (MY_DISP_HOR_RES-1) - lx;
|
||||
* c) xp = ly; yp = lx;
|
||||
*/
|
||||
uint16_t px0 = (uint16_t)(PANEL_NATIVE_W - ly0 - lh);
|
||||
uint16_t py0 = lx0;
|
||||
uint16_t pw = lh;
|
||||
uint16_t ph = lw;
|
||||
|
||||
for (uint16_t ly = 0; ly < lh; ly++)
|
||||
{
|
||||
for (uint16_t lx = 0; lx < lw; lx++)
|
||||
{
|
||||
uint16_t rel_xp = (uint16_t)(lh - 1U - ly);
|
||||
uint16_t rel_yp = lx;
|
||||
|
||||
rot_buf[(uint32_t)rel_yp * pw + rel_xp] =
|
||||
(uint16_t)color_p[(uint32_t)ly * lw + lx].full;
|
||||
}
|
||||
}
|
||||
|
||||
ILI9341_WriteArea_DMA(px0, py0, pw, ph,
|
||||
rot_buf,
|
||||
disp_flush_done_cb,
|
||||
disp_drv);
|
||||
/* 硬件旋转:LVGL 坐标即面板坐标,直接把这块像素 DMA 出去。
|
||||
* LV_COLOR_16_SWAP=1 保证 color_p 的字节序即面板发送序。 */
|
||||
ILI9341_FlushArea_DMA((uint16_t)x1, (uint16_t)y1,
|
||||
(uint16_t)(x2 - x1 + 1), (uint16_t)(y2 - y1 + 1),
|
||||
(const uint16_t *)color_p,
|
||||
disp_flush_done_cb, disp_drv);
|
||||
}
|
||||
|
||||
/*
|
||||
* ILI9341_WriteArea_DMA 完成后的回调:通知 LVGL 这一块已经刷新完。
|
||||
*/
|
||||
/* DMA 传输完成回调(SPI 中断上下文):通知 LVGL 这一块刷新完成。
|
||||
* lv_disp_flush_ready() 只是清标志,可安全从中断调用。 */
|
||||
static void disp_flush_done_cb(void * user_data)
|
||||
{
|
||||
lv_disp_drv_t * disp_drv = (lv_disp_drv_t *)user_data;
|
||||
lv_disp_flush_ready(disp_drv);
|
||||
lv_disp_flush_ready((lv_disp_drv_t *)user_data);
|
||||
}
|
||||
|
||||
#else /*Enable this file at the top*/
|
||||
|
||||
/*This dummy typedef exists purely to silence -Wpedantic.*/
|
||||
#else
|
||||
typedef int keep_pedantic_happy;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user