根据知识库中的代码示例,我可以整理出 ST7789 数据手册中最常用的命令。但需要说明:完整的命令参数细节以你手上的 ST7789V2 数据手册为准,以下是基于知识库代码总结的重点命令。
如果无法预览,请点击 此处下载 ST7789P3_SPEC_V0.0_Preliminary PDF 文件。
壹. ST7789 数据手册重点命令
一、系统控制类命令(必须掌握)
| 命令 | 十六进制 | 名称 | 作用 |
|---|---|---|---|
| SWRESET | 0x01 | Software Reset | 软件复位,重置所有寄存器 |
| SLPIN | 0x10 | Sleep In | 进入睡眠模式(省电) |
| SLPOUT | 0x11 | Sleep Out | 退出睡眠,上电必发 |
| DISPOFF | 0x28 | Display Off | 关闭显示输出 |
| DISPON | 0x29 | Display On | 打开显示,初始化最后一步 |
[系统控制命令]
重点:上电后必须先发
0x11(SLPOUT),等待至少 5ms,最后发0x29(DISPON)。这是屏幕能否点亮的关键顺序。

二、显示配置类命令(初始化核心)
0x36 — MADCTL(Memory Data Access Control)最重要!
控制屏幕的扫描方向、横竖屏、RGB/BGR顺序,靠这一个寄存器的各个 bit 来控制:
| Bit | 名称 | 作用 |
|---|---|---|
| Bit 7 | MY | 行地址顺序(上下翻转) |
| Bit 6 | MX | 列地址顺序(左右翻转) |
| Bit 5 | MV | 行列互换(横竖屏切换) |
| Bit 4 | ML | 垂直刷新方向 |
| Bit 3 | BGR | 0=RGB,1=BGR(颜色顺序) |
| Bit 2 | MH | 水平刷新方向 |
常用参数示例(来自知识库代码):
c
// 竖屏,RGB顺序
{0x36, {0x00}, 1}
// 横屏(MV+MX)
{0x36, {(1<<5)|(1<<6)}, 1}
// 竖屏,BGR顺序(很多屏幕需要这个,否则红蓝颠倒)
{0x36, {0x08}, 1}

0x3A — COLMOD(Interface Pixel Format)
设置像素颜色格式,决定每个像素用多少位表示:
c
{0x3A, {0x55}, 1} // 0x55 = 16位色 RGB565(最常用)
{0x3A, {0x66}, 1} // 0x66 = 18位色 RGB666[COLMOD说明]
RGB565 格式:每个像素 2 字节,R占5位、G占6位、B占5位,是 TFT 屏幕最常用的格式。
0x20 / 0x21 — INVOFF / INVON(Display Inversion)
颜色反转开关。ST7789 的屏幕通常需要开启 INVON(0x21)才能显示正常颜色,否则颜色会反相。
c
{0x21, {0}, 0} // Display Inversion On(ST7789 通常需要这个)[颜色反转说明]
三、显存操作类命令(画图必用)
这三个命令是每次画图都要用到的,必须掌握:
0x2A — CASET(Column Address Set)设置列范围
c
// 设置列地址范围:从 x_start 到 x_end
lcd_cmd(spi, 0x2A);
uint8_t col_data[] = {
(x_start >> 8) & 0xFF, // 起始列高字节
x_start & 0xFF, // 起始列低字节
((x_end-1) >> 8) & 0xFF, // 结束列高字节
(x_end-1) & 0xFF // 结束列低字节
};
lcd_data(spi, col_data, 4);0x2B — RASET(Row Address Set)设置行范围
c
// 设置行地址范围:从 y_start 到 y_end
lcd_cmd(spi, 0x2B);
uint8_t row_data[] = {
(y_start >> 8) & 0xFF,
y_start & 0xFF,
((y_end-1) >> 8) & 0xFF,
(y_end-1) & 0xFF
};
lcd_data(spi, row_data, 4);0x2C — RAMWR(Memory Write)写入像素数据
c
// 发完 CASET 和 RASET 之后,发这个命令,再发颜色数据
lcd_cmd(spi, 0x2C);
lcd_data(spi, color_data, len); // 逐像素发送 RGB565 颜色值[显存操作命令]
画图固定流程:
CASET→RASET→RAMWR→ 发颜色数据。每次画图都要先设置区域,再写数据。
四、电源与 Gamma 类命令(初始化配置)
这些命令在初始化时发送一次,之后不需要改动:
| 命令 | 名称 | 作用 |
|---|---|---|
0xB2 | PORCTRL | Porch Setting,控制消隐期,影响刷新稳定性 |
0xB7 | GCTRL | Gate Control,栅极驱动电压 |
0xBB | VCOMS | VCOM 电压,影响对比度 |
0xC0 | LCMCTRL | LCM 控制 |
0xC2 | VDVVRHEN | VDV/VRH 使能 |
0xC3 | VRHS | VRH 电压设置 |
0xC4 | VDVS | VDV 电压设置 |
0xC6 | FRCTRL2 | 帧率控制,0x0F = 60Hz |
0xD0 | PWCTRL1 | 电源控制 |
0xE0 | PVGAMCTRL | 正极 Gamma 曲线校正 |
0xE1 | NVGAMCTRL | 负极 Gamma 曲线校正 |
关于 Gamma:Gamma 曲线影响屏幕的色彩准确性和过渡效果。初学阶段可以直接用知识库中的参数值,屏幕厂商通常也会在规格书里提供推荐值。
五、完整初始化顺序总结
上电
↓
RST 拉低 → 等待 → RST 拉高(硬件复位)
↓
0x11 SLPOUT 退出睡眠(等待 ≥ 5ms,建议 100ms)
↓
0x3A COLMOD 设置像素格式(RGB565)
0x36 MADCTL 设置扫描方向
0xB2 PORCTRL Porch 设置
0xB7 GCTRL Gate 控制
0xBB VCOMS VCOM 设置
0xC0 LCMCTRL
0xC2 VDVVRHEN
0xC3 VRHS
0xC4 VDVS
0xC6 FRCTRL2 帧率 60Hz
0xD0 PWCTRL1 电源控制
0xE0 PVGAMCTRL 正 Gamma
0xE1 NVGAMCTRL 负 Gamma
0x21 INVON 颜色反转(ST7789 通常需要)
↓
0x29 DISPON 打开显示
↓
BL 引脚拉高(点亮背光)
↓
画图:CASET → RASET → RAMWR → 发颜色数据[完整初始化序列参考] [ST7789驱动源码]

好的!下面我把之前那三步的组件代码,完全改写为直接操作 SPI + 寄存器的原生驱动代码,不使用任何 esp_lcd 组件。

贰. 从零开始:ST7789V2 原生 SPI 驱动
1. 完整代码
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
/* ============================================================
* 引脚定义(根据你的接线)
* ============================================================ */
#define PIN_NUM_MOSI 11 // SDA(MOSI)
#define PIN_NUM_SCLK 12 // SCLK
#define PIN_NUM_CS 10 // CS
#define PIN_NUM_DC 14 // DC
#define PIN_NUM_RST 13 // RST
#define PIN_NUM_BL 9 // BL(背光)
/* ============================================================
* 屏幕分辨率
* ============================================================ */
#define LCD_WIDTH 240
#define LCD_HEIGHT 320
/* ============================================================
* ST7789 常用命令定义
* ============================================================ */
#define ST7789_SWRESET 0x01 // Software Reset
#define ST7789_SLPOUT 0x11 // Sleep Out
#define ST7789_INVOFF 0x20 // Display Inversion Off
#define ST7789_INVON 0x21 // Display Inversion On
#define ST7789_DISPOFF 0x28 // Display Off
#define ST7789_DISPON 0x29 // Display On
#define ST7789_CASET 0x2A // Column Address Set
#define ST7789_RASET 0x2B // Row Address Set
#define ST7789_RAMWR 0x2C // Memory Write
#define ST7789_MADCTL 0x36 // Memory Data Access Control
#define ST7789_COLMOD 0x3A // Interface Pixel Format
/* ============================================================
* 初始化命令表结构体
* databytes:参数字节数
* bit7=1 表示发完后需要延时 100ms
* 0xFF 表示命令表结束
* ============================================================ */
typedef struct {
uint8_t cmd;
uint8_t data[16];
uint8_t databytes;
} lcd_init_cmd_t;
/* ============================================================
* ST7789V2 初始化命令序列
* 数据放在 DRAM,确保 DMA 可以访问
* ============================================================ */
DRAM_ATTR static const lcd_init_cmd_t st7789_init_cmds[] = {
/* MADCTL:扫描方向,MV+MX = 横屏;竖屏改为 {0x00} */
{ST7789_MADCTL, {(1 << 5) | (1 << 6)}, 1},
/* COLMOD:像素格式,0x55 = RGB565(16位色)*/
{ST7789_COLMOD, {0x55}, 1},
/* PORCTRL:Porch Setting,控制消隐期 */
{0xB2, {0x0C, 0x0C, 0x00, 0x33, 0x33}, 5},
/* GCTRL:Gate Control,栅极驱动电压 */
{0xB7, {0x45}, 1},
/* VCOMS:VCOM 电压,影响对比度 */
{0xBB, {0x2B}, 1},
/* LCMCTRL */
{0xC0, {0x2C}, 1},
/* VDVVRHEN:VDV/VRH 使能 */
{0xC2, {0x01, 0xFF}, 2},
/* VRHS:VRH 电压 */
{0xC3, {0x11}, 1},
/* VDVS:VDV 电压 */
{0xC4, {0x20}, 1},
/* FRCTRL2:帧率控制,0x0F = 60Hz */
{0xC6, {0x0F}, 1},
/* PWCTRL1:电源控制 */
{0xD0, {0xA4, 0xA1}, 2},
/* PVGAMCTRL:正极 Gamma 曲线 */
{0xE0, {0xD0, 0x00, 0x05, 0x0E, 0x15, 0x0D,
0x37, 0x43, 0x47, 0x09, 0x15, 0x12,
0x16, 0x19}, 14},
/* NVGAMCTRL:负极 Gamma 曲线 */
{0xE1, {0xD0, 0x00, 0x05, 0x0D, 0x0C, 0x06,
0x2D, 0x44, 0x40, 0x0E, 0x1C, 0x18,
0x16, 0x19}, 14},
/* SLPOUT:退出睡眠,bit7=1 表示之后延时 100ms */
{ST7789_SLPOUT, {0}, 0x80},
/* DISPON:打开显示,bit7=1 表示之后延时 100ms */
{ST7789_DISPON, {0}, 0x80},
/* 结束标志 */
{0, {0}, 0xFF}
};
/* ============================================================
* SPI 传输前回调:根据 t->user 设置 DC 引脚
* t->user = 0 → DC 低电平 → 命令
* t->user = 1 → DC 高电平 → 数据
* ============================================================ */
void lcd_spi_pre_transfer_callback(spi_transaction_t *t)
{
int dc = (int)t->user;
gpio_set_level(PIN_NUM_DC, dc);
}
/* ============================================================
* 发送一个命令字节(DC = 0)
* ============================================================ */
void lcd_cmd(spi_device_handle_t spi, uint8_t cmd)
{
spi_transaction_t t;
memset(&t, 0, sizeof(t));
t.length = 8; // 8 位
t.tx_buffer = &cmd;
t.user = (void*)0; // DC = 0,命令
ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &t));
}
/* ============================================================
* 发送数据/参数(DC = 1)
* ============================================================ */
void lcd_data(spi_device_handle_t spi, const uint8_t *data, int len)
{
if (len == 0) return;
spi_transaction_t t;
memset(&t, 0, sizeof(t));
t.length = len * 8; // 字节转换为位
t.tx_buffer = data;
t.user = (void*)1; // DC = 1,数据
ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &t));
}
/* ============================================================
* 设置绘图区域(CASET + RASET)
* 每次写像素前必须先调用这个函数
* ============================================================ */
void lcd_set_window(spi_device_handle_t spi,
uint16_t x0, uint16_t y0,
uint16_t x1, uint16_t y1)
{
uint8_t data[4];
/* CASET:列地址范围 */
lcd_cmd(spi, ST7789_CASET);
data[0] = (x0 >> 8) & 0xFF;
data[1] = x0 & 0xFF;
data[2] = (x1 >> 8) & 0xFF;
data[3] = x1 & 0xFF;
lcd_data(spi, data, 4);
/* RASET:行地址范围 */
lcd_cmd(spi, ST7789_RASET);
data[0] = (y0 >> 8) & 0xFF;
data[1] = y0 & 0xFF;
data[2] = (y1 >> 8) & 0xFF;
data[3] = y1 & 0xFF;
lcd_data(spi, data, 4);
/* RAMWR:准备写入像素数据 */
lcd_cmd(spi, ST7789_RAMWR);
}
/* ============================================================
* 用单一颜色填充整个屏幕
* color:RGB565 格式,例如:
* 红色 = 0xF800
* 绿色 = 0x07E0
* 蓝色 = 0x001F
* 白色 = 0xFFFF
* 黑色 = 0x0000
* ============================================================ */
void lcd_fill_color(spi_device_handle_t spi, uint16_t color)
{
/* 设置全屏区域 */
lcd_set_window(spi, 0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1);
/* 准备一行的颜色缓冲区 */
uint16_t *line_buf = heap_caps_malloc(
LCD_WIDTH * sizeof(uint16_t), MALLOC_CAP_DMA);
/* 填充颜色(注意字节序:高字节在前)*/
for (int i = 0; i < LCD_WIDTH; i++) {
line_buf[i] = (color >> 8) | (color << 8); // 大端转换
}
/* 逐行发送 */
spi_transaction_t t;
for (int y = 0; y < LCD_HEIGHT; y++) {
memset(&t, 0, sizeof(t));
t.length = LCD_WIDTH * 16; // 一行像素,单位:位
t.tx_buffer = line_buf;
t.user = (void*)1; // DC = 1,数据
ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &t));
}
free(line_buf);
}
/* ============================================================
* LCD 初始化:GPIO + SPI + 发送初始化命令序列
* ============================================================ */
void lcd_init(spi_device_handle_t spi)
{
/* --- 1. 配置 DC / RST / BL 为输出引脚 --- */
gpio_config_t io_conf = {};
io_conf.pin_bit_mask = ((1ULL << PIN_NUM_DC) |
(1ULL << PIN_NUM_RST) |
(1ULL << PIN_NUM_BL));
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&io_conf);
/* --- 2. 硬件复位 --- */
gpio_set_level(PIN_NUM_RST, 0);
vTaskDelay(100 / portTICK_PERIOD_MS);
gpio_set_level(PIN_NUM_RST, 1);
vTaskDelay(100 / portTICK_PERIOD_MS);
/* --- 3. 逐条发送初始化命令 --- */
int i = 0;
while (st7789_init_cmds[i].databytes != 0xFF) {
/* 发命令(DC=0)*/
lcd_cmd(spi, st7789_init_cmds[i].cmd);
/* 发参数(DC=1),低5位是实际字节数 */
lcd_data(spi, st7789_init_cmds[i].data,
st7789_init_cmds[i].databytes & 0x1F);
/* bit7=1 表示需要延时 100ms */
if (st7789_init_cmds[i].databytes & 0x80) {
vTaskDelay(100 / portTICK_PERIOD_MS);
}
i++;
}
/* --- 4. 点亮背光 --- */
gpio_set_level(PIN_NUM_BL, 1);
}
/* ============================================================
* app_main:程序入口
* ============================================================ */
void app_main(void)
{
spi_device_handle_t spi;
/* --- 第一步:初始化 SPI 总线 --- */
spi_bus_config_t buscfg = {
.mosi_io_num = PIN_NUM_MOSI,
.miso_io_num = -1, // 不需要读数据
.sclk_io_num = PIN_NUM_SCLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = LCD_WIDTH * 16 * sizeof(uint16_t)
};
ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
/* --- 第二步:注册 LCD 为 SPI 设备 --- */
spi_device_interface_config_t devcfg = {
.clock_speed_hz = 40 * 1000 * 1000, // 40MHz
.mode = 0, // SPI Mode 0
.spics_io_num = PIN_NUM_CS, // CS 引脚
.queue_size = 7,
.pre_cb = lcd_spi_pre_transfer_callback, // DC 引脚回调
};
ESP_ERROR_CHECK(spi_bus_add_device(SPI2_HOST, &devcfg, &spi));
/* --- 第三步:初始化 LCD --- */
lcd_init(spi);
/* --- 第四步:填充颜色测试 --- */
lcd_fill_color(spi, 0xF800); // 红色
vTaskDelay(1000 / portTICK_PERIOD_MS);
lcd_fill_color(spi, 0x07E0); // 绿色
vTaskDelay(1000 / portTICK_PERIOD_MS);
lcd_fill_color(spi, 0x001F); // 蓝色
vTaskDelay(1000 / portTICK_PERIOD_MS);
lcd_fill_color(spi, 0xFFFF); // 白色
}[SPI底层发送函数参考] [初始化命令序列参考] [SPI总线初始化参考]
叁. 代码结构总览
app_main()
├── spi_bus_initialize() 初始化 SPI2 总线
├── spi_bus_add_device() 注册屏幕为 SPI 从设备
└── lcd_init()
├── gpio_config() 配置 DC/RST/BL 引脚
├── RST 复位时序
├── 逐条发送初始化命令
│ ├── lcd_cmd() DC=0,发命令
│ └── lcd_data() DC=1,发参数
└── BL 拉高,背光亮起
画图流程:
lcd_set_window() CASET → RASET → RAMWR
lcd_data() / polling_transmit 发送 RGB565 像素数据
肆. 几个关键点说明
| 问题 | 说明 |
|---|---|
为什么用 MALLOC_CAP_DMA? | DMA 传输要求内存在特定区域,普通 malloc 分配的内存 DMA 可能无法访问 |
| 为什么要大端转换? | ST7789 期望高字节在前,ESP32 是小端架构,需要手动交换字节序 |
databytes & 0x1F 是什么意思? | 低5位才是真正的参数字节数,bit7 被用来标记"是否需要延时" |
pre_cb 回调的作用? | 在每次 SPI 传输开始前自动设置 DC 引脚,不需要手动控制 |
