RYMCU

仅依赖 DC/SCK/MOSI 三个引脚(无 CS/RST),如何驱动SPI显示屏?

Hugh 3 月前
# SPI # LCD # ESP32-S3

以下是针对「无复位引脚 + CS 引脚硬件永久拉低」场景的​完整可运行代码​,适配 ESP-IDF 框架,以主流的 ST7789 240x240 LCD 为例(其他型号如 ILI9341 仅需替换面板初始化函数),核心是「禁用软件控制 CS / 复位引脚 + 软件指令复位 + 高频时序补偿」:

完整代码(关键注释 + 适配逻辑)

#include "driver/spi_master.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

// ====================== 硬件配置(根据实际接线修改) ======================
#define LCD_SPI_HOST        SPI3_HOST       // SPI主机(HSPI)
#define LCD_DC_GPIO         GPIO_NUM_39     // DC/RS引脚(必须接)
#define LCD_SCK_GPIO        GPIO_NUM_14     // SPI时钟引脚
#define LCD_MOSI_GPIO       GPIO_NUM_13     // SPI数据引脚(仅输出,MISO可省)
#define LCD_PCLK_HZ         40 * 1000 * 1000// SPI时钟频率(80MHz易出错,先40MHz)
#define LCD_WIDTH           240             // LCD宽度
#define LCD_HEIGHT          240             // LCD高度
#define LCD_COLOR_INVERT    true            // 颜色反转(根据面板调整)
#define LCD_SWAP_XY         false           // XY轴交换(根据面板调整)
#define LCD_MIRROR_X        false           // X轴镜像(根据面板调整)
#define LCD_MIRROR_Y        false           // Y轴镜像(根据面板调整)

static const char *TAG = "LCD_NO_RST_NO_CS";
esp_lcd_panel_io_handle_t panel_io = NULL;
esp_lcd_panel_handle_t panel = NULL;

// ====================== 核心初始化函数 ======================
static void lcd_init_no_rst_no_cs(void)
{
    // 1. 初始化SPI总线(必须先初始化总线,再初始化面板IO)
    spi_bus_config_t bus_cfg = {
        .sclk_io_num = LCD_SCK_GPIO,         // SPI时钟引脚
        .mosi_io_num = LCD_MOSI_GPIO,        // SPI数据输出引脚
        .miso_io_num = -1,                   // 无MISO(仅写LCD,无需读)
        .quadwp_io_num = -1,                 // 禁用四线SPI
        .quadhd_io_num = -1,                 // 禁用四线SPI
        .max_transfer_sz = LCD_WIDTH * 2,    // 最大传输大小(16位色,每行字节数)
    };
    ESP_ERROR_CHECK(spi_bus_initialize(LCD_SPI_HOST, &bus_cfg, SPI_DMA_CH_AUTO));

    // 2. 配置LCD面板IO(核心:禁用CS/复位引脚)
    esp_lcd_panel_io_spi_config_t io_config = {
        .cs_gpio_num = GPIO_NUM_NC,          // 禁用软件控制CS(硬件永久低电平)
        .dc_gpio_num = LCD_DC_GPIO,          // DC引脚(必须配置)
        .spi_mode = 2,                       // SPI模式(根据LCD手册,ST7789常用模式2)
        .pclk_hz = LCD_PCLK_HZ,              // SPI时钟频率
        .trans_queue_depth = 10,             // 传输队列深度(增大避免阻塞)
        .lcd_cmd_bits = 8,                   // 命令位宽(多数LCD为8)
        .lcd_param_bits = 8,                 // 参数位宽(多数LCD为8)
        // 关键:高频SPI时序补偿(无CS引脚需增加补偿)
        .cs_ena_pretrans = 2,                // 传输前补偿2个bit-cycle
        .cs_ena_posttrans = 2,               // 传输后补偿2个bit-cycle
        // 关键:CS低电平有效(硬件永久低电平,必须显式配置)
        .flags = {
            .cs_high_active = 0,             // CS低电平有效(核心!)
            .dc_high_on_cmd = 0,             // DC低电平=命令,高电平=数据(ST7789默认)
            .dc_low_on_data = 1,             // DC低电平=数据(与上一行匹配)
            .lsb_first = 0,                  // 高位先传输
        },
    };
    // 创建SPI面板IO
    ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_SPI_HOST, &io_config, &panel_io));

    // 3. 配置LCD面板(核心:禁用复位引脚,依赖软件复位)
    esp_lcd_panel_dev_config_t panel_cfg = {
        .reset_gpio_num = GPIO_NUM_NC,       // 禁用硬件复位引脚(无RST引脚)
        .bits_per_pixel = 16,                // 16位色(RGB565,嵌入式主流)
    };
    // 初始化ST7789面板(替换为对应型号:ILI9341用esp_lcd_new_panel_ili9341)
    ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_cfg, &panel));

    // 4. 软件复位(无RST引脚,依赖指令复位)+ 延长稳定时间
    ESP_LOGI(TAG, "Software reset LCD (no RST pin)");
    esp_lcd_panel_reset(panel);             // 内部发送软件复位指令(如ST7789的0x01)
    vTaskDelay(pdMS_TO_TICKS(200));         // 延长稳定时间(无RST引脚需≥200ms)

    // 5. 面板初始化 + 显示配置
    ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
    ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, LCD_COLOR_INVERT));
    ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, LCD_SWAP_XY));
    ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, LCD_MIRROR_X, LCD_MIRROR_Y));
    ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); // 开启显示

    ESP_LOGI(TAG, "LCD init success (no RST, no CS)");
}

// ====================== 测试函数:显示纯色 ======================
static void lcd_test_draw_color(uint16_t color)
{
    uint16_t *buf = (uint16_t *)malloc(LCD_WIDTH * sizeof(uint16_t));
    if (buf == NULL) {
        ESP_LOGE(TAG, "Malloc buffer failed");
        return;
    }
    // 填充纯色
    for (int i = 0; i < LCD_WIDTH; i++) {
        buf[i] = color;
    }
    // 逐行绘制(覆盖整个屏幕)
    for (int y = 0; y < LCD_HEIGHT; y++) {
        ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel, 0, y, LCD_WIDTH, y+1, buf));
    }
    free(buf);
    ESP_LOGI(TAG, "Draw color 0x%04X success", color);
}

// ====================== App Main ======================
void app_main(void)
{
    // 初始化LCD(无RST/CS引脚)
    lcd_init_no_rst_no_cs();

    // 循环显示不同颜色,验证显示正常
    while (1) {
        lcd_test_draw_color(0xF800); // 红色(RGB565)
        vTaskDelay(pdMS_TO_TICKS(1000));
        lcd_test_draw_color(0x07E0); // 绿色
        vTaskDelay(pdMS_TO_TICKS(1000));
        lcd_test_draw_color(0x001F); // 蓝色
        vTaskDelay(pdMS_TO_TICKS(1000));
        lcd_test_draw_color(0xFFFF); // 白色
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

关键适配点(必看!)

1. 核心配置说明(无 RST/CS 场景)

配置项 取值 / 原因
cs_gpio_num = GPIO_NUM_NC 禁用软件控制 CS,完全依赖硬件永久低电平
reset_gpio_num = GPIO_NUM_NC 禁用硬件复位引脚,依赖软件指令复位
cs_ena_pretrans/posttrans = 2 补偿 SPI 时序(无 CS 引脚时,高频下需 ≥2)
flags.cs_high_active = 0 明确 CS 低电平有效(硬件永久低电平,必须显式配置)
复位后延时 200ms 无 RST 引脚时,软件复位指令需要更长时间生效(默认 120ms→ 延长到 200ms)

2. 不同 LCD 型号适配

  • ILI9341​:将 esp_lcd_new_panel_st7789 替换为 esp_lcd_new_panel_ili9341,SPI 模式改为 0(ILI9341 常用模式 0);
  • ST7735​:替换为 esp_lcd_new_panel_st7735,调整 LCD_WIDTH/HEIGHT 为 128x160;
  • ​**SSD1306(OLED)**​:需改用 I2C 配置,cs_gpio_num 仍设为 GPIO_NUM_NC,复位引脚同理。

3. 高频 SPI 适配(如需 80MHz)

若要将 LCD_PCLK_HZ 改为 80MHz,需进一步增加时序补偿:

.io_config.cs_ena_pretrans = 3;  // 80MHz下补偿3个bit-cycle(37.5ns)
.io_config.cs_ena_posttrans = 3;

常见问题排查(无 RST/CS 场景)

  1. 显示乱码 / 无显示​:
    • 降低 SPI 频率到 20MHz 验证;
    • 检查 spi_mode 是否与 LCD 手册一致(模式 0/2 是主流);
    • 确认 dc_high_on_cmd/dc_low_on_data 配置(DC 引脚电平是否区分命令 / 数据)。
  2. 软件复位失效​:
    • 延长复位后延时到 300ms;

    • 手动发送复位指令(替代 esp_lcd_panel_reset):

      const uint8_t sw_reset_cmd = 0x01; // ST7789复位指令
      ESP_ERROR_CHECK(esp_lcd_panel_io_tx_param(panel_io, sw_reset_cmd, NULL, 0));
      vTaskDelay(pdMS_TO_TICKS(200));
      
  3. 颜色反转 / 镜像错误​:
    • 调整 LCD_COLOR_INVERT/LCD_MIRROR_X/Y/LCD_SWAP_XY 参数;

    • 部分面板需配置 esp_lcd_panel_set_gap 调整偏移:

      esp_lcd_panel_set_gap(panel, 0, 0); // X/Y偏移,根据面板调整
      

核心总结

该代码的核心设计是:

  1. 禁用软件控制​:通过 GPIO_NUM_NC 让驱动放弃 CS / 复位引脚的控制;
  2. 时序补偿​:针对无 CS 引脚的硬件设计,增加 SPI 时序补偿;
  3. 软件复位兜底​:无 RST 引脚时,延长复位后延时保证指令生效;
  4. 极简依赖​:仅依赖 DC/SCK/MOSI 三个引脚(无 CS/RST),适配硬件极简设计。

可直接烧录测试,若显示异常,优先降低 SPI 频率、调整 SPI 模式和时序补偿参数。

后发布评论
作者

注意事项:

上电后至少给显示器100ms复位信号,可通过增加RC复位电路的电容值实现。🤒