移植到自己的开发板(零基础教程)¶
这篇教程面向第一次给 ESP32 接屏幕的人。目标不是让你复制一块已有板子的代码,而是让你按顺序证明每一层都能工作,最后得到一个容易维护的新板型。
请从 templates/board/ 开始。模板里的显示部分是一个常见 SPI + ST7789 示例,不是所有屏幕的通用答案。你的屏幕如果使用 ILI9341、ST7796、I80 并口、RGB 并口、QSPI 或 MIPI,显示部分必须按本章选择对应路线。
第 5 步的输入先做一级选择:纯旋钮板走“无触摸”路线;带触摸的板走“有触摸”路线,再按控制器区分“电阻屏”或“电容屏”。旋钮可以叠加到任意有触摸路线,触摸与旋钮可以共存。

整个移植分成七步,每一步都有一个明确的成功标准:
- 准备环境,并成功编译已有板型。
- 查清硬件,先判断屏幕接口属于哪一类。
- 创建并登记一个干净的板型骨架。
- 只点亮屏幕,显示稳定的纯色测试图。
- 把已经验证的显示代码接入 BSP 和 LVGL,再加入输入。
- 编译并分层验证正式固件。
- 按现象排错,准备贡献 PR。
一次只解决一层
点屏阶段不要同时加入触摸、WiFi、Moonraker 和完整 UI。背光亮起也不等于屏幕已经工作:背光 LED 和液晶像素通常是两套独立电路。
第 0 步:准备环境¶
0.1 安装 ESP-IDF v5.5.5¶
本项目固定使用 ESP-IDF v5.5.5。
- Windows:用乐鑫安装器安装 v5.5.5。默认会得到
C:\esp\v5.5.5\esp-idf和C:\Espressif\tools。 - Linux/macOS:使用对应版本 ESP-IDF 自带的
install.sh。
仓库里的 tools/idf.ps1、tools/idf-env.bat 和构建脚本会负责激活环境,不需要自己长期修改 PATH。
0.2 拉取项目并验证工具链¶
git clone https://github.com/umeiko/KlipperScreen-esp.git
cd KlipperScreen-esp
bash tools/build-esp32.sh cyd_2432s028r
末尾出现 Project build complete,并且应用没有超过 Smallest app partition,才说明工具链正常。第一次构建需要下载依赖,通常比后续增量构建慢很多。
本步成功标准:不修改源码也能编译一个已有板型。做不到就先修环境,不要开始改显示驱动。
第 1 步:先把硬件资料查完整¶
1.1 建立一张板型资料卡¶
从原理图、厂商示例、屏幕排线丝印和芯片手册里填写下面的表。商品标题经常只写屏幕尺寸,不能代替这些信息。
| 项目 | 你要记录的内容 | 例子 |
|---|---|---|
| ESP 芯片 | 精确型号 | ESP32、ESP32-S3、ESP32-P4 |
| Flash / PSRAM | 容量、模式、频率 | 16MB Flash、8MB OPI PSRAM |
| 显示控制器或时序芯片 | 完整型号 | ILI9341、ST7789、ST7796、ST7262 |
| 像素接口 | SPI、I80、RGB、QSPI、MIPI DSI | SPI |
| 原生分辨率 | 面板未旋转时的宽 × 高 | 240×320 |
| 显示引脚 | 全部信号名和 GPIO | SCLK、MOSI、CS、DC、RST |
| 显示参数 | SPI mode / 频率,或 RGB 时序 | 40MHz、mode 0 |
| 背光 | GPIO、有效电平、是否支持 PWM | GPIO21,高电平点亮 |
| 触摸(可选) | 控制器、接口、共用总线情况 | XPT2046,独立 SPI |
| 旋钮(可选) | A、B、按键 GPIO | GPIO4/5/6 |
资料优先级建议是:能稳定运行的同板厂商示例 > 原理图和屏幕规格书 > 同控制器参考代码 > 商品页 > 猜测。
1.2 先根据引脚名字判断接口¶
“并口屏”不是一种实现。I80 和 RGB 都有很多数据线,但工作方式完全不同。
| 常见引脚 | 接口类型 | 屏幕如何收图 | 本项目中的起点 |
|---|---|---|---|
SCLK/MOSI/CS/DC/RST |
SPI 命令屏 | MCU 发送命令和像素,屏内 GRAM 保存画面 | 板型模板、CYD、E32R35T |
D0..D7/15 + WR/RD/CS/DC |
I80/8080 命令并口 | 和 SPI 类似,只是一次并行传 8/16 位 | ESP-IDF I80 示例 + 匹配的 panel driver |
R0..B4 + PCLK/HSYNC/VSYNC/DE |
RGB/DOTCLK 并口 | MCU 必须连续输出整帧,屏通常不替你保存画面 | ESP-IDF RGB panel 示例;JC8048 仅作特殊案例 |
CLK + D0..D3 + CS |
QSPI | 用 4 条数据线传命令或像素,协议依控制器而定 | 对应控制器驱动或厂商示例 |
D0P/D0N、CLKP/CLKN |
MIPI DSI | 高速差分链路 | 仅在芯片支持 DSI 时从 ESP-IDF DSI 示例开始 |
控制器型号和接口是两个问题
同一种 ST7789 控制器可以接 SPI,也可以接 I80。选择哪套总线代码要看板子实际接出了哪些引脚,不能只看“ST7789”这个名字。
1.3 判断内存是否够用¶
RGB565 每个像素占 2 字节:
一帧字节数 = 宽 × 高 × 2
320 × 240 = 153,600 字节
480 × 320 = 307,200 字节
800 × 480 = 768,000 字节
SPI/I80 命令屏通常只需要 20~40 行的局部 DMA 缓冲。RGB 屏通常需要至少一块完整帧缓冲,双缓冲则需要两倍空间。大分辨率 RGB 屏基本离不开 PSRAM,还要考虑 PSRAM 与显示 DMA 是否争用带宽。
本步成功标准:你能明确说出自己的接口类型,并把所有显示引脚和关键参数写进资料卡。接口还不确定时不要复制 BSP。
第 2 步:创建并登记板型骨架¶
先复制干净模板:
cp templates/board/bsp_board_template.c src/bsp/esp32/bsp_myboard.c
cp templates/board/sdkconfig.defaults.board_template src/ports/esp32/sdkconfig.defaults.myboard
替换 BOARD_TEMPLATE、board_template 和全部 TODO(board)。此时显示部分仍可以暂时保留示例 ST7789 transport;第 3 步会在硬件测试前替换它。
需要修改以下位置:
| 文件 | 要做什么 |
|---|---|
src/bsp/Kconfig.projbuild |
在 board choice 中添加 CONFIG_BOARD_MYBOARD |
src/bsp/CMakeLists.txt |
添加 bsp_myboard.c;登记新显示/触摸组件依赖 |
src/ports/esp32/entry/idf_component.yml |
添加组件注册表中的新 driver 依赖 |
src/ports/esp32/sdkconfig.defaults.myboard |
芯片、Flash、PSRAM、板型和旋钮默认项 |
src/ui/ui_layout.c |
为分辨率选择小/大字体档 |
tools/build-esp32.sh |
增加 target、build 目录和 sdkconfig 名称 |
板型默认配置从模板开始,只从同芯片板型复制芯片/Flash/PSRAM 配置。不要复制另一个板子的显示 GPIO 或触摸配置。
sdkconfig 容易让人误判
第一次构建后会生成完整的 sdkconfig.myboard。之后只改 sdkconfig.defaults.myboard 不会更新旧 sdkconfig。调配置时要么同步修改两者,要么确认可以丢弃旧配置后重新生成。# CONFIG_XXX is not set 也会覆盖 defaults。
第 3 步:单独点亮屏幕¶
这一章只解决一件事:让屏幕稳定显示红、绿、蓝、白、黑五条色带。只修改第 2 步新建板型文件中的显示 transport;LVGL 和输入留到后面。
3.1 先分清“背光”和“画面”¶
- 背光亮、整屏白色:通常只证明背光供电正常,显示控制器可能没有初始化。
- 背光不亮、串口日志正常:先检查背光 GPIO、电平和供电;像素可能已经在刷新,只是你看不见。
- 背光亮、颜色条稳定:显示总线、初始化序列和最基本的像素传输已经成立。
点屏时先让背光固定 100%,暂时不要加入亮度滑杆和自动息屏。
3.2 找一个“已知能亮”的最小参考¶
优先使用同一块板子的厂商例程。先原样编译、烧录并确认它真的稳定,再抄出这些参数:
- 显示接口和引脚;
- 复位、背光和显示使能电平;
- SPI mode、SPI 时钟,或 RGB PCLK 与 porch/pulse 时序;
- 控制器初始化命令;
- RGB/BGR、反色、旋转和坐标偏移;
- 缓冲放在内部 RAM 还是 PSRAM。
如果厂商例程也不亮,应先解决接线、供电或资料错误。本项目的 UI 无法补救错误的硬件参数。
3.3 所有接口都遵循同一个点屏顺序¶
- 配置供电、背光和复位 GPIO。
- 初始化像素总线。
- 创建 panel 或时序驱动。
- 复位屏幕。
- 发送初始化序列。
- 打开显示输出。
- 推送五色测试图。
- 保持静止至少 30 秒,观察闪烁、偏移和撕裂。
只有第 2、3、5、7 步会随屏幕类型大幅变化。
3.4 路线 A:SPI 命令屏¶
适合 ILI9341、ST7789、ST7796 等通过 SPI 接线的屏幕。模板就是这条路线。
先改什么¶
- 把模板中的引脚改成原理图数值。没有 MISO 很正常,显示通常只写不读。
- 从 10~20MHz 开始验证,稳定后再逐步提高到厂商确认的频率。
- 按厂商示例填写
spi_mode,不要靠反复试四种模式代替查资料。 - 把
esp_lcd_new_panel_st7789()换成真实控制器的构造函数。 - 如果该驱动不在当前依赖中,在
idf_component.yml和 BSP 的 CMake 依赖里登记它。 - 按实际需要设置
swap_xy、mirror、invert_color和set_gap。
总线、panel IO 和控制器是三层不同的东西:
GPIO/SPI host
└─ esp_lcd_new_panel_io_spi() 负责怎么发送字节
└─ esp_lcd_new_panel_xxx() 负责发送什么初始化命令
└─ draw_bitmap() 负责把一个矩形像素块写进屏幕 GRAM
换了控制器时,通常不只是换头文件。初始化命令、颜色格式、可见区域偏移和休眠/唤醒命令都可能不同。
SPI 屏适合的 LVGL 模式¶
使用 LV_DISPLAY_RENDER_MODE_PARTIAL,准备两块 20~40 行的 MALLOC_CAP_DMA 缓冲。flush 把 area 对应的矩形交给 esp_lcd_panel_draw_bitmap()。
DMA 传输是异步的。只有确认传输完成后,才能调用 lv_display_flush_ready() 让 LVGL 复用缓冲。模板用 on_color_done + 信号量完成这个等待。
常见 SPI 特有问题¶
- 全白:CS/DC/RST 错、控制器驱动错、初始化命令没发出。
- 整体图像平移或边缘缺失:需要
esp_lcd_panel_set_gap()。 - 红蓝互换:切换 RGB/BGR。
- 像照片负片:切换
esp_lcd_panel_invert_color()。 - 颜色像随机雪花:先降 SPI 时钟,再检查 RGB565 字节顺序。
- 第一帧正常,动画后破碎:DMA 缓冲在传完以前被释放或重写。
3.5 路线 B:I80/8080 命令并口¶
I80 屏有 WR、RD、CS、DC 和 8/16 根数据线。它虽然叫并口,工作模型仍接近 SPI 命令屏:屏内有 GRAM,MCU 把矩形区域写进去后,屏幕自己保持画面。
与 SPI 路线相比,主要替换两层:
esp_lcd_new_i80_bus()
esp_lcd_new_panel_io_i80()
后面的 esp_lcd_new_panel_xxx()、esp_lcd_panel_draw_bitmap()、传输完成回调和 LVGL PARTIAL 缓冲思路通常仍可复用。数据位宽、WR 时钟、数据线顺序和最大传输字节数必须来自原理图或已知可用示例。
不要把 I80 当成 RGB
I80 有 WR/DC/CS,RGB 有 PCLK/HSYNC/VSYNC/DE。二者的驱动、缓冲和时序不可互换。
3.6 路线 C:RGB/DOTCLK 并口¶
RGB 屏有多根颜色数据线和 PCLK/HSYNC/VSYNC/DE。屏幕按像素时钟不停扫描;如果数据停止,画面通常也不能像 SPI 屏那样继续由 GRAM 保持。
必须从可靠来源得到的参数¶
- 每根 R/G/B 数据线对应的 GPIO,顺序不能错;
PCLK频率以及在哪个边沿采样;- HSYNC/VSYNC 的 pulse width、back porch、front porch;
- 是否使用 DE,及其有效电平;
- 分辨率和一行/一帧的总时序;
- 帧缓冲位置、数量及 PSRAM 配置。
先从 ESP-IDF v5.5.5 的 examples/peripherals/lcd/rgb_panel 或同板厂商例程做最小色带测试。普通 RGB 板应先尝试官方 esp_lcd_new_rgb_panel()。不要一开始就复制 JC8048W550 的 rgb44。
RGB 屏适合的 LVGL 模式¶
RGB 屏通常由完整帧缓冲驱动,常见组合是:
| 方案 | 内存 | 特点 |
|---|---|---|
| 单全帧缓冲 | 1 帧 | 省内存,但边扫描边写可能撕裂 |
| 双全帧缓冲 | 2 帧 | 离屏渲染后在 VSYNC 换页,画面更稳 |
| bounce buffer | 全帧 + 小块内部 RAM | 可缓解某些 PSRAM/DMA 限制,但参数敏感 |
本项目的 JC8048W550 使用 rgb44 + LVGL DIRECT 双缓冲,是针对这块 ESP32-S3/800×480 板经过实测得到的特殊路径。它还要求 VSYNC 换页、等待换页完成,以及在自管 PSRAM 帧缓冲时正确处理 cache 写回。完整原因见 JC8048W550 RGB 屏专项指南。
只有在下面三件事都成立时,才考虑借用这条特殊路径:
- 厂商最小例程稳定;
- 官方 RGB panel 最小例程在相同硬件参数下出现可重复的欠载、错位或撕裂;
- 你已经测量并确认问题在传输模型,而不是 PCLK、时序、引脚或 UI 负载。
常见 RGB 特有问题¶
- 面板循环显示自检色:PCLK 或同步时序不被面板接受。
- 整屏斜着滚、周期错位:一行/一帧总时序错误。
- 颜色通道错乱:R/G/B 数据线顺序或位宽错误。
- 静态正常,滑动时抽动:PSRAM 带宽、DMA 欠载或 cache 一致性问题。
- 小块更新不出现,大面积更新偶尔出现:检查自管帧缓冲的 cache 写回和换页同步。
调 RGB 屏时每次只改一个变量,并记录厂商例程和当前固件的对应参数。
3.7 路线 D:QSPI、MIPI 或不认识的接口¶
QSPI 不是“把 SPI 的 MOSI 改成四根线”;MIPI DSI 也不能套用 RGB 时序。先确认 ESP 芯片是否具有对应外设,再从 ESP-IDF 同版本的官方示例或控制器厂商驱动开始。
本仓库目前没有可直接复制的 QSPI/MIPI 板型模板。此时仍可复用 BSP 公共接口、UI、输入和配置层,但显示 transport 是一个新的适配工作。PR 中应附上数据手册、已知可用最小例程和色带测试结果,避免后来的人再次猜协议。
3.8 用统一的五色色带验收¶
先实现 bsp_lcd_push(x, y, w, h, pixels),然后在 panel/帧缓冲初始化完成后、lv_init() 之前临时调用下面的测试。它只使用 20 行缓冲,适合小内存板:
static void display_smoke_test(void)
{
static const uint16_t colors[] = {
0xF800, /* red */
0x07E0, /* green */
0x001F, /* blue */
0xFFFF, /* white */
0x0000, /* black */
};
const int lines = 20;
uint16_t *buf = heap_caps_malloc(LCD_H_RES * lines * 2, MALLOC_CAP_DMA);
ESP_ERROR_CHECK(buf ? ESP_OK : ESP_ERR_NO_MEM);
for (int band = 0; band < 5; band++) {
int y1 = band * LCD_V_RES / 5;
int y2 = (band + 1) * LCD_V_RES / 5;
for (int y = y1; y < y2; y += lines) {
int h = y + lines <= y2 ? lines : y2 - y;
for (int i = 0; i < LCD_H_RES * h; i++) buf[i] = colors[band];
bsp_lcd_push(0, y, LCD_H_RES, h, buf);
}
}
while (1) vTaskDelay(pdMS_TO_TICKS(1000));
}
bsp_lcd_push() 必须吸收具体传输差异:SPI/I80 路线写 panel GRAM 并等待 DMA;RGB 路线把像素写入当前测试帧缓冲并确保扫描端能看到它。
验收时检查:
- 五条颜色顺序和颜色本身正确;
- 色带覆盖完整可见区域,没有固定偏移;
- 画面方向符合产品安装方向;
- 静止 30 秒不闪、不滚动、不出现随机线;
- 重启十次都能点亮。
通过后保存一张照片和串口日志,删除无限循环,再进入下一步。
3.9 把已验证参数写在 BSP 顶部¶
至少留下这些注释:资料来源、原生分辨率、接口、稳定频率/时序、颜色顺序、方向和任何非默认初始化命令。以后出现显示回归时,这份记录就是基线。
本步成功标准:不启动 LVGL,也能稳定显示五色色带。未通过时不要继续。
第 4 步:把显示接入 BSP 和 LVGL¶
4.1 先分清哪些保留、哪些替换¶
继续使用第 2 步创建的板型文件。不要整份复制已有 BSP;它可能包含别的板子的触摸校准、显示时序、内存策略和硬件补丁。
模板分成两类内容:
- 公共生命周期:锁、存储、重启、LVGL 任务、息屏契约。通常保留。
- 显示 transport:总线、panel、
bsp_lcd_push、flush_cb、缓冲和渲染模式。必须使用第 3 步已经验证的方案。
如果是 SPI/ST7789,可以直接从模板逐项修改。如果是其他 SPI 控制器,替换 panel driver 和初始化差异。如果是 I80 或 RGB,应删除模板的 SPI transport,再放入对应实现;不要同时保留两套。
4.2 上层只要求这些显示边界¶
| BSP 接口 | 上层用途 | 新板需要保证什么 |
|---|---|---|
bsp_lcd_push() |
LVGL 启动前的开机动画 | 调用返回时,传入缓冲已经可以安全复用 |
bsp_get_display() |
UI 查询默认显示器 | 返回 lv_display_create() 创建的默认 display |
bsp_screen_power_init() |
注册背光实现 | 传入板级 backlight_apply(0..100) 和毫秒时钟 |
bsp_fade_out() |
重启前渐暗 | 至少安全关闭背光;能清帧则更好 |
bsp_disp_can_*() |
是否显示反色/旋转设置 | 硬件或当前 transport 不支持就返回 false |
bsp_lcd_push() 的“返回即安全”很重要。开机动画会复用同一块像素内存;异步 DMA 没结束就返回会产生条带和随机色块。
4.3 选择 LVGL 缓冲模式¶
| 显示 transport | 推荐起点 | 缓冲放置 | flush 的责任 |
|---|---|---|---|
| SPI 命令屏 | PARTIAL,双 20~40 行 | 内部 DMA RAM | 写脏矩形,等传输完成,再 flush_ready |
| I80 命令屏 | PARTIAL,双局部缓冲 | 对应 DMA 可访问内存 | 与 SPI 相同,只是总线不同 |
| RGB 并口 | 官方驱动建议的全帧方案 | 常在 PSRAM | 维护持续扫描、cache 和换页同步 |
| 自管双帧 RGB | DIRECT,双全帧 | 按芯片能力选择 | 只在最后一个 flush 请求换页,完成后再 flush_ready |
不要因为 FULL、DIRECT 看起来“更快”就随意切换。渲染模式必须和物理屏幕的传输模型及缓冲所有权一致。
4.4 分三次把完整 UI 接回来¶
- LVGL 基础测试:只创建 display,画一个纯色背景和一个矩形。
- 项目开机动画:确认
bsp_lcd_push()连续更新稳定。 - 完整
ui_app_create():检查页面切换、列表滚动和大面积重绘。
每次只增加一层。若第 1 层正常、第 2 层坏,重点检查 bsp_lcd_push 的缓冲生命周期;若前两层正常、完整 UI 滑动才坏,重点检查刷新吞吐、DMA/PSRAM 带宽和换页。
4.5 公共 BSP 函数¶
下面这些通常与显示控制器无关,可以保留模板实现:
bsp_lvgl_lock()/bsp_lvgl_unlock():保护 LVGL;bsp_delay_ms()/bsp_restart():延时和重启;- NVS 与 LittleFS 初始化:保存网络、Moonraker 和界面配置;
lvgl_task():周期调用lv_timer_handler()和bsp_screen_power_poll();bsp_screen_activity()、亮度记忆、自动息屏和唤醒:由公共bsp_screen_power实现,新板不要复制一套状态变量。
板型只实现 static void backlight_apply(int percent):把公共状态机给出的 0~100 转成真实 PWM、GPIO 或背光 IC 命令。有效电平和非线性曲线留在这个函数里;不要在板型 BSP 再保存用户亮度或 screen_off。
static void backlight_apply(int percent)
{
/* TODO(board):只在这里把 0..100 换算成真实硬件信号。 */
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0,
percent * 255 / 100);
ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
}
static uint64_t screen_now_ms(void)
{
return (uint64_t)(esp_timer_get_time() / 1000);
}
/* 背光硬件初始化完成后调用一次。 */
bsp_screen_power_init(backlight_apply, screen_now_ms);
本步成功标准:开机动画和完整 UI 都能稳定显示,连续滚动不会出现花屏、撕裂或错位。
第 5 步:最后加入输入¶
显示没通过第 4 步之前,不要调触摸坐标。
5.1 先只回答一个问题:这个产品有没有触摸?¶
输入路线只有下面两条。旋钮是两条路线都能追加的可选输入,不会改变触摸驱动的写法。
- 没有触摸:不要创建 LVGL pointer,也不要放入任何触摸初始化或校准代码。纯旋钮产品只需启用第 5.6 节的旋钮 Kconfig。
- 有触摸:只创建一个 LVGL pointer,然后根据实际控制器在“电阻触摸实现”和“电容触摸实现”中二选一。需要旋钮时再额外启用第 5.6 节;触摸和旋钮可以同时存在。
没有触摸 ──> 不创建 pointer ──> 可选:启用旋钮
有触摸 ───> 选择且只选择一种触摸实现 ──> 创建 pointer
└──> 可选:同时启用旋钮
├─ 电阻控制器:原始坐标 ──> 校准/映射 ──┐
└─ 电容控制器:屏幕坐标 ──────────────┴─> LVGL
不要同时实现下面两套示例。它们是同一个触摸适配器接口的两种替代实现。
5.2 两种触摸最终都交给同一个 LVGL pointer¶
无论控制器是电阻还是电容,板型 BSP 最终只需要创建一个触摸适配器:
#if BOARD_HAS_TOUCH
ESP_ERROR_CHECK(board_touch_input_create(display, &touch_config));
#endif
board_touch_input_create() 负责初始化真实触摸芯片、创建 LV_INDEV_TYPE_POINTER,并注册一个读取回调。控制器差异应藏在这个适配器内部。给 LVGL 的最后一段逻辑基本相同:
这里的 board_touch_input_create()、touch_read_screen_point() 等名字用于说明接口形状,不是要求逐字复制的公共 API。可直接对照仓库中的电阻屏实现
bsp_cyd_2432s028r.c、
bsp_e32r35t.c,以及电容屏实现
bsp_jc8048w550.c 和
touch_input_board_template.c。
static void touch_read_cb(lv_indev_t *indev, lv_indev_data_t *data)
{
static bool swallow_until_release;
uint16_t x, y;
if (!touch_read_screen_point(&x, &y)) {
swallow_until_release = false;
data->state = LV_INDEV_STATE_RELEASED;
return;
}
if (bsp_screen_activity())
swallow_until_release = true; /* 本次按下只用于唤醒 */
if (swallow_until_release) {
data->state = LV_INDEV_STATE_RELEASED;
return;
}
data->point.x = x;
data->point.y = y;
data->state = LV_INDEV_STATE_PRESSED;
}
这里的 touch_read_screen_point() 是需要二选一的部分:电阻屏实现先读原始 ADC 坐标再做校准映射;电容屏实现通常直接读取屏幕坐标。
5.3 怎样判断是电阻还是电容实现?¶
看触摸控制器型号和驱动输出,不要根据 LCD 型号猜。
| 典型控制器 | 驱动通常返回 | 项目侧校准 |
|---|---|---|
| XPT2046、ADS7846 | 原始 ADC 坐标 | 通常需要 |
| GT911、CST816S、FT5x06 | 已换算的屏幕坐标 | 通常不需要 |
最终判断标准是驱动输出:如果同一个物理点在不同机器上得到明显不同的原始数值,就需要保存校准参数;如果驱动已经稳定返回 0..宽-1、0..高-1 的坐标,就不应再套两点校准。
5.4 电阻触摸实现:读取原始值,再映射成屏幕坐标¶
下面是 XPT2046 一类控制器的核心形状。xpt2046_read_raw() 只读取硬件,touch_cal 属于这个电阻触摸适配器,不属于公共 BSP:
typedef struct {
float x_mul, x_add;
float y_mul, y_add;
} touch_cal_t;
static touch_cal_t touch_cal;
static bool touch_read_screen_point(uint16_t *x, uint16_t *y)
{
uint16_t raw_x, raw_y;
if (!xpt2046_read_raw(&raw_x, &raw_y))
return false;
int32_t sx = lroundf(raw_x * touch_cal.x_mul + touch_cal.x_add);
int32_t sy = lroundf(raw_y * touch_cal.y_mul + touch_cal.y_add);
*x = LV_CLAMP(0, sx, LCD_H_RES - 1);
*y = LV_CLAMP(0, sy, LCD_V_RES - 1);
return true;
}
触摸可能与显示共用 SPI,也可能使用独立 SPI,必须按原理图确认。共用总线时复用已经创建的 SPI host,不能再次初始化同一个 host。
5.4.1 电阻屏已经有可靠的出厂校准值¶
这仍然需要“校准映射”,只是用户不需要看见校准页面。没有已保存数据时装入该板实测过的默认值:
xpt2046_init();
if (!touch_cal_load(&touch_cal)) {
touch_cal = BOARD_FACTORY_TOUCH_CAL; /* 必须来自该板真机测量 */
touch_cal_save(&touch_cal);
}
board_touch_register_pointer(display);
不要从另一种板型复制默认值。同一个 XPT2046,因为面板尺寸、安装方向和 ADC 范围不同,参数也可能完全不同。
5.4.2 电阻屏没有可靠的出厂值,需要用户校准¶
先让触摸硬件能够返回原始坐标;找不到已保存参数时才显示校准点,计算映射并保存:
xpt2046_init();
if (!touch_cal_load(&touch_cal)) {
touch_cal_run(&touch_cal); /* 显示校准点并采集 raw_x/raw_y */
touch_cal_save(&touch_cal);
}
board_touch_register_pointer(display);
touch_cal_load/run/save() 是电阻触摸适配器自己的函数,不是每块板都必须实现的 BSP 接口。校准流程使用显示和原始触摸采样,因此应留在电阻屏代码旁边。
5.4.3 完全不需要校准时,代码是什么样?¶
不需要校准就完全不要出现 touch_cal_* 或 touch.json:
touch_controller_init();
board_touch_register_pointer(display);
这种路线通常用于电容屏,或者用于已经在驱动内部完成坐标换算的特殊控制器。不要为了“接口完整”创建空校准函数。
5.5 电容触摸实现:直接读取屏幕坐标¶
GT911、CST816S、FT5x06 一类控制器通常直接报告屏幕坐标。适配器只需初始化控制器,按安装方向设置 swap/mirror,并把坐标交给第 5.2 节的公共 pointer 回调;不读取 touch.json,也不进入校准页面。
仓库提供了一个可以直接阅读和复制的 CST816S 实现:
templates/board/touch_input_board_template.h:板型 BSP 看到的小接口;templates/board/touch_input_board_template.c:I2C、CST816S、中断后读取、坐标上报和息屏唤醒。
模板 BSP 的调用只有这一条路线:
board_template_touch_input_config_t touch_config = {
.h_res = LCD_H_RES,
.v_res = LCD_V_RES,
.reset_gpio = PIN_TOUCH_RST,
.interrupt_gpio = PIN_TOUCH_INT,
.swap_xy = false,
.mirror_x = false,
.mirror_y = false,
};
ESP_ERROR_CHECK(board_template_touch_input_create(
display, touch_i2c_bus, &touch_config));
接入新板时:
- 复制
touch_input_board_template.c/.h,把文件名及board_template改成板名。 - 在
src/bsp/CMakeLists.txt的SRCS加入.c文件。 - 在
src/ports/esp32/entry/idf_component.yml加入匹配的触摸驱动。 - 先把 swap/mirror 全设为 false,用四角测试页只调整实际需要的方向。
CST816S 在触摸事件后才短暂响应 I2C,所以示例使用 INT 和信号量,只在中断后读取。商品资料写 CST816T 时应先核对丝印和数据手册;名字接近不代表寄存器和中断行为兼容。换成另一个 esp_lcd_touch 控制器时,通常替换头文件、IO 配置宏、创建函数和必要的读取策略,第 5.2 节的 LVGL pointer 形状可以保留。
如果确认是 CST816S,但初始化停在读取芯片 ID,可按驱动说明尝试:
CONFIG_ESP_LCD_TOUCH_CST816S_DISABLE_READ_ID=y
5.6 可选的旋转编码器¶
编码器与“有没有触摸”是两个独立选择。共享 bsp_input_init() 根据 Kconfig 创建 encoder,不要把 GPIO 中断、PCNT 或焦点遍历写进板型 BSP。
CONFIG_INPUT_ROTARY_ENCODER=y
CONFIG_INPUT_ROTARY_GPIO_A=4
CONFIG_INPUT_ROTARY_GPIO_B=5
CONFIG_INPUT_ROTARY_GPIO_BUTTON=6
CONFIG_INPUT_ROTARY_COUNTS_PER_DETENT=4
CONFIG_INPUT_ROTARY_PHASE_PULLUPS=y
CONFIG_INPUT_ROTARY_BUTTON_ACTIVE_LOW=y
裸编码器公共脚通常接 GND,A/B 和按键接对应 GPIO。模块已有外部上拉时关闭内部相位上拉;方向反了启用 CONFIG_INPUT_ROTARY_REVERSE;一格跳过多个控件时把 counts 从 4 降到 2 或 1。
纯旋钮板会使用专用交互:Moonraker 主机地址按四个 0~255 段输入,温度直接在卡片内旋转调节。域名或 API Key 等任意文本需要由出厂配置、维护接口或触摸键盘提供;常见同局域网部署也可使用 Moonraker trusted_clients。
5.7 息屏唤醒属于公共策略¶
触摸和旋钮都调用 bsp_screen_activity()。它返回“这次操作是否刚刚唤醒屏幕”,各自的适配器再按输入形态吞掉本次点击、旋转或按压。独立息屏键调用 bsp_screen_toggle(),不作为 LVGL 输入。
本步成功标准:无触摸板完全没有 pointer 和校准流程;有触摸板只编译一种匹配控制器的实现;电容屏永不进入校准;电阻屏能装入该板的出厂参数或完成一次校准;可选旋钮能与两条路线正常组合。
第 6 步:编译、烧录和分层验收¶
bash tools/build-esp32.sh myboard
bash tools/build-esp32.sh myboard flash COM6
按下面顺序验收,不要只看“主界面出现了”:
- 构建:产物没有超过应用分区。
- 重复上电:十次都能点亮,不偶发白屏。
- 静态画面:一分钟没有随机线、闪烁或颜色跳变。
- 高负载显示:快速切页和滚动列表仍稳定。
- 输入:触摸、旋钮或二者并存都符合声明。
- 网络:配置 Moonraker,能接收状态并发出一次低风险控制。
- 息屏:超时熄灭,第一次输入只唤醒。
再构建至少一个已有板型,确认共享 UI 和 BSP 接口没有被新板修改破坏。
第 7 步:按现象排错¶
7.1 显示问题快速表¶
| 现象 | 先查什么 |
|---|---|
| 背光不亮 | 背光供电、GPIO、有效电平、PWM |
| 背光亮但整屏白/黑 | 复位、CS/DC、panel driver、初始化序列 |
| 完全无规律噪点 | 接线、供电、时钟过高、字节顺序 |
| 画面固定偏移 | 原生分辨率和 set_gap |
| 红蓝互换 | RGB/BGR 或 RGB 数据线顺序 |
| 颜色像负片 | invert 设置 |
| 镜像或转了 90° | swap_xy 与 mirror;先画带方向标记的测试图 |
| 第一帧对,动画花 | DMA 未完成就复用缓冲 |
| RGB 屏整幅滚动 | PCLK、HSYNC/VSYNC、porch |
| RGB 静态稳、滑动抽 | DMA 欠载、PSRAM 带宽、cache/换页同步 |
| 编译找不到 panel 构造函数 | driver 依赖和头文件没有登记 |
7.2 输入问题快速表¶
| 现象 | 先查什么 |
|---|---|
| 电阻触摸位置乱 | 原始轴对应关系和两点校准 |
| 电容屏进入校准 | 错误复制了电阻屏流程 |
| 触摸完全没反应 | 控制器型号、总线、CS/IRQ、共用总线方式 |
| 旋钮方向反 | CONFIG_INPUT_ROTARY_REVERSE |
| 一格跳多项 | COUNTS_PER_DETENT |
| 唤醒同时误点 | 是否使用 bsp_screen_activity() 的返回值 |
仍未解决时,回到最近一个成功阶段做 A/B。提交问题时附上板型资料卡、五色色带照片、串口日志、已知可用厂商例程和每次只改一个变量的记录。
登记新板型(PR 清单)¶
给仓库贡献一块新板子的支持,需要“一个 BSP 实现 + 全链路登记”。必须从 templates/board/ 开始;现有 BSP 只用于查阅某个控制器的特殊实现,不作为复制底板。
命名约定¶
板型标识用小写下划线的型号名(如 cyd_2432s028r、e32r35t),Kconfig 宏为 CONFIG_BOARD_<大写>。构建产物、CI 矩阵、sdkconfig 文件名全部使用同一个标识。
需要修改/新增的文件清单¶
| # | 文件 | 改动 | 必需? |
|---|---|---|---|
| 1 | src/bsp/esp32/bsp_<board>.c |
复制板型模板,只填写标出的硬件区块;整文件包在 #if CONFIG_BOARD_<BOARD> 里 |
✅ |
| 2 | src/bsp/Kconfig.projbuild |
choice BOARD 里加 config BOARD_<BOARD> |
✅ |
| 3 | src/bsp/CMakeLists.txt |
SRCS 加新文件;新驱动芯片时 REQUIRES 加组件名 | ✅ |
| 4 | src/ports/esp32/entry/idf_component.yml |
新驱动芯片时加 managed component 依赖及版本约束 | 视情况 |
| 5 | src/ports/esp32/sdkconfig.defaults.<board> |
新建:CONFIG_BOARD_<BOARD>=y + flash 模式/大小/主频(照抄同芯片板型改宏即可) |
✅ |
| 6 | tools/build-esp32.sh |
board_conf() 加 case(TARGET/BDIR/SDKCFG/DEFS),all 分支加构建,头部注释更新 |
✅ |
| 7 | .github/workflows/build.yml |
firmware 矩阵加一项(board/target/bdir/sdkcfg/defs/chip/flash_size/bl_offset);package 矩阵加一项 |
✅ |
| 8 | src/ui/ui_layout.c |
字号档加 #elif defined(CONFIG_BOARD_<BOARD>) |
✅ |
| 9 | README.md / README_zh.md |
支持板型列表加一行(含状态标注) | ✅ |
| 10 | docs/boards/<芯片族>.md 及对应 .zh.md |
硬件信息与引脚表(“支持的板子”对应芯片族子页) | ✅ |
| 11 | src/ports/esp32/partitions_<board>.csv |
仅当 flash 布局与默认 partitions.csv 不同时 |
视情况 |
| 12 | src/ports/esp32/sdkconfig.<board> |
首次构建生成的完整 sdkconfig,建议提交(与 jc8048w550 先例一致),避免 CI 重新生成时漂移 | 建议 |
规则与注意事项¶
- 不要按板型裁剪编译:
src/bsp/CMakeLists.txt里所有板的源文件都参与编译,靠文件内#if CONFIG_BOARD_*裁剪。原因是组件注册早于 kconfig 加载,if(CONFIG_...)在 CMake 层面拿不到。 - 新板型一律标 WIP:CI
package矩阵里suffix: "-WIP",README 和 boards 页同步标注。真机验证稳定后再去标。 - WIP 说明文案:workflow 里的 WIP.txt 步骤按
matrix.board自动生成,无需单独改。 - Bootloader 偏移:ESP32/ESP32-S3 用
bl_offset: 0x1000;C3 等新启动链的芯片是0x0。 - README 双语同步:
README.md与README_zh.md都要改。
提交前的验证¶
- 本地全量构建新板型通过:
bash tools/build-esp32.sh <board> - 回归构建至少一块既有板型(共享文件有改动时必须):
bash tools/build-esp32.sh cyd_2432s028r - 固件大小检查:构建末尾的
binary size不超过应用分区(当前 0x320000) - 按本指南先做不带 LVGL 的五色色带测试,再验证开机动画、完整 UI 和声明的全部输入
- 没有真机就在 PR 里明确注明“未经真机验证”,板型继续保持 WIP
PR 里请附上¶
- 板子资料链接(厂商 wiki / 原理图 / 引脚表)
- 屏幕接口分类(SPI/I80/RGB/QSPI/MIPI)、控制器型号、稳定频率或时序参数
- 五色色带和完整 UI 的真机照片或串口日志;没有则明确说明
- 与模板 BSP 的有意差异点(例如共总线、无 RST、强制校准),方便 review
- 输入形态:触摸、旋钮、两者都有或都没有。旋钮 GPIO 写进 Kconfig/sdkconfig,不写私有 UI 实现
提交信息风格参考历史:feat: 新增 <板型> 板型支持(<屏幕> + <触摸/旋钮>,WIP)。