我把你新传的afc852a1-1018-4da6-874e-6ed587f16fd0.cc里跟 LCD/SPI/LVGL 相关的地方对上了,你现在这个panel io spi tx color(...): spi transmit (queue) color failed,在你代码里有两个“非常容易把 SPI 队列打爆/把一次传输拖得很长”的点,会导致“偶尔第一句话就卡死、卡在文字渲染”。
下面按“结合文件”的方式把关键点和改法给你(直接对应你文件里的行号附近)。
1) 你现在的 LVGL 刷新块太大:buffer_size = width * 300
在display_cfg里(大概 172 行附近):
.buffer_size = static_cast<uint32_t>(width * 300), .double_buffer = true, .flags = { .buff_dma = 1, .buff_spiram = 1, ... },你屏是 360×360、RGB565(2 字节/像素):
单 buffer 数据量:
360 * 300 * 2 = 216,000 bytes双 buffer:约432 KB(还不算其它开销)
问题:
刷新块大 → 单次 flush 持有 SPI 总线时间长 → 下一次 flush/其它 draw 进来更容易出现 “queue 队列满/排队失败”
buff_dma=1+buff_spiram=1这组组合也很危险:要么分配失败/退化成 bounce copy,要么更吃内部 DMA 资源(不同 IDF/port 行为不完全一样,但都不“稳”)。
✅建议(优先改):把 LVGL 刷新块改小到 40~80 行
比如 60 行最常用:
.buffer_size = static_cast<uint32_t>(width * 60), .double_buffer = true, .flags = { .buff_dma = 1, // 小 buffer 放内部 DMA .buff_spiram = 0, // 关掉 spiram,避免 DMA/PSRAM 组合坑 ... },60 行时:
360*60*2 = 43,200 bytes,双 buffer 也就 86KB,很稳。
2) 你初始化时“逐行清屏”可能在启动阶段直接把 SPI 队列塞爆
在CustomSpiLcdDisplay构造里(大概 134~139 行):
std::vector<uint16_t> buffer(width, 0xFFFF); for (int y = 0; y < height; y++) { esp_lcd_panel_draw_bitmap(panel_, 0, y, width, y + 1, buffer.data()); }这段是360 次 draw_bitmap,如果底层是用 queue 异步发(你日志就是 queue fail),这段很容易在某些时序下把trans_queue_depth=10的队列填满,然后开始报:
spi transmit (queue) color failed
更关键的是:你这里没检查返回值,失败了继续跑,后面 LVGL 再开始渲染就可能进入“等不到 flush 完成”的卡死表现。
✅建议:把清屏改成分块(比如每次 40 行)+ 检查返回值
例如:
const int LINES = 40; std::vector<uint16_t> buffer(width * LINES, 0xFFFF); for (int y = 0; y < height; y += LINES) { int y2 = std::min(y + LINES, height); esp_err_t ret = esp_lcd_panel_draw_bitmap(panel_, 0, y, width, y2, buffer.data()); if (ret != ESP_OK) { ESP_LOGE(TAG, "clear screen failed at y=%d: %s", y, esp_err_to_name(ret)); vTaskDelay(pdMS_TO_TICKS(5)); // 给 SPI 队列一点喘息 y -= LINES; // 简单重试 } }这一下从 360 次 transaction 变成 9 次 transaction,队列满的概率会骤降。
3)max_transfer_sz = 64现在确实不合理,但“改很大更频繁出问题”也解释得通
你 SPI bus 初始化(大概 390 行):
.max_transfer_sz = 64,你说改大后更频繁出问题 —— 这其实常见:
传输越大 -> 单次占用 SPI 越久 -> 队列消费越慢 -> 更容易 queue 满而失败(尤其你 LVGL 还开了 double buffer)。
✅建议:不要一步改到“超大”(比如整屏/300行),而是跟“分块行数”匹配,改到刚好够用
如果你按上面建议用 60 行:
.max_transfer_sz = DISPLAY_WIDTH * 60 * 2 + 8, // 约 43208如果清屏用 40 行,那 max_transfer_sz 至少也得覆盖 40 行那一块:
.max_transfer_sz = DISPLAY_WIDTH * 40 * 2 + 8, // 约 288084)trans_queue_depth = 10:先别盲目加大,先把“块”缩小
你 io_config(大概 408 行):
.trans_queue_depth = 10, .pclk_hz = 80 * 1000 * 1000,在你当前“大块刷新 + 逐行清屏”条件下,队列深度再大也可能照样打爆,而且更吃内存。
✅ 我建议你按这个顺序来:
先把清屏改分块+LVGL buffer 改 60 行以内
仍然出现 queue fail,再把
trans_queue_depth从 10 提到 15/20同时建议你把
pclk_hz先降到 40MHz 做稳定性验证(80MHz 对硬件边界更敏感):
.pclk_hz = 40 * 1000 * 1000,5) 你现在“卡在文字渲染”本质是:某次 flush 失败后 LVGL 等不到完成
这点你描述得非常典型:SPI queue 失败 -> port/驱动没把这一帧 flush 走完 -> LVGL 等 flush_ready -> UI 就卡住。
你现在用的是lvgl_port_add_disp(),flush 逻辑在 port 里。短期内最有效的办法不是去改 port,而是让 queue fail 不再发生(上面 1~4)。
给你一套“最稳”的落地配置(按你 360×360)
你可以直接按这套改,基本就是“把一次刷新的块缩小到 40~60 行,避免塞爆队列”:
LVGL:
buffer_size = width * 60double_buffer = true(先保留)buff_dma=1, buff_spiram=0(小 buffer 放内部 DMA)
SPI bus:
max_transfer_sz = width * 60 * 2 + 8
清屏:
不要逐行,改成 40 行一块,检查错误并稍微 delay/重试
可选稳定性:
pclk_hz从 80MHz 降到 40MHz 验证TE(
enable_tearing_effect)如果你硬件/信号不稳,也建议先关掉验证一轮(因为 TE 会让刷新等待更不可控)