Skip to content

SPI + ST7789 TFT LCD 完全入门指南


1. 为什么需要 SPI?先从"沟通问题"说起

你的屏幕有 $240 \times 320 = 76,800$ 个像素点,每个像素用 RGB565 格式需要 16 位数据。刷一帧画面就要传输海量数据,所以需要一种又快又省线的通信方式。

想象一下,你(MCU/单片机)要指挥一个工人(LCD 屏幕)画画。 你们之间需要沟通,但问题来了:怎么传递信息?

1.1 单片机怎么"说话"给屏幕听?

你的 ESP32-S3 是一个"大脑",ST7789V2 是屏幕的"控制芯片"。它们是两个独立的芯片,要让屏幕显示内容,大脑必须把指令和数据"传递"给屏幕控制芯片。

这就需要一种通信协议——就像两个人说话要用同一种语言一样。

从 ESP32-S3 到 TFT 面板

1.2 最笨的方法:并行总线

用 8 根线,一次传 8 位数据,快是快,但线太多,引脚不够用!

1.3 聪明的方法:SPI(串行外设接口)

只用 3~4 根线,一位一位地串行传输,节省引脚,速度也够快。

1.4 SPI 的全称

SPI = Serial Peripheral Interface(串行外设接口),是一种同步串行通信协议

1.5 核心概念:主机与从机

  • 主机(Master):发起通信的一方,就是你的 ESP32-S3。
  • 从机(Slave):被动响应的一方,就是你的 ST7789V2 屏幕控制芯片。

SPI = Serial Peripheral Interface(串行外设接口) 这是一种"主从"通信协议:MCU 是主人(Master),LCD 是仆人(Slave)。主人发号施令,仆人照做。

为什么选择 SPI

2. SPI 的四根线,各司其职

你的模块是 8Pin、无 DC 引脚的 3-Line SPI,各引脚分工如下:

信号线全称作用
SCK (SCL)Serial Clock时钟线,由主人(MCU)产生,像节拍器一样控制节奏,每跳动一下传一个 bit
MOSI (SDA)Master Out Slave In主人发数据给仆人
MISOMaster In Slave Out仆人发数据给主人(你的屏幕没有这根线!)
CS (片选)Chip Select选中某个仆人,告诉它"我要跟你说话了"
D/CData/Command没有单独将Data线独立出来,是D/C共用一条

你的 ST7789 模块是 8Pin、无 MISO、3-line SPI,所以只有 SCK、SDA(MOSI)、CS、DC、RST、BLK 等,没有 MISO——因为屏幕只需要接收命令,不需要回话。

SPI 四根核心信号线

2.1 3-Line SPI 如何区分"命令"和"数据"?

没有 DC 引脚,屏幕怎么知道收到的是命令还是数据?答案是:把 D/C 标志位藏在数据流的第一个 bit 里

4-line SPI(有 DC 引脚):
  DC引脚=低  +  8bit数据  →  命令
  DC引脚=高  +  8bit数据  →  数据

3-line SPI(无 DC 引脚,9-bit 模式):
  第1bit=0   +  8bit内容  →  命令(共9bit)
  第1bit=1   +  8bit内容  →  数据(共9bit)

下图直观展示了 3-line 和 4-line 两种模式的时序对比,可以清楚看到 3-line 模式在 SDA 数据线上多了一个 D/C 标志位:

⚠️ 注意:没有 DC 引脚,这就是 3-Line SPI 的关键所在!

4-line SPI 用一根独立的 DC 引脚来区分"命令"和"数据"。而你的模块没有 DC 引脚,它的区分方式是:在每次传输的第 1 个 bit 里塞入一个 D/C 标志位,然后再跟上 8 位的命令或数据内容,合计 9 个 bit 一起发送,所以 3-line 模式也叫 9-bit 模式


3. CS 片选信号——"喂,我在跟你说话!"

这是很多小白最困惑的地方,我们用生活场景来理解:

3.1 🏫 教室场景比喻

想象教室里有很多学生(多个 SPI 设备),老师(MCU)要点名提问:

  • 老师说:"张三,站起来!"(连接着张三的 CS 拉低 = 选中张三)
  • 老师开始提问(传输数据)
  • 老师说:"坐下!"(CS 拉高 = 取消选中)
  • 其他学生全程忽略老师说的话

3.2 CS 的规则:

  • CS 拉低(LOW) = "我选中你了,准备接收数据!"
  • CS 拉高(HIGH) = "我说完了,你可以休息了"
  • 传输数据期间,CS 必须保持低电平(如果CS拉高,仍发送数据也是无效的)
  • 没被选中的设备,忽略总线上的所有信号
CS:   ‾‾‾‾‾‾|_______________|‾‾‾‾‾‾
              ↑               ↑
           拉低=开始        拉高=结束
SCK:          ||||||||||||||||
MOSI:         [数据数据数据数据]

📌 生活类比: CS 就像按门铃。你按下去(拉低),屏幕才开门听你说话;你松开(拉高),屏幕就关门不理你了。

为什么需要 CS? 一条 SPI 总线上可以同时挂多个设备(比如屏幕 + 传感器)。开发板通过控制不同设备的 CS 引脚,来决定"现在在跟谁说话",互不干扰。[SPI LCD API]


4. 时钟(SCK)——数据传输的"节拍器"

SPI 是同步通信,意思是:数据的发送和接收都要跟着时钟节拍走。

4.1 🎵 音乐节拍比喻

. 就像乐队演奏,指挥(SCK)打一拍,所有人(数据线)同步动作一次。

  • 时钟每跳动一次(一个上升沿或下降沿),传输 1 个 bit
  • 传输 1 个字节(8 bit)需要 8 个时钟脉冲

4.2 SPI 速率

SPI 的速率就是时钟频率由 SCK 时钟频率 决定,频率越高,传输越快:

  • 低速:几百 kHz(适合简单传感器)
  • 高速:几十 MHz(适合 LCD 屏幕刷图)
  • ESP32 系列最高支持 80 MHz(即每秒传输 8000 万个 bit)
  • 实际使用时不能超过 LCD 规格书推荐的上限,否则信号失真、通信不稳定
  • 在代码中通过 pclk_hz 参数设置

你的 ST7789 屏幕支持较高的 SPI 时钟,但不能超过规格书推荐值,否则数据会出错。

[创建接口设备]


5. 时序图——用图看懂数据是怎么传的

下面是 4-line SPI(你的屏幕用的模式)的时序图概念:

CS:    ‾‾|_________________________________|‾‾
DC:       |___命令___|‾‾‾‾‾数据‾‾‾‾‾|
SCK:       ↑↓↑↓↑↓↑↓  ↑↓↑↓↑↓↑↓↑↓↑↓↑↓↑↓
MOSI:      [D7..D0]   [D7..D0][D7..D0]...

5.1 关键点:DC 线(Data/Command)

你的屏幕有一根特殊的线叫 DC(也叫 RS),这是 4-line 模式的核心:

  • DC = 0(低电平) → 你发的是"命令"(告诉屏幕做什么,比如"设置显示区域")
  • DC = 1(高电平) → 你发的是"数据"(像素颜色数据)

这就是 4-line 模式(有 DC 线)和 3-line 模式(无 DC 线,用第9位区分命令/数据)的区别。你的模块虽然叫"3-Line SPI",但实际上有 DC 引脚,属于 4-line 接口,ESP 完全支持!

[SPI LCD 详解]


6. SPI 通信完整流程(以发一条命令为例)

以"告诉 ST7789 清屏"为例,完整流程:

第1步:CS 拉低  → "ST7789,我要跟你说话了!"
第2步:DC 拉低  → "我接下来发的是命令"
第3步:MOSI 发送命令字节(如 0x2C)→ 一位一位随时钟发出
第4步:DC 拉高  → "接下来发的是数据"
第5步:MOSI 发送像素数据(颜色值)→ 大量数据
第6步:CS 拉高  → "说完了,再见!"
第7步:ST7789V2 把收到的颜色数据写入内部 GRAM(显存),控制LCD屏幕显示出对应颜色

SPI 驱动结果


7. 用 ESP-IDF 驱动 ST7789 的步骤

好了,概念理解了,现在来看代码怎么写。ESP-IDF 把 SPI LCD 的驱动分成了三大步骤

🔧 Step 1:初始化 SPI 总线

先告诉 ESP32 哪些引脚用于 SPI,就像"铺好道路":

c
spi_bus_config_t buscfg = {
    .sclk_io_num = EXAMPLE_PIN_NUM_SCLK,   // 时钟线引脚
    .mosi_io_num = EXAMPLE_PIN_NUM_MOSI,   // 数据线引脚(你的SDA)
    .miso_io_num = -1,                     // 你的屏幕没有MISO,填-1
    .quadwp_io_num = -1,
    .quadhd_io_num = -1,
    .max_transfer_sz = 240 * 320 * sizeof(uint16_t), // 最大传输大小(整屏)
};
ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); // 启用DMA加速

DMA 是什么? 直接内存访问,让数据传输不占用 CPU,CPU 可以去干别的事,效率更高!

[SPI 接口的 LCD]


🔧 Step 2:创建 LCD IO 设备句柄

告诉驱动:DC 线、CS 线在哪,时钟频率多少:

c
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_spi_config_t io_config = {
    .dc_gpio_num = EXAMPLE_PIN_NUM_LCD_DC,       // DC引脚
    .cs_gpio_num = EXAMPLE_PIN_NUM_LCD_CS,       // CS引脚
    .pclk_hz = 40 * 1000 * 1000,                // 时钟频率:40MHz
    .lcd_cmd_bits = 8,                           // 命令位宽:8位
    .lcd_param_bits = 8,                         // 参数位宽:8位
    .spi_mode = 0,                               // SPI模式0(最常用)
    .trans_queue_depth = 10,                     // 传输队列深度
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(
    (esp_lcd_spi_bus_handle_t)SPI2_HOST, 
    &io_config, 
    &io_handle
));

[SPI 接口的 LCD]


🔧 Step 3:安装 ST7789 面板驱动

最后,告诉驱动"我用的是 ST7789 芯片,屏幕是 240×320":

c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_dev_config_t panel_config = {
    .reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST,  // 复位引脚
    .rgb_endian = LCD_RGB_ENDIAN_RGB,
    .bits_per_pixel = 16,                        // RGB565格式,16位色
};
// 使用ST7789驱动(需要在组件中添加对应驱动)
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));

// 复位并初始化屏幕
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); // 开启显示

8. 整体流程总结图

你的代码


[ESP-IDF SPI 驱动]
   │  SCK(时钟)
   │  MOSI(数据)
   │  CS(片选)
   │  DC(命令/数据区分)
   │  RST(复位)

[ST7789 V2 驱动芯片]


[240×320 TFT LCD 像素阵列]

小结:SPI 核心要点速记 📌

概念一句话记住
SPI主从串行通信,4根线搞定
CS拉低=选中,拉高=释放
SCK时钟节拍,控制传输速率
MOSI主→从,发命令和数据
DC低=命令,高=数据(ST7789专用)
4-line有DC线,ESP32 完全支持 ✅

掌握了这些概念,再看代码就会清晰很多!有任何不懂的地方,随时可以继续问 😊

觉醒,然后燎原。 © 2026 门主引擎