移植numworks图形计算器:6.移植LCD驱动——为ESP32-S3启用I8080并口模式

张开发
2026/4/20 18:17:47 15 分钟阅读

分享文章

移植numworks图形计算器:6.移植LCD驱动——为ESP32-S3启用I8080并口模式
移植LCD驱动——为ESP32-S3启用I8080并口模式在上一篇文章中我们搭建好了开发环境。现在我们将真正开始与硬件打交道将NumWorks的图形输出到屏幕上。与模拟器不同在实体硬件上我们需要为Kandinsky图形库提供一个底层的“画布”。本文将详细介绍如何在ESP-IDF框架下使用Intel 8080I80并行接口驱动ST7789屏幕使其作为NumWorks的显示后端。1. I8080接口与引脚定义与SPI相比I8080并口最大的优势在于传输速度快因为它使用多条数据线通常是8条或16条并行传输数据。ST7789支持8位或9位/16位的I8080并行接口 。为了达到最佳性能和与原版NumWorks内存布局的兼容性我们选择8位并口 RGB565颜色格式。这意味着每个像素的16位颜色数据需要通过两次8位传输来完成即写两次但这对上层是透明的我们只需要专注于正确配置总线即可。ST7789在I8080模式下的主要引脚功能如下引脚名称功能说明连接至ESP32-S3VCC电源3.3V供电3.3VGND地电源地GNDDB[0:7]数据总线8位双向数据总线用于传输命令和像素数据任意8个GPIO需连续或不连续下文详述WR写时钟写使能时钟上升沿锁存数据GPIO 12RD读时钟读使能时钟我们一般不用可接高电平或悬空NC 或 3.3VDC数据/命令选择高电平数据低电平命令GPIO 14CS片选低电平有效选中该设备GPIO 10RST复位低电平复位屏幕控制器GPIO 9BLK背光控制高电平点亮背光GPIO 46重要提示在I8080模式下WR写时钟引脚是必不可少的它由ESP32-S3的LCD外设控制用于产生精确的写入时序 。DC引脚仍然用于区分命令和数据。2. ESP32-S3 的 I80 LCD 外设ESP32-S3内置了LCD外设也称为I80控制器专门用于驱动I8080并口屏幕 。它支持8位或16位总线宽度并集成了DMA控制器可以高效地从内存可以是内部SRAM或外部PSRAM中搬运帧缓冲区数据到屏幕极大地解放了CPU 。我们的驱动程序将基于esp_lcd组件它提供了统一的API来操作这个外设我们无需直接操作底层寄存器 。3. 硬件连接示例ESP32-S3的GPIO功能非常灵活除了WR和部分特殊功能引脚外数据线DB0-DB7可以任意分配。这里提供一个常用的参考接线ST7789引脚连接至ESP32-S3引脚说明DB0GPIO 0数据位0DB1GPIO 1数据位1DB2GPIO 2数据位2DB3GPIO 3数据位3DB4GPIO 4数据位4DB5GPIO 5数据位5DB6GPIO 6数据位6DB7GPIO 7数据位7WRGPIO 12写时钟(必须)DCGPIO 14数据/命令选择CSGPIO 10片选RSTGPIO 9复位BLKGPIO 46背光控制4. 代码实现在ESP-IDF中初始化I8080 LCD以下代码基于ESP-IDF v5.x展示了如何初始化I80总线并驱动ST7789。虽然ESP-IDF自带的ST7789驱动主要基于SPI但其esp_lcd框架的设计允许我们通过自定义初始化命令来适配I8080模式。我们将在main.c中实现初始化并为后续Kandinsky的移植提供一个简单的画点测试函数。步骤 1: 包含必要的头文件c#include stdio.h #include freertos/FreeRTOS.h #include freertos/task.h #include esp_lcd_panel_io.h #include esp_lcd_panel_ops.h #include esp_lcd_panel_vendor.h // 包含了 ST7789 的驱动声明 #include driver/gpio.h #include esp_err.h #include soc/soc_caps.h步骤 2: 定义引脚和屏幕参数c// 使用 I80 接口数据总线宽度为 8 #define EXAMPLE_LCD_IO_DATA0 0 #define EXAMPLE_LCD_IO_DATA1 1 #define EXAMPLE_LCD_IO_DATA2 2 #define EXAMPLE_LCD_IO_DATA3 3 #define EXAMPLE_LCD_IO_DATA4 4 #define EXAMPLE_LCD_IO_DATA5 5 #define EXAMPLE_LCD_IO_DATA6 6 #define EXAMPLE_LCD_IO_DATA7 7 #define EXAMPLE_LCD_IO_WR 12 // 写时钟 #define EXAMPLE_LCD_IO_DC 14 // 数据/命令 #define EXAMPLE_LCD_IO_CS 10 // 片选 #define EXAMPLE_LCD_IO_RST 9 // 复位 #define EXAMPLE_LCD_IO_BCKL 46 // 背光 // 屏幕分辨率 #define EXAMPLE_LCD_H_RES 320 #define EXAMPLE_LCD_V_RES 240 // 颜色深度 #define EXAMPLE_LCD_BITS_PER_PIXEL 16 // RGB565 // 背光点亮电平 (根据模块调整多数为高电平点亮) #define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL 1步骤 3: 编写I80总线和LCD初始化函数cstatic esp_lcd_panel_handle_t panel_handle NULL; void bsp_lcd_i80_init(void) { esp_err_t ret ESP_OK; // 1. 准备数据总线引脚数组 int lcd_data_pins[] { EXAMPLE_LCD_IO_DATA0, EXAMPLE_LCD_IO_DATA1, EXAMPLE_LCD_IO_DATA2, EXAMPLE_LCD_IO_DATA3, EXAMPLE_LCD_IO_DATA4, EXAMPLE_LCD_IO_DATA5, EXAMPLE_LCD_IO_DATA6, EXAMPLE_LCD_IO_DATA7, }; // 2. 配置 I80 总线 esp_lcd_i80_bus_handle_t i80_bus NULL; esp_lcd_i80_bus_config_t bus_config { .clk_src LCD_CLK_SRC_PLL160M, // 时钟源 .dc_gpio_num EXAMPLE_LCD_IO_DC, // DC 引脚 .wr_gpio_num EXAMPLE_LCD_IO_WR, // WR 引脚 .data_gpio_nums lcd_data_pins, // 数据引脚数组 .bus_width 8, // 总线宽度 .max_transfer_bytes EXAMPLE_LCD_H_RES * 80 * sizeof(uint16_t), // DMA 传输的最大字节数 }; ret esp_lcd_new_i80_bus(bus_config, i80_bus); ESP_ERROR_CHECK(ret); // 3. 创建 I80 面板 IO 句柄 (用于发送命令和数据) esp_lcd_panel_io_handle_t io_handle NULL; esp_lcd_panel_io_i80_config_t io_config { .cs_gpio_num EXAMPLE_LCD_IO_CS, // CS 引脚 .pclk_hz 20 * 1000 * 1000, // 像素时钟 20MHz .trans_queue_depth 10, // 事务队列深度 .dc_levels { .dc_idle_level 0, .dc_cmd_level 0, // 命令阶段 DC 为低 .dc_dummy_level 0, .dc_data_level 1, // 数据阶段 DC 为高 }, .lcd_cmd_bits 8, // 命令长度 8 位 .lcd_param_bits 8, // 参数长度 8 位 }; ret esp_lcd_new_panel_io_i80(i80_bus, io_config, io_handle); ESP_ERROR_CHECK(ret); // 4. 创建 ST7789 面板实例 esp_lcd_panel_dev_config_t panel_config { .reset_gpio_num EXAMPLE_LCD_IO_RST, // 复位引脚 .rgb_ele_order LCD_RGB_ELEMENT_ORDER_RGB, // RGB 顺序通常 ST7789 需要设置为 BGR可以尝试 RGB 或 BGR .bits_per_pixel EXAMPLE_LCD_BITS_PER_PIXEL, }; ret esp_lcd_new_panel_st7789(io_handle, panel_config, panel_handle); ESP_ERROR_CHECK(ret); // 5. 复位并初始化面板 ret esp_lcd_panel_reset(panel_handle); ESP_ERROR_CHECK(ret); ret esp_lcd_panel_init(panel_handle); ESP_ERROR_CHECK(ret); // 6. 配置屏幕方向和显示参数 (根据实际硬件调整) esp_lcd_panel_invert_color(panel_handle, true); // 根据模块决定是否需要颜色反转 esp_lcd_panel_swap_xy(panel_handle, false); // 是否交换XY轴用于旋转 esp_lcd_panel_mirror(panel_handle, false, false); // 镜像 // 7. 开启显示 ret esp_lcd_panel_disp_on_off(panel_handle, true); ESP_ERROR_CHECK(ret); // 8. 初始化并点亮背光 gpio_config_t bl_gpio_config { .pin_bit_mask 1ULL EXAMPLE_LCD_IO_BCKL, .mode GPIO_MODE_OUTPUT, .pull_up_en GPIO_PULLUP_DISABLE, .pull_down_en GPIO_PULLDOWN_DISABLE, .intr_type GPIO_INTR_DISABLE, }; gpio_config(bl_gpio_config); gpio_set_level(EXAMPLE_LCD_IO_BCKL, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL); }步骤 5: 测试显示cvoid lcd_i80_test(void) { // 分配一个 DMA 兼容的行缓冲区 (一行像素: 320 * 2字节 640字节) uint16_t *line_buffer heap_caps_malloc(EXAMPLE_LCD_H_RES * sizeof(uint16_t), MALLOC_CAP_DMA); if (!line_buffer) { printf(Failed to allocate line buffer\n); return; } // 绘制一个简单的渐变或色块 for (int y 0; y EXAMPLE_LCD_V_RES; y) { for (int x 0; x EXAMPLE_LCD_H_RES; x) { // 生成一个简单的颜色模式红色分量随x变化绿色随y变化 uint8_t r (x * 255) / EXAMPLE_LCD_H_RES; uint8_t g (y * 255) / EXAMPLE_LCD_V_RES; uint8_t b 128; // 转换为 RGB565 line_buffer[x] ((r 3) 11) | ((g 2) 5) | (b 3); } // 将一行数据通过 DMA 发送到屏幕 esp_lcd_panel_draw_bitmap(panel_handle, 0, y, EXAMPLE_LCD_H_RES, y1, line_buffer); } free(line_buffer); printf(Display test finished\n); } void app_main(void) { bsp_lcd_i80_init(); vTaskDelay(pdMS_TO_TICKS(100)); // 等待初始化完成 lcd_i80_test(); }5. 常见问题与调试屏幕无显示或花屏检查硬件连接特别是WR引脚的连接是否正确数据线是否有虚焊或接错。I8080模式对时序要求较高不稳定的连接会导致数据错误 。调整颜色反转esp_lcd_panel_invert_color()。ST7789在I8080模式下可能需要或不需要反转。检查RGB顺序rgb_ele_order设置为RGB还是BGR。如果颜色显示错乱例如红色变成蓝色可以尝试切换此选项。编译错误确保在idf_component.yml或 CMakeLists.txt 中正确引入了esp_lcd组件依赖。性能问题我们使用的是8位并口每次传输一个字节。DMA (MALLOC_CAP_DMA) 是保证性能的关键。确保用于esp_lcd_panel_draw_bitmap的缓冲区是从DMA-capable内存中分配的。现在你的ESP32-S3已经可以通过I8080并口驱动ST7789屏幕了。下一章我们将把Kandinsky图形库的输出重定向到这个面板句柄上让NumWorks的UI真正跑起来源代码已开源https://github.com/jojo240607/esp-numworks.git

更多文章