入坑了一个新的玩具 esp32 开发板,买的是最基础款的 esp32-wroom,有点小贵 20 元,还有一个 TFT 的 1.77 英寸显示屏(spi 串口的);
买回来的第一事件就是用 cc 去尝试点亮板子,效果非常好基本一次成功,但是还是需要一些调整;
不过这其中最困难的地方其实是环境的问题,可能是网络的问题,使用 kiro(可以理解是 vs code) 安装的 ESP-IDE 插件 实在是太难搞了;然后我其实了解了一下 esp32 是有两种开发框架的,除了 ESP-IDE 好像还可以使用 Arduino框架 ;这个我就不多说了,没有使用过没法子分享经验,我之前使用过 keil4 所以觉得ESP-IDE 可能会好些;
下面是我后面才查到的一些资料,也没机会尝试了,留给有需要的人吧(真无语了):
- 选项1:ESP-IDF Installation Manager (EIM) - 支持离线安装包,整个环境打包成单一压缩文件,无需联网。 下载地址:https://docs.espressif.com/projects/idf-im-ui/en/latest/offline_installation.html
- 选项2:Espressif-IDE All-in-One Windows Installer 集成 IDE + ESP-IDF + 所有工具链的离线一键安装包,最适合国内网络环境。 下载地址:https://github.com/espressif/idf-eclipse-plugin
测试的效果还行,如下图使用了一张图片,很快完整整个效果估计也就半个小时吧,并且之前在调整显示文字的问题,调整好了之后显示这个图片,就 10 分钟;不过根据我的观察似乎这个用的 token 比我之前问前端和 python 的问题费的 token 多多了,可能是这个上面问的问题太泛了导致的吧,肉眼可见的增加了消耗;
好了接下来就是跟着 cc 老师去了解这个效果实现的一些细节了(虽然 cc 已经实现了比较理想的效果,但是我觉得一些细节或者说一些大方向基本原理我们还是要了解一些的,跟着了解了解总是没有错的);
硬件和引脚的连接
/** 硬件连接:* ESP32 GPIO18 → SCK (SPI时钟)* ESP32 GPIO23 → MOSI/SDA (SPI数据输出)* ESP32 GPIO15 → CS (片选,低有效)* ESP32 GPIO2 → DC (数据/命令选择:0=命令, 1=数据)* ESP32 GPIO4 → RST (硬件复位,低有效)* 3.3V → VCC / BL (电源 / 背光)* GND → GND** 驱动方式:ESP-IDF SPI Master(同步轮询模式)* 屏幕型号:GMT177-10 v4.1(驱动IC: ST7735S,分辨率: 128×160,颜色: RGB565)*/
Q:这里有个疑问,这些接线方式是否是固定的,是否可以改变?
可一个改变,SCK 和 MOSI 是比较固定的,VSPI 会默认;但是 DC、CS、RST 可以接其他的引脚;
简单了解一些需要的相关知识
大佬不要嘲笑,本以为可以简单得了解学习一下 spi 显示屏的驱动逻辑,但是在了解得时候发现有很多基础知识需要先了解一下(果然隔行如隔山),太久没有接触过了,都有点忘记了:
- 位(bit):最小的单位,只有 0 和 1,可以理解为 通电/不通电;
- 字节(Byte):把 8 个 bit 位打包成 1 个字节(Byte),例如:11110000
还有需要了解的就是 十六进制,例如 uint8_t cmd = 0x2C; 0x2C 是一个十六进制数,对应的是 0010 1100 恰好是一个字节 8 位;
浅显的理解一下 SPI 的驱动方式
1、 初始化总线(一次性,配引脚)
这里的 spi_bus_config_t 是一个固定的结构体类型,同样的固定结构体还有 spi_device_interface_config_t 和 spi_transaction_t 一个是 设备配置、一个是单次传输配置;
LCD_HOST 定义的 SPI2_HOST , #define LCD_HOST SPI2_HOST
/* --- SPI 总线初始化 --- */spi_bus_config_t bus = {.mosi_io_num = PIN_MOSI,.sclk_io_num = PIN_SCK,.miso_io_num = -1, /* 不需要 MISO(只写) */.quadwp_io_num = -1,.quadhd_io_num = -1,.max_transfer_sz = sizeof(s_char_buf), /* 最大单次传输:16×16×2=512 字节 */};ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &bus, SPI_DMA_CH_AUTO));
2、 注册设备(一次性,配时钟/回调)
这里的就是 spi_device_interface_config_t 也是固定的结构类型,参数也基本比较固定,暂时也没有想深入探究的想法
/* --- 添加 SPI 设备(ST7735S) --- */spi_device_interface_config_t dev = {.clock_speed_hz = 20000000, /* 20 MHz SPI 时钟 */.mode = 0, /* SPI Mode 0(CPOL=0, CPHA=0) */.spics_io_num = PIN_CS, /* CS 引脚(驱动自动控制) */.queue_size = 1, /* 队列深度 1(同步模式不需要更大) */.pre_cb = spi_pre_cb, /* 每次事务前设置 DC 引脚 */};ESP_ERROR_CHECK(spi_bus_add_device(LCD_HOST, &dev, &s_spi));
3、 发送数据(每次传输)
这是我看的最认真的地方,首先我想先贴一下初始化函数 st7735_init,这里的配置基本也是固定的;
/* =====================================================================* ST7735S 初始化序列** ST7735S 是一款支持 SPI 接口的 TFT LCD 控制器。* 初始化步骤:* 1. 硬件复位(RST 拉低后拉高)* 2. 软件复位(SWRESET,0x01)* 3. 退出睡眠模式(SLPOUT,0x11)* 4. 设置帧率控制(FRMCTR1~3)* 5. 设置电源控制(PWCTR1~5)* 6. 设置内存访问方向(MADCTL,0x36):* - MX(bit6)=1:列地址从右到左扫描(水平镜像)* - MY(bit7)=1:行地址从下到上扫描(垂直镜像)* - BGR(bit3)=0:RGB 颜色顺序* 7. 设置颜色格式(COLMOD,0x3A):0x05 = 16位 RGB565* 8. 设置 Gamma 曲线* 9. 关闭颜色反转(INVOFF,0x20)* 10. 开启正常显示(NORON,0x13)* 11. 开启显示(DISPON,0x29)* ===================================================================== */staticvoidst7735_init(void){/* 硬件复位:RST 低电平 50ms 后拉高,等待 150ms 稳定 */gpio_set_level(PIN_RST, 0); vTaskDelay(pdMS_TO_TICKS(50));gpio_set_level(PIN_RST, 1); vTaskDelay(pdMS_TO_TICKS(150));lcd_cmd(0x01); vTaskDelay(pdMS_TO_TICKS(150)); /* SWRESET 软件复位 */lcd_cmd(0x11); vTaskDelay(pdMS_TO_TICKS(150)); /* SLPOUT 退出睡眠 *//* 帧率控制:正常模式 / 空闲模式 / 部分模式 */lcd_reg(0xB1, (uint8_t[]){0x01,0x2C,0x2D}, 3);lcd_reg(0xB2, (uint8_t[]){0x01,0x2C,0x2D}, 3);lcd_reg(0xB3, (uint8_t[]){0x01,0x2C,0x2D,0x01,0x2C,0x2D}, 6);lcd_reg(0xB4, (uint8_t[]){0x07}, 1); /* INVCTR 显示反转控制 *//* 电源控制寄存器 */lcd_reg(0xC0, (uint8_t[]){0xA2,0x02,0x84}, 3);lcd_reg(0xC1, (uint8_t[]){0xC5}, 1);lcd_reg(0xC2, (uint8_t[]){0x0A,0x00}, 2);lcd_reg(0xC3, (uint8_t[]){0x8A,0x2A}, 2);lcd_reg(0xC4, (uint8_t[]){0x8A,0xEE}, 2);lcd_reg(0xC5, (uint8_t[]){0x0E}, 1); /* VMCTR1 VCOM 控制 */lcd_cmd(0x20); /* INVOFF 关闭颜色反转 *//** MADCTL (0x36) — 内存访问控制,决定屏幕方向* 0xC0 = MY(1) | MX(1):屏幕 180° 旋转(补偿本模块的物理安装方向)* 注:字符渲染中通过位反序(0x01<
* 0x00 正常方向* 0x60 旋转90°* 0xC0 旋转180°(当前)* 0xA0 旋转270°* 0x3A COLMOD — 颜色格式*/lcd_reg(0x36, (uint8_t[]){0xC0}, 1);lcd_reg(0x3A, (uint8_t[]){0x05}, 1); /* COLMOD 16位色 RGB565 *//* Gamma 正极性校正曲线 */lcd_reg(0xE0,(uint8_t[]){0x02,0x1C,0x07,0x12,0x37,0x32,0x29,0x2D,0x29,0x25,0x2B,0x39,0x00,0x01,0x03,0x10},16);/* Gamma 负极性校正曲线 */lcd_reg(0xE1,(uint8_t[]){0x03,0x1D,0x07,0x06,0x2E,0x2C,0x29,0x2D,0x2E,0x2E,0x37,0x3F,0x00,0x00,0x02,0x10},16);lcd_cmd(0x13); vTaskDelay(pdMS_TO_TICKS(10)); /* NORON 正常显示模式 */lcd_cmd(0x29); vTaskDelay(pdMS_TO_TICKS(100)); /* DISPON 开启显示 */}
我主要想记录一下的,是如下图的一个封装方法吧,觉得很巧妙;
首先在看的时候,一直没有在初始化函数 st7735_init 中找到关于 DC 引脚的定义,后来看见被做了一个简单的封装,感觉真的很巧妙(可能是我太菜了);
lcd_reg 中封装了 lcd_cmd 和 lcd_dat ,而 lcd_cmd 和 lcd_dat 分别对应着 DC = 0 的传输命令,和 DC = 1 的传输数据;而 lcd_cmd 和 lcd_dat 又都主要封装的是 spi_send , 在 spi_send 中就用到了 spi_transaction_t 结构体类型用来单次传输配置,三个步骤对应着三个结构体;
草草总结一下
配置完后,我们看一下整个流程:
调用 lcd_cmd/lcd_dat│├─ 设置 t.user = 0 或 1├─ 调用 polling_transmit│ ││ ├─ 触发 pre_cb → gpio_set_level(DC, t.user)│ ├─ CS 自动拉低│ ├─ SCK 输出时钟,MOSI 逐位发送│ └─ CS 自动拉高└─ 函数返回(传输完成)
以及发送图片的函数:
/*** @brief 绘制全屏图片** image_data 是 const 数组(存于 Flash),DMA 无法直接访问 Flash。* 因此分块拷贝到 DRAM 缓冲区(chunk)后再发送,每块 256 像素(512 字节)。*/staticvoiddraw_image(int x, int y){static uint16_t chunk[256];set_window(x, y, x + IMAGE_W - 1, y + IMAGE_H - 1);int total = IMAGE_W * IMAGE_H;for (int i = 0; i < total; i += 256) {int n = (i + 256 > total) ? total - i : 256;memcpy(chunk, image_data + i, n * 2);lcd_dat(chunk, n * 2);}}
image_data 是图片数据,一个数组如下图,然后 image_data + i 是从 第 0 元素地址开始向后每次加 256 个元素
这里就是我初步了解的关于 SPI 显示图像的基本方式,很久以前玩过 stm32, 当时有个显示器发送的数据都是按照时序挨个引脚拉的;但是这里的一些都被函数封装好了,例如 polling_transmit 里面就完成 CS 和 MOSI 的操作;
本来想着去了解的更细致一些的,甚至找了一下 ST7735S 的参考手册,但是太难看懂了,链接方下面了,留给以后的自己或者想看的人去看来了;
真的挺难也挺有意思的,上面的内容虽然有点凌乱和浅显但是这也是现在的我尽力了;
ST7735S 的参考手册:
- https://www.orientdisplay.com/wp-content/uploads/2018/08/ST7735S.pdf


