本课对应《DNESP32S3 使用指南(IDF 版)》第 22 章。配套完整工程:examples/17-spi-lcd。
环境与验收边界: DNESP32S3 V1.2,ESP-IDF v5.5.5;本机 macOS 构建通过。Ubuntu 26、外设接线、屏幕效果和实际测量仍待实板验收。下文输出均为预期格式,不能当作已采集的硬件日志。
本课完成后能做什么
- 红、绿、蓝、白、黑每秒切换一次,全屏范围正确。
- 日志报告每次填充耗时;用逻辑分析仪可看到 D/C 区分命令与数据。
- 解释只写屏为何不需要 MISO,但 SPI 仍具有全双工能力。
硬件连接与资源
| 信号或资源 | 对应关系 |
|---|---|
| 模块 | ATK-MD0240,ST7789V;本例竖屏 240×320 |
| MOSI / SCLK / CS | GPIO11 / GPIO12 / GPIO21 |
| D/C | GPIO40,P5 IO_SEL 与 LCD_DC 跳线 |
| 复位 / 供电使能 | XL9555 IO1_2 / IO1_3 |
| MISO | 本例只写屏,设 -1;板上总线信号为 GPIO13 |
以下为正点原子 DNESP32S3 V1.2 原理图的相关页,保留原厂标识;表格用于快速定位,遇到板卡版本差异以实际原理图为准。点击图片可查看引脚与网络名。


SPI 的四根线与本例的三线写屏
通常 SPI 有 SCLK、MOSI、MISO、CS。主机每发一个时钟,可以同时移出和移入一位,因此总线具备全双工能力。但显示屏绘图主要向显存写数据,本例不读取屏幕,MISO 配置为 -1。不能由此推断所有 SPI 外设都不需要 MISO。
D/C 是屏幕另外增加的信号:0 表示命令,1 表示命令参数或像素数据。CS 由 SPI 驱动控制,D/C 在事务开始前设置。P5 必须把 IO_SEL 接到 LCD_DC,否则 GPIO40 的变化到不了屏幕。
本工程明确针对 2.4 寸 ATK-MD0240 / ST7789V。模块常以 320×240 横屏宣传,本例 MADCTL=0 使用 240×320 竖屏。1.3 寸 240×240 模块可能需要显示偏移和初始化差异,不属于此默认配置。
一个像素为什么是两个字节
RGB565 分给红色 5 位、绿色 6 位、蓝色 5 位:
| 颜色 | 16 位值 | 发送字节 |
|---|---|---|
| 红 | 0xF800 | F8 00 |
| 绿 | 0x07E0 | 07 E0 |
| 蓝 | 0x001F | 00 1F |
ESP32 内存的小端布局不能直接当成屏幕要求的字节顺序。本例显式生成高字节、低字节,避免红蓝或明暗出现难以理解的异常。
先窗口,再像素
0x2A 设置列范围 0~239,0x2B 设置行范围 0~319,0x2C 开始写显存。程序只准备一行 480 字节,重复写 320 次,省去 153600 字节整屏缓冲。
spi_device_polling_transmit() 返回后才能复用行缓冲。本例用同步接口把缓冲区生命周期讲清楚;未来换异步 DMA 时必须等待完成事件,不能提交后立即改写数据。10 MHz 下仅像素净荷的理论下限约 122.9 ms,实际还包含事务和软件开销,不应把日志耗时当成精确 FPS。
驱动先打开屏幕电源、拉低复位,再释放并等待,随后退出睡眠、设置 RGB565 和显示方向。初始化次序与等待时间同样属于协议。
工程如何组织
驱动放在 components/BSP/,应用放在 main/main.c。本工程包含 BUS、EXIO、LCD 模块。项目根目录的 components/BSP 会被 ESP-IDF 自动发现,main 的 PRIV_REQUIRES BSP 声明组件依赖;INCLUDE_DIRS 导出头文件搜索路径,不是运行时查找文件。
固定引脚以 CONFIG_LESSON_* 常量写入本工程 Kconfig.projbuild,构建后可在 sdkconfig 核对;这些默认值对应本板连线,不表示任意改数字就能改变硬件。
应用代码:入口与处理流程
下面代码直接取自配套工程的 main/main.c,头文件和 BSP 实现在仓库中一并提供。初学时先看 app_main(),再追踪它调用的模块。
1#include <stdio.h>
2#include <stdint.h>
3#include <stdbool.h>
4#include <string.h>
5#include <stdlib.h>
6#include <assert.h>
7#include "esp_err.h"
8#include "esp_log.h"
9#include "esp_timer.h"
10#include "freertos/FreeRTOS.h"
11#include "freertos/task.h"
12#include "lcd.h"
13#include "exio.h"
14void app_main(void)
15{
16 i2c_master_bus_handle_t bus;
17 ESP_ERROR_CHECK(bus_init(0,CONFIG_LESSON_I2C_SDA,CONFIG_LESSON_I2C_SCL,&bus));
18 ESP_ERROR_CHECK(exio_init(bus));
19 ESP_ERROR_CHECK(lcd_init());
20 const uint16_t colors[]={0xf800,0x07e0,0x001f,0xffff,0};
21 while(1) for(int i=0;i<5;i++) {
22 int64_t begin=esp_timer_get_time();
23 ESP_ERROR_CHECK(lcd_fill(colors[i]));
24 ESP_LOGI("lcd","RGB565=0x%04x fill=%lld us",colors[i],esp_timer_get_time()-begin);
25 vTaskDelay(pdMS_TO_TICKS(1000));
26 }
27}
BSP 关键实现:LCD
以下是本课对应驱动的完整源文件。对外声明见 lcd.h。公共 BUS 模块统一使用新 I²C master API,将单次事务等待限制在 50 ms;设备 API 使用 7 位地址。
1#include "sdkconfig.h"
2#include "lcd.h"
3#include "exio.h"
4#include "driver/spi_master.h"
5#include "driver/gpio.h"
6#include "freertos/FreeRTOS.h"
7#include "freertos/task.h"
8// ATK-MD0240 / ST7789V,竖屏 240x320;其他屏幕不能只改分辨率就假定兼容。
9static spi_device_handle_t s_spi;
10static esp_err_t transfer(int dc,const void *data,size_t n)
11{
12 gpio_set_level(CONFIG_LESSON_LCD_DC,dc);
13 spi_transaction_t t={.length=n*8,.tx_buffer=data};
14 return spi_device_polling_transmit(s_spi,&t); // 返回后缓冲区才可复用。
15}
16static esp_err_t command(uint8_t cmd,const void *data,size_t n)
17{
18 ESP_RETURN_ON_ERROR(transfer(0,&cmd,1),"lcd","command");
19 return n ? transfer(1,data,n) : ESP_OK;
20}
21esp_err_t lcd_init(void)
22{
23 gpio_config_t g={.pin_bit_mask=1ULL<<CONFIG_LESSON_LCD_DC,.mode=GPIO_MODE_OUTPUT};
24 ESP_RETURN_ON_ERROR(gpio_config(&g),"lcd","DC");
25 spi_bus_config_t bus={.mosi_io_num=CONFIG_LESSON_SPI_MOSI,.miso_io_num=-1,.sclk_io_num=CONFIG_LESSON_SPI_SCLK,
26 .quadwp_io_num=-1,.quadhd_io_num=-1,.max_transfer_sz=480};
27 ESP_RETURN_ON_ERROR(spi_bus_initialize(SPI2_HOST,&bus,SPI_DMA_CH_AUTO),"lcd","bus");
28 spi_device_interface_config_t dev={.clock_speed_hz=10000000,.mode=0,
29 .spics_io_num=CONFIG_LESSON_LCD_CS,.queue_size=1};
30 ESP_RETURN_ON_ERROR(spi_bus_add_device(SPI2_HOST,&dev,&s_spi),"lcd","device");
31 ESP_RETURN_ON_ERROR(exio_output(0x0c00,0x0800),"lcd","power/reset");
32 vTaskDelay(pdMS_TO_TICKS(20));
33 ESP_RETURN_ON_ERROR(exio_output(0x0400,0x0400),"lcd","reset release");
34 vTaskDelay(pdMS_TO_TICKS(120));
35 ESP_RETURN_ON_ERROR(command(0x11,NULL,0),"lcd","sleep out");
36 vTaskDelay(pdMS_TO_TICKS(120));
37 uint8_t format=0x55,orientation=0;
38 ESP_RETURN_ON_ERROR(command(0x3a,&format,1),"lcd","RGB565");
39 ESP_RETURN_ON_ERROR(command(0x36,&orientation,1),"lcd","orientation");
40 ESP_RETURN_ON_ERROR(command(0x21,NULL,0),"lcd","inversion on");
41 return command(0x29,NULL,0);
42}
43esp_err_t lcd_fill(uint16_t rgb565)
44{
45 const uint8_t xs[]={0,0,0,239},ys[]={0,0,1,63}; // 319=0x013f。
46 ESP_RETURN_ON_ERROR(command(0x2a,xs,4),"lcd","x window");
47 ESP_RETURN_ON_ERROR(command(0x2b,ys,4),"lcd","y window");
48 ESP_RETURN_ON_ERROR(command(0x2c,NULL,0),"lcd","RAM write");
49 uint8_t row[480];
50 for(int x=0;x<240;x++) {
51 row[2*x]=rgb565>>8; row[2*x+1]=rgb565&255; // 屏幕要求高字节先发送。
52 }
53 for(int y=0;y<320;y++)
54 ESP_RETURN_ON_ERROR(transfer(1,row,sizeof(row)),"lcd","row");
55 return ESP_OK;
56}
CMake 与中文注释
1# 私有依赖只供本组件源码使用。
2idf_component_register(SRCS "main.c" INCLUDE_DIRS "."
3 PRIV_REQUIRES esp_timer BSP )
每个工程有自己的 project() 名称和 sdkconfig.defaults。VS Code 设置已关闭普通 CMake 自动配置,C/C++ 语言服务读取 build/compile_commands.json;首次构建前文件尚不存在,应先激活 IDF 并构建,而不是手工拼头文件路径。
编译、烧录与观察
1# 激活 EIM 安装的 IDF;每个新终端都需要激活。
2source ~/.espressif/tools/activate_idf_v5.5.5.sh
3# 进入单个示例根目录,不是在 examples 总目录运行。
4cd ~/mycode/esp32-blog/examples/17-spi-lcd
5# 首次配置目标;已有正确配置时不用每次重复 set-target。
6idf.py set-target esp32s3
7# 编译会生成 build/compile_commands.json,供 VS Code 使用。
8idf.py build
9# 把这个值换成实际连接开发板的端口;macOS 通常为 /dev/cu.*。
10PORT=/dev/ttyUSB0
11idf.py -p "$PORT" flash monitor
12# 串口监视退出:Ctrl+]。
预期输出或屏幕内容(示意;数值、日志前缀和启动时间依现场而变):
1lcd: RGB565=0xf800 fill=<实测微秒> us
2lcd: RGB565=0x07e0 fill=<实测微秒> us
故障定位与逐项验收
先检查目标是否是 esp32s3、实际端口与供电,再按本文引脚表核对跳线。编译成功只能说明源码与工具链兼容,无法证明模块型号、物理线路或时序已经正确。
- 红、绿、蓝、白、黑每秒切换一次,全屏范围正确。
- 日志报告每次填充耗时;用逻辑分析仪可看到 D/C 区分命令与数据。
- 解释只写屏为何不需要 MISO,但 SPI 仍具有全双工能力。
遇到通信错误先看错误码和对应模块,禁止简单用“错误时返回 0”掩盖异常。预期日志是阅读程序的参考,验收时应保存自己的现象、日志与接线照片。
与教材和官方实现对照
对照 10_spilcd:保留 SPI2、GPIO11/12/21/40 和 XL9555 屏幕控制;缩小为 ST7789V 240×320 色块教学,不引入字体与多型号自动判断。
本地验证记录与参考
- 2026-10-24:ESP-IDF v5.5.5、目标 ESP32-S3,本机 macOS
idf.py build通过。 - Hugo 草稿页面、文内本地图片和课程入口已检查;
draft: true保留,实板验收后再考虑发布。 - 未执行本课硬件烧录、波形测量或外设现象验收,以上待办不提前勾选。
- ESP-IDF v5.5.5 官方接口文档。
- 教材第 22 章;DNESP32S3 V1.2 原理图第 1、2、3、4 页。