SPI + ST7789 TFT LCD 完全入门指南
1. 为什么需要 SPI?先从"沟通问题"说起
你的屏幕有 $240 \times 320 = 76,800$ 个像素点,每个像素用 RGB565 格式需要 16 位数据。刷一帧画面就要传输海量数据,所以需要一种又快又省线的通信方式。
想象一下,你(MCU/单片机)要指挥一个工人(LCD 屏幕)画画。 你们之间需要沟通,但问题来了:怎么传递信息?
1.1 单片机怎么"说话"给屏幕听?
你的 ESP32-S3 是一个"大脑",ST7789V2 是屏幕的"控制芯片"。它们是两个独立的芯片,要让屏幕显示内容,大脑必须把指令和数据"传递"给屏幕控制芯片。
这就需要一种通信协议——就像两个人说话要用同一种语言一样。

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)。主人发号施令,仆人照做。

2. SPI 的四根线,各司其职
你的模块是 8Pin、无 DC 引脚的 3-Line SPI,各引脚分工如下:
| 信号线 | 全称 | 作用 |
|---|---|---|
| SCK (SCL) | Serial Clock | 时钟线,由主人(MCU)产生,像节拍器一样控制节奏,每跳动一下传一个 bit |
| MOSI (SDA) | Master Out Slave In | 主人发数据给仆人 |
| MISO | Master In Slave Out | 仆人发数据给主人(你的屏幕没有这根线!) |
| CS (片选) | Chip Select | 选中某个仆人,告诉它"我要跟你说话了" |
| D/C | Data/Command | 没有单独将Data线独立出来,是D/C共用一条 |
你的 ST7789 模块是 8Pin、无 MISO、3-line SPI,所以只有 SCK、SDA(MOSI)、CS、DC、RST、BLK 等,没有 MISO——因为屏幕只需要接收命令,不需要回话。

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 完全支持!
6. SPI 通信完整流程(以发一条命令为例)
以"告诉 ST7789 清屏"为例,完整流程:
第1步:CS 拉低 → "ST7789,我要跟你说话了!"
第2步:DC 拉低 → "我接下来发的是命令"
第3步:MOSI 发送命令字节(如 0x2C)→ 一位一位随时钟发出
第4步:DC 拉高 → "接下来发的是数据"
第5步:MOSI 发送像素数据(颜色值)→ 大量数据
第6步:CS 拉高 → "说完了,再见!"
第7步:ST7789V2 把收到的颜色数据写入内部 GRAM(显存),控制LCD屏幕显示出对应颜色
7. 用 ESP-IDF 驱动 ST7789 的步骤
好了,概念理解了,现在来看代码怎么写。ESP-IDF 把 SPI LCD 的驱动分成了三大步骤:
🔧 Step 1:初始化 SPI 总线
先告诉 ESP32 哪些引脚用于 SPI,就像"铺好道路":
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 可以去干别的事,效率更高!
🔧 Step 2:创建 LCD IO 设备句柄
告诉驱动:DC 线、CS 线在哪,时钟频率多少:
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
));🔧 Step 3:安装 ST7789 面板驱动
最后,告诉驱动"我用的是 ST7789 芯片,屏幕是 240×320":
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 完全支持 ✅ |
掌握了这些概念,再看代码就会清晰很多!有任何不懂的地方,随时可以继续问 😊
