# drv_lcd_st7789v **Repository Path**: NetADs/drv_lcd_st7789v ## Basic Information - **Project Name**: drv_lcd_st7789v - **Description**: drivers Lcd SPI240x320 st7789v驱动。GPIO 引脚.sck = 21,.mosi = 47, .cs = 41, .dc = 40, .rst = 45, .bl = 42。我的ESP32S3-N16R8开发板与LCD两寸屏专用驱动组件。 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-24 - **Last Updated**: 2026-07-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # drv_lcd_st7789v ESP-IDF 独立组件 — ST7789V SPI LCD 驱动层 为其他 ESP-IDF 项目提供可复用的 240×320 ST7789V LCD 驷动,支持通过 Git 或 IDF Component Manager 自动拉取依赖。 --- ## 目录结构 ``` drv_lcd_st7789v/ ├── CMakeLists.txt # 组件构建入口 ├── idf_component.yml # IDF Component Manager 清单 ├── include/ │ └── drv_lcd_st7789v.h # 公共 API 头文件 ├── src/ │ └── drv_lcd_st7789v.c # 驷动实现 ├── examples/ │ └── basic_display/ # 完整示例工程 │ ├── CMakeLists.txt │ ├── sdkconfig.defaults │ └── main/ │ ├── CMakeLists.txt │ └── main.c └── README.md ``` --- ## 硬件规格 | 参数 | 值 | | -------------- | ------------------------- | | 显示控制器 | ST7789V | | 物理分辨率 | 240 × 320(竖屏) | | 色彩深度 | RGB565(16-bit/像素) | | 接口 | 4-wire SPI | | SPI 时钟 | 最高 40 MHz | | 背光控制 | PWM(LEDC,8-bit,5 kHz) | | 睡眠电流 | < 1 mA(SLPIN 模式) | --- ## 引脚定义(默认) | 信号 | GPIO | | -------- | ---- | | SCK | 21 | | MOSI | 47 | | CS | 41 | | DC | 40 | | RST | 45 | | BL (背光)| 42 | > 所有引脚均可在运行时通过 `lcd_config_t` 结构体自定义,无需修改驱动源码。 --- ## 快速开始 ### 方式一:Git 子模块(推荐用于私有项目) 在你的项目根目录执行: ```bash git submodule add https://gitee.com/NetADs/drv_lcd_st7789v.git components/drv_lcd_st7789v git submodule update --init --recursive ``` 然后在项目 `CMakeLists.txt` 中,确保 `components/` 目录被 IDF 扫描(默认已包含)。 你的项目 `CMakeLists.txt` 最终大致如下: ```cmake cmake_minimum_required(VERSION 3.16) # IDF 会自动扫描 ${PROJECT_DIR}/components/ 目录 include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(my_project) ``` ### 方式二:IDF Component Manager(idf_component.yml) 在你的项目 `main/idf_component.yml` 中添加依赖: ```yaml dependencies: idf: ">=5.0.0" drv_lcd_st7789v: git: "https://gitee.com/NetADs/drv_lcd_st7789v.git" # 或使用 tag: # version: "1.1.0" ``` 运行 `idf.py reconfigure` 或 `idf.py build` 时,IDF Component Manager 会自动下载组件。 ### 方式三:EXTRA_COMPONENT_DIRS(本地引用) 适合在同一工作区中多个项目共用一份驱动: ```cmake # 在你的项目 CMakeLists.txt 中 set(EXTRA_COMPONENT_DIRS "/path/to/drv_lcd_st7789v") include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(my_project) ``` --- ## API 参考 ### 初始化 ```c #include "drv_lcd_st7789v.h" // 方式 A:使用默认引脚宏(推荐) lcd_config_t cfg = LCD_CONFIG_DEFAULT(); lcd_handle_t lcd = NULL; ESP_ERROR_CHECK(lcd_init(&cfg, &lcd)); // 方式 A-2:切换颜色顺序(红蓝互换时修改此项) lcd_config_t cfg = LCD_CONFIG_DEFAULT(); cfg.color_order = LCD_COLOR_ORDER_RGB; // 默认 BGR,部分面板需设为 RGB ESP_ERROR_CHECK(lcd_init(&cfg, &lcd)); // 方式 B:自定义引脚 lcd_config_t cfg = { .pin_sck = 21, .pin_mosi = 47, .pin_cs = 41, .pin_dc = 40, .pin_rst = 45, .pin_bl = 42, .spi_host = SPI2_HOST, .spi_clock_hz = 40 * 1000 * 1000, .orientation = LCD_ORIENTATION_PORTRAIT, .color_order = LCD_COLOR_ORDER_BGR, .skip_bus_init = false, // 默认:驱动初始化 SPI bus }; ESP_ERROR_CHECK(lcd_init(&cfg, &lcd)); ``` ### SPI Bus 共享(skip_bus_init) 当同一 SPI bus 上挂载多个设备(LCD + 触摸 IC、SPI Flash 等)时,设置 `skip_bus_init = true`: ```c // 1. 外部初始化 SPI bus(必须设置 max_transfer_sz >= 3840) spi_bus_config_t buscfg = { .mosi_io_num = 47, .miso_io_num = -1, // 或触摸 IC 的 MISO 引脚 .sclk_io_num = 21, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = 4096, // >= LCD_DMA_BUFFER_SIZE (3840) }; ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); // 2. 初始化 LCD(跳过 bus init) lcd_config_t cfg = LCD_CONFIG_DEFAULT(); cfg.skip_bus_init = true; ESP_ERROR_CHECK(lcd_init(&cfg, &lcd)); // 3. 在同一 bus 上添加触摸 IC // ... spi_bus_add_device(SPI2_HOST, &touch_devcfg, &touch_spi); ``` > **注意**:`skip_bus_init = true` 时,调用者必须确保 `max_transfer_sz` >= 3840 字节,否则 SPI 传输会失败。`lcd_deinit()` 不会释放 bus,由调用者管理 bus 生命周期。 ### 方向控制与分辨率查询 ```c // 设置方向 lcd_set_orientation(lcd, LCD_ORIENTATION_LANDSCAPE); // 320×240 lcd_set_orientation(lcd, LCD_ORIENTATION_PORTRAIT); // 240×320(默认) lcd_set_orientation(lcd, LCD_ORIENTATION_PORTRAIT_INV); // 240×320 倒置 lcd_set_orientation(lcd, LCD_ORIENTATION_LANDSCAPE_INV); // 320×240 倒置 // 查询当前分辨率(方向切换后自动更新) uint16_t w, h; lcd_get_resolution(lcd, &w, &h); // PORTRAIT → w=240, h=320 // LANDSCAPE → w=320, h=240 ``` ### Display 控制 ```c // 息屏但保留帧缓冲(OTA / 传感器采集期间) lcd_display_off(lcd); // 屏幕变黑,但内部帧缓冲内容不变 lcd_display_on(lcd); // 恢复显示,立即呈现缓冲区内容 // 低功耗睡眠模式(电池供电设备核心功能) lcd_set_backlight(lcd, 0); // 先关背光 lcd_sleep(lcd); // SLPIN → 电流 < 1 mA // ... 休眠期间 ... lcd_wakeup(lcd); // SLPOUT + 120ms 延迟 lcd_set_backlight(lcd, 255); // 恢复背光 ``` > `lcd_sleep()` 后必须调用 `lcd_wakeup()` 才能恢复绘图。内部包含 datasheet 要求的 120ms 延迟。 ### 背光控制 ```c lcd_set_backlight(lcd, 255); // 最亮 lcd_set_backlight(lcd, 128); // 50% lcd_set_backlight(lcd, 0); // 关闭 ``` ### 色彩控制 ```c // 颜色顺序配置(初始化时设置,运行时不可改) // 若红色显示为蓝色、蓝色显示为红色,将 color_order 改为 LCD_COLOR_ORDER_RGB lcd_config_t cfg = LCD_CONFIG_DEFAULT(); cfg.color_order = LCD_COLOR_ORDER_RGB; // 或 LCD_COLOR_ORDER_BGR(默认) // 反转开关(部分面板需要关闭反转才显示正确色彩) lcd_set_inversion(lcd, true); // INVON(默认,init sequence 已启用) lcd_set_inversion(lcd, false); // INVOFF // Gamma 曲线选择(4 条可用曲线) lcd_set_gamma_curve(lcd, LCD_GAMMA_CURVE_1); // 默认曲线 lcd_set_gamma_curve(lcd, LCD_GAMMA_CURVE_2); // 曲线 2 ``` ### 绘图 API ```c // 清屏 lcd_fill_screen(lcd, LCD_COLOR_BLACK); // 填充矩形 lcd_fill_rect(lcd, x, y, width, height, LCD_COLOR_RED); // 绘制矩形轮廓 lcd_draw_rect(lcd, x, y, width, height, LCD_COLOR_WHITE); // 绘制水平线 / 垂直线 lcd_draw_hline(lcd, x, y, length, LCD_COLOR_GREEN); lcd_draw_vline(lcd, x, y, length, LCD_COLOR_BLUE); // 写入单像素 lcd_draw_pixel(lcd, x, y, LCD_COLOR_YELLOW); // 写入 RGB565 位图(row-major,native endian) // 驱动内部自动做 bswap16 → 传入小端数据即可 lcd_draw_bitmap(lcd, x, y, w, h, pixel_data_ptr); ``` ### 颜色宏 ```c LCD_COLOR_BLACK // 0x0000 LCD_COLOR_WHITE // 0xFFFF LCD_COLOR_RED // 0xF800 LCD_COLOR_GREEN // 0x07E0 LCD_COLOR_BLUE // 0x001F LCD_COLOR_YELLOW // 0xFFE0 LCD_COLOR_CYAN // 0x07FF LCD_COLOR_MAGENTA // 0xF81F // 自定义 RGB(8-bit 分量 → RGB565) uint16_t color = LCD_RGB565(255, 128, 0); // 橙色 ``` ### 资源释放 ```c lcd_deinit(lcd); // DISPOFF + SLPIN + 关背光 + 释放 SPI + 释放内存 // 如果 skip_bus_init=false,还会释放 SPI bus // 如果 skip_bus_init=true,只移除 SPI device,bus 由调用者管理 ``` --- ## 在其他项目中配置使用 ### 1. 添加组件依赖 在你的 UI 项目 `main/idf_component.yml` 中同时引用驱动和 LVGL: ```yaml dependencies: idf: ">=5.0.0" drv_lcd_st7789v: git: "https://gitee.com/NetADs/drv_lcd_st7789v.git" lvgl: version: "8.3.*" ``` > 也可以用 **Git 子模块** 方式(见上方"快速开始"章节),两种方式不要同时使用以免冲突。 ### 2. 项目 sdkconfig.defaults 配置 在你的项目根目录 `sdkconfig.defaults` 中添加以下关键配置: ```ini # SPI 驱动需要 IRAM 访问(pre_cb 回调在 IRAM 中执行) CONFIG_SPI_MASTER_IN_IRAM=y # LVGL 颜色深度:必须设为 16(RGB565),与 ST7789V 一致 CONFIG_LV_COLOR_DEPTH_16=y # LVGL 字节交换:必须关闭!驱动内部已做 bswap16, # 开启此选项会导致双重交换 → 颜色错乱(红变青,蓝变黄) CONFIG_LV_COLOR_16_SWAP=n # 主任务栈大小(LVGL + SPI polling 需要足够栈空间) CONFIG_ESP_MAIN_TASK_STACK_SIZE=4096 # 80 MHz flash 以提高 SPI 吞吐 CONFIG_ESPTOOLPY_FLASHFREQ_80M=y ``` > **`CONFIG_LV_COLOR_16_SWAP=n` 是最关键的配置项。** 如果误设为 `y`,LVGL 会预交换字节,驱动又做一次交换,双重交换后颜色回到原始序——ST7789V 收到的就是错误的小端序,屏幕颜色全面错乱。 ### 3. LVGL 横屏集成代码 以下是完整的初始化 + flush 回调粘合代码(约 40 行),**不需要修改驱动源码**: ```c #include "drv_lcd_st7789v.h" #include "lvgl.h" static lcd_handle_t s_lcd = NULL; /* ---- LVGL flush 回调 ---- */ static void lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_p) { uint16_t w = (uint16_t)(area->x2 - area->x1 + 1); uint16_t h = (uint16_t)(area->y2 - area->y1 + 1); /* lcd_draw_bitmap() 内部自动做 bswap16, 直接传入 LVGL 缓冲区即可(native endian) */ lcd_draw_bitmap(s_lcd, (uint16_t)area->x1, (uint16_t)area->y1, w, h, (const uint16_t *)color_p); /* 通知 LVGL 刷新完成 */ lv_disp_flush_ready(drv); } /* ---- 初始化 LCD + LVGL ---- */ static lv_color_t s_buf1[320 * 10]; /* 分块缓冲区:10 行 */ void lvgl_lcd_init(void) { /* 初始化 LCD 驱动,横屏模式 */ lcd_config_t cfg = LCD_CONFIG_DEFAULT(); cfg.orientation = LCD_ORIENTATION_LANDSCAPE; /* 320 × 240 */ ESP_ERROR_CHECK(lcd_init(&cfg, &s_lcd)); /* 用 lcd_get_resolution() 动态获取分辨率 */ uint16_t width = 0, height = 0; lcd_get_resolution(s_lcd, &width, &height); /* 初始化 LVGL */ lv_init(); /* 注册 display driver */ static lv_disp_draw_buf_t draw_buf; lv_disp_draw_buf_init(&draw_buf, s_buf1, NULL, width * 10); static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.hor_res = width; /* 动态分辨率 */ disp_drv.ver_res = height; disp_drv.flush_cb = lvgl_flush_cb; disp_drv.draw_buf = &draw_buf; lv_disp_drv_register(&disp_drv); } ``` 在 `app_main()` 中调用: ```c void app_main(void) { lvgl_lcd_init(); /* 创建你的 LVGL 页面 */ // create_my_ui_page(); /* LVGL 主循环 */ while (1) { lv_timer_handler(); vTaskDelay(pdMS_TO_TICKS(5)); } } ``` ### 4. SPI Bus 共享(LCD + 触摸 IC) 如果你的项目在 SPI2_HOST 上同时挂了 LCD 和触摸 IC(如 XPT2046),需要由调用者统一管理 bus: ```c /* 1. 初始化 SPI bus(LCD + 触摸共用) */ spi_bus_config_t buscfg = { .mosi_io_num = 47, .miso_io_num = 38, // 触摸 IC 的 MISO .sclk_io_num = 21, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = 4096, }; ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); /* 2. 初始化 LCD(skip_bus_init=true) */ lcd_config_t lcd_cfg = LCD_CONFIG_DEFAULT(); lcd_cfg.skip_bus_init = true; ESP_ERROR_CHECK(lcd_init(&lcd_cfg, &s_lcd)); /* 3. 初始化触摸 IC */ // spi_device_interface_config_t touch_cfg = { ... }; // spi_bus_add_device(SPI2_HOST, &touch_cfg, &touch_spi); ``` > `lcd_deinit()` 在 `skip_bus_init=true` 时只移除 SPI device,不释放 bus。调用者需要在所有设备都 deinit 后才调用 `spi_bus_free()`。 ### 5. 横屏坐标映射说明 当 `orientation = LCD_ORIENTATION_LANDSCAPE` 时: | 项目 | 竖屏 (PORTRAIT) | 横屏 (LANDSCAPE) | | --- | --- | --- | | 宽度 | 240 | 320 | | 高度 | 320 | 240 | | ST7789V MADCTL | (BGR 或 RGB) | MV \| MX \| (BGR 或 RGB) | | lcd_get_resolution() | 240, 320 | 320, 240 | | LVGL hor_res | 240 | 320 | | LVGL ver_res | 320 | 240 | > MADCTL 中 BGR 位由 `lcd_cfg.color_order` 控制。默认 `LCD_COLOR_ORDER_BGR`(位=1),若出现红蓝互换则改为 `LCD_COLOR_ORDER_RGB`(位=0)。 坐标翻转由 ST7789V 硬件 MADCTL 寄存器处理,**应用层不需要手动交换 x/y**。`lcd_fill_rect()` 和 `lcd_draw_bitmap()` 内部会自动使用切换后的 `cur_width / cur_height`。 ### 6. LVGL 颜色编写 在横屏 LVGL 页面中正常使用颜色 API,字节序由驱动统一处理: ```c /* 24-bit hex → LVGL 自动转 RGB565 */ lv_obj_set_style_bg_color(obj, lv_color_hex(0xFF0000), 0); /* 红 */ lv_obj_set_style_bg_color(obj, lv_color_hex(0x1E1E1E), 0); /* 深灰 */ lv_obj_set_style_text_color(label, lv_color_hex(0xFFFFFF), 0); /* 白字 */ /* Material Design 色板 */ lv_obj_set_style_bg_color(btn, lv_palette_blue(LV_PALETTE_MAIN), 0); ``` > 颜色数据从 LVGL(小端)→ 驱动(bswap16)→ ST7789V SPI(大端),全程自动,无需手动处理。 --- ## 线程安全与使用限制 | 限制 | 说明 | | --- | --- | | **非线程安全** | 所有绘图 API 必须从同一 RTOS task 调用(如 LVGL 的 timer task)。并发访问会破坏 SPI bus 和 DMA buffer。 | | **单实例** | DC 引脚存储在模块级变量 `s_dc_pin` 中,同一时刻只能有一个 `lcd_handle_t` 活跃。需要第二块屏时,先 `lcd_deinit()` 第一块。 | | **不可重入** | `lcd_deinit()` 后 handle 变为无效指针,不要再调用任何 API。 | --- ## 构建与烧录 ```bash # 进入示例目录 cd examples/basic_display # 设置目标芯片(ESP32-S3 示例) idf.py set-target esp32s3 # 编译 idf.py build # 烧录并监控串口 idf.py -p /dev/ttyUSB0 flash monitor ``` --- ## 依赖 | 依赖 | 版本要求 | | ----------------- | ---------- | | ESP-IDF | >= 5.0.0 | | `driver`(组件) | IDF 内置 | | `esp_timer` | IDF 内置 | | `log` | IDF 内置 | | `freertos` | IDF 内置 | 无第三方库依赖。 --- ## 技术说明 ### 错误处理策略 所有公共 API 返回 `esp_err_t`,不使用 `ESP_ERROR_CHECK()`(不会 abort)。调用者自行决定是否用 `ESP_ERROR_CHECK()` 将错误升级为 fatal。 内部 SPI 传输函数同样返回 `esp_err_t`,错误在 init sequence 中通过 CHK 宏传播,在绘图 API 中直接返回。 ### SPI 事务模型 驱动使用 **polling transmit**(轮询传输)而非 DMA 中断事务,原因: - 命令/寄存器写入字节数极少(1–5 字节),DMA 建立开销远大于实际传输时间。 - 像素块写入使用预分配的 DMA 对齐缓冲区(`MALLOC_CAP_DMA`),每次最多发送 `LCD_DMA_BUFFER_LINES × LCD_H_RES` 像素,避免单次事务超过 SPI 硬件 65535 字节限制。 ### DC 引脚驱动机制 ST7789V 采用 4-wire SPI,DC(Data/Command)引脚区分命令帧(DC=0)与数据帧(DC=1)。驱动通过 `spi_device_interface_config_t.pre_cb` 回调在每个 SPI 事务开始前由硬件时序精确切换 DC 电平,保证建立时间满足数据手册要求(≥15 ns before SCLK rising edge)。 DC 电平编码在 `spi_transaction_t.user` 字段中:`(intptr_t)0` = 命令,`(intptr_t)1` = 数据。DC 引脚号存储在模块级变量 `s_dc_pin` 中供 IRAM 回调访问。 ### 字节序 ESP32 系列 CPU 为小端(little-endian),而 ST7789V 期望 RGB565 数据以**大端**字节顺序写入。驱动在 `lcd_fill_rect` 和 `lcd_draw_bitmap` 中使用 `__builtin_bswap16()` 在填充 DMA 缓冲区时完成字节交换,CPU 开销极小(单指令 `REV16` 在 Xtensa LX7 上)。 **调用者传入 native endian (小端) 数据即可,不需要手动 swap。** ### 内存布局 ``` heap (MALLOC_CAP_DMA): dma_buf: LCD_H_RES × LCD_DMA_BUFFER_LINES × 2 bytes = 240 × 8 × 2 = 3840 bytes heap (internal): lcd_dev_s struct: ~80 bytes (含 bus_initialized / is_sleeping 标志) ``` 总额外运行时堆占用:约 **3.9 KB**。 --- ## 许可证 MIT License — 自由用于商业及开源项目。 --- ## 更新日志 ### v1.1.1 - 新增 `lcd_color_order_t` 枚举和 `color_order` 配置项 — 支持 RGB/BGR 颜色顺序切换 - 修复:部分面板红蓝互换问题 — 默认 BGR,可改为 RGB ### v1.1.0 - 新增 `lcd_get_resolution()` — 动态查询当前横/竖屏分辨率 - 新增 `lcd_display_on()` / `lcd_display_off()` — 息屏控制 - 新增 `lcd_sleep()` / `lcd_wakeup()` — 低功耗睡眠模式(含 120ms 延迟) - 新增 `lcd_set_inversion()` — 运行时色彩反转开关 - 新增 `lcd_set_gamma_curve()` — 4 条 gamma 曲线选择 - 新增 `skip_bus_init` 配置项 — SPI bus 共享(LCD + 触摸 IC) - 修复:删除死代码 `lcd_spi_pre_transfer_callback()`(含空指针 UB) - 修复:所有内部 SPI 传输函数改为返回 `esp_err_t`,不再 abort - 修复:`lcd_fill_rect()` uint16_t 下溢 — 先做 out-of-bounds 判断 - 修复:`lcd_draw_bitmap()` 无边界裁剪 — 新增 clipped 逐行传输路径 - 修复:`lcd_draw_bitmap()` 文档误标"大端输入" — 更正为 native endian - 修复:`lcd_deinit()` 不区分 bus owner — 新增 `bus_initialized` 标志 - 修复:`lcd_deinit()` 后 `s_dc_pin` 残留 — 重置为 -1 - 修复:`lcd_set_backlight()` 不检查 LEDC 返回值 - 修复:`t->user` 语义混乱 — 统一 `(void *)(intptr_t)` 编码 ### v1.0.0 - 初始版本 - 支持 ST7789V 240×320 SPI LCD - RGB565 绘图 API:fill, rect, line, pixel, bitmap - 方向旋转支持(0°/90°/180°/270°) - LEDC PWM 背光控制 - ESP-IDF 5.x 兼容