Skip to content

2.4 TFT LCD 显示屏 ​

这节要搞懂:把一块屏幕点亮,显示文字和数字。 需要基础:2.3(SPI 部分)

一、什么是 TFT LCD ​

TFT LCD(Thin Film Transistor Liquid Crystal Display,薄膜晶体管液晶显示屏)是一种彩色液晶屏。每个像素由一个薄膜晶体管独立控制,因此响应快、色彩好。

一块屏幕模组由三部分组成:

部分作用
液晶面板真正显示的那一层
驱动 IC一颗小芯片,负责接收数据并驱动面板
背光后面的灯,不亮就什么都看不见

你的代码打交道的对象是驱动 IC,常见型号:ST7789、ILI9341、GC9A01。

2.8 寸屏有两种驱动 IC,别买错

同样是 2.8 寸 240×320 的模块,市面上有两种芯片:

驱动 IC说明
ST7789本课程用这个,ESP-IDF 自带驱动
ILI9341也很常见,驱动源码要换成对应型号的

二、买哪块屏 ​

项目使用 2.8 寸 ST7789,240×320 分辨率,SPI 接口。

三、屏幕用了哪些引脚 ​

屏幕是焊在板子上的,不用自己接线。要记住的是每根信号线走的是哪个 GPIO —— 代码里的宏就是按这张表填的。

屏幕上的丝印ESP32-S3 引脚作用
VCC3.3V供电
GNDGND地
SCL / SCKGPIO12SPI 时钟(FSPI 默认脚)
SDA / MOSIGPIO11SPI 数据(FSPI 默认脚)
CSGPIO10片选(FSPI 默认脚)
DC / RSGPIO5⭐ 关键脚,见下
RES / RSTGPIO4复位
BLK / LEDGPIO17(经三极管驱动)背光

DC 这根线是干嘛的

SPI 传过去的都是一串数字,屏幕怎么知道这是命令("清屏!")还是数据("这个点是红色")?

就靠 DC 这根线的电平:

  • DC = 低电平 → 接下来传的是命令
  • DC = 高电平 → 接下来传的是数据

这是 SPI 屏幕的标配,代码里 LCD_DC_Clr() / LCD_DC_Set() 干的就是这件事。

四、不用自己写驱动 ​

ST7789 的初始化要发几十条命令,每条是什么意思都写在几百页的手册里。这部分不用自己啃。

本课程用的是一套移植好的驱动,加上两个数据文件:

文件管什么
lcd_init.c / lcd_init.h底层:配置引脚、发命令发数据、开机时的初始化序列、设置绘图窗口
lcd.c / lcd.h上层:画点画线、填色块、显示字符、显示汉字、显示图片
lcdfont.h字库:汉字和 ASCII 字符的点阵数据
pic.h图片:图片取完模之后生成的数组

后两个是取模软件生成的,不用手写,下面的操作过程会讲怎么做。

平时写业务代码,只需要调 lcd.h 里的那些函数:

c
LCD_Init();                                     // 初始化屏幕
LCD_Fill(0, 0, LCD_W, LCD_H, WHITE);            // 填一块颜色
LCD_ShowString(0, 40, (const u8 *)"hello", RED, WHITE, 16, 0);   // 显示字符串
LCD_ShowIntNum(48, 40, 240, 3, RED, WHITE, 16); // 显示数字
LCD_ShowPicture(0, 160, 223, 73, gImage_test2); // 显示图片

lcd_init.c 里那几十条初始化命令,看不懂也没关系,它只在开机时跑一次。

底层其实只有三个动作

整个驱动翻来覆去就在干三件事:

函数干什么
LCD_WR_REG(cmd)发命令:把 DC 拉低,送一个字节,再把 DC 拉回高
LCD_WR_DATA(color)发数据:DC 保持高,送两个字节(一个 RGB565 像素)
LCD_Address_Set(x1,y1,x2,y2)划定范围:告诉屏幕"接下来的数据往这个矩形里填"

画图就是:先 LCD_Address_Set 圈一块地,然后一个劲往里 LCD_WR_DATA,屏幕会自动从左上角往右下角按顺序填。

想换屏幕方向改这里

lcd_init.h 里的 USE_HORIZONTAL 控制屏幕方向(横屏 / 竖屏 / 翻转),它改的是 ST7789 的 0x36 寄存器。

改了这个,LCD_W 和 LCD_H 也要跟着对调。

五、什么是 RGB565 ​

RGB565 是一种颜色编码格式:用 16 位(2 字节) 表示一个像素,其中红色占 5 位、绿色占 6 位、蓝色占 5 位。

绿色多一位,是因为人眼对绿色最敏感。

不用自己算,用这个宏:

c
#define RGB565(r, g, b)  ((((r) & 0xF8) << 8) | (((g) & 0xFC) << 3) | ((b) >> 3))

uint16_t red   = RGB565(255, 0, 0);
uint16_t white = RGB565(255, 255, 255);
uint16_t black = RGB565(0, 0, 0);

六、这套驱动用的是"软件模拟 SPI" ⭐ ​

打开 lcd_init.c,发数据的函数长这样:

c
void LCD_Writ_Bus(u8 dat)
{
    u8 i;
    LCD_CS_Clr();                    // 片选拉低:我要跟你说话了
    for (i = 0; i < 8; i++) {
        LCD_SCLK_Clr();              // 时钟拉低
        if (dat & 0x80) {            // 取出最高位
            LCD_MOSI_Set();          //   是 1 → 数据线拉高
        } else {
            LCD_MOSI_Clr();          //   是 0 → 数据线拉低
        }
        LCD_SCLK_Set();              // 时钟拉高 ← 屏幕在这一刻读走这一位
        dat <<= 1;                   // 左移一位,准备发下一位
    }
    LCD_CS_Set();                    // 片选拉高:说完了
}

这里面没有用到芯片的 SPI 外设,全是在用 gpio_set_level() 手动翻转引脚 —— 这叫软件模拟 SPI(也叫 bit-bang,"一位一位地敲")。

这正好把 2.3 讲的波形变成了代码

对照 2.3 的 SPI 波形 看这段循环:

  • LCD_SCLK_Clr() → 时钟低电平的时候把数据准备好
  • LCD_SCLK_Set() → 时钟上升沿,接收方在这一刻采样

这就是 2.3 里说的 Mode 0:上升沿采样。别人画的波形图,在这里就是这七行代码。

软件模拟的代价是慢,好处是:

软件模拟 SPI硬件 SPI 外设
速度慢(每一位都要 CPU 亲自翻)快(外设自己发,CPU 可以去干别的)
引脚任意 GPIO 都行走特定引脚才能跑满速
移植换芯片只要改几个宏要重写整套配置
代码一眼看懂要配总线、配设备、配 DMA

对时钟、天气这类画面变化不频繁的项目,软件模拟完全够用,而且学习价值更高 —— 你能亲眼看到协议是怎么一位一位拼出来的。

所以串口打印的是普通 GPIO 日志

烧录后串口会打印 gpio: GPIO[11]| InputEn: 0| OutputEn: 1| ...,这几个脚是被当成普通输出口配置的,不是 SPI 外设的脚。看到这个别以为配错了 —— 这套驱动本来就不用 SPI 外设。

七、刷屏会卡怎么办 ​

先算一笔账。屏幕是 240×320,一个像素 16 位:

整屏像素数:240 × 320 = 76,800 个
每个像素要发 16 位,每一位要翻一次时钟
整屏时钟翻转次数:76,800 × 16 = 1,228,800 次

一百多万次 GPIO 翻转全靠 CPU 一次次执行,整屏刷一次是几百毫秒级别的事 —— 慢到你能用眼睛看见它从上往下刷。

解决办法:别刷整屏,只刷变化的地方。

时钟界面上其实只有"秒"在变:

c
// ❌ 每秒整屏重画一次 —— 肉眼可见地闪
LCD_Fill(0, 0, LCD_W, LCD_H, WHITE);
LCD_ShowIntNum(100, 50, second, 2, RED, WHITE, 16);

// ✅ 只把秒数那两个字符的位置重画一遍 —— 瞬间完成
LCD_ShowIntNum(100, 50, second, 2, RED, WHITE, 16);

两个字符是 16 × 16 × 2 = 512 个像素,只有整屏的万分之七。

驱动里为什么到处塞 vTaskDelay(1)

翻开 lcd.c,LCD_Fill、LCD_ShowChar、LCD_ShowPicture 的循环里都有一句:

c
vTaskDelay(pdMS_TO_TICKS(1));

这是在主动让出 CPU。整屏刷新要占用 CPU 好几百毫秒,如果一口气跑完不撒手:

  • 别的任务(读传感器、连 WiFi)全都轮不上
  • 看门狗会认为系统卡死,直接把板子重启

每画完一行让一下,这些问题就都没有了。这就是第一章 1.6 调度器 讲的"协作"—— 长任务要主动给别人留出机会。

代价是变慢了:LCD_Fill 刷整屏 320 行,光这些 delay 就是 320 毫秒起步。所以更要遵守"只刷变化区域"。

本节要点 ​

  • TFT LCD = 面板 + 驱动 IC + 背光,代码对接的是驱动 IC
  • 本课程用 2.8 寸 240×320、ST7789、SPI 接口的屏
  • ⭐ DC 脚区分命令和数据,别接错
  • 驱动不用自己写,把现成的 lcd_init.c / lcd.c 拷进工程,在 CMakeLists.txt 里登记一下就能用
  • RGB565 = 16 位表示一个像素
  • ⭐ 本课程的驱动是软件模拟 SPI,用 GPIO 一位一位翻出来的,没用 SPI 外设
  • 卡顿靠只刷变化区域解决

操作过程 ​

目标:在屏幕上显示汉字、英文和数字、以及一张图片。

这一节要用到两个取模软件,先下载好:

两个都是免安装的小工具,解压出来直接双击运行。

第 1 步:建工程 ​

建工程流程见 2.2 第 2 步

Project Name 填 SPI(叫什么都行),Target 选 esp32s3。

第 2 步:把驱动源码放进工程 ​

ST7789 的驱动(lcd_init.c / lcd_init.h / lcd.c / lcd.h)不用自己写,拷现成的。

先在工程根目录下新建一个 driver 文件夹,把这四个文件放进去:

在工程根目录(和 main 平级)新建 driver 文件夹,驱动源码放这里
在工程根目录(和 main 平级)新建 driver 文件夹,驱动源码放这里

为什么不直接丢进 main 文件夹

丢进 main 也能编译。单独放一个 driver 文件夹,是为了把"别人写的驱动"和"自己写的业务代码"分开,以后换屏、换芯片时一眼就知道该动哪些文件。

第 3 步:告诉编译器去哪找这些文件 ​

新建的文件夹编译器并不知道,要在 main/CMakeLists.txt 里登记一下。

打开 main 文件夹下的 CMakeLists.txt(注意不是根目录那个)
打开 main 文件夹下的 CMakeLists.txt(注意不是根目录那个)

改成这样:

把 driver 下的 .c 文件加进 SRCS,把 driver 文件夹加进 INCLUDE_DIRS
把 driver 下的 .c 文件加进 SRCS,把 driver 文件夹加进 INCLUDE_DIRS
cmake
idf_component_register(SRCS "main.c" "../driver/lcd.c" "../driver/lcd_init.c"
                       INCLUDE_DIRS "." "../driver")

两处各管一件事:

写在哪管什么不写会怎样
SRCS要编译哪些 .c 文件报 undefined reference to LCD_Init 之类的链接错误
INCLUDE_DIRS去哪些文件夹里找 .h报 lcd.h: No such file or directory

.. 表示上一级目录 —— 因为这个 CMakeLists.txt 在 main/ 里面,要往上退一层才能看到 driver/。

第 4 步:用 PCtoLCD2002 取汉字字模 ​

屏幕不认识"周"这个字,它只认识一堆点亮/不亮的点。取模就是把汉字翻译成这样一串点阵数据。

打开 PCtoLCD2002,先点**「模式」→「字符模式」,再点「选项」**,按下图设置:

字模选项:阴码、逐列式、逆向(低位在前)、十六进制、C51 格式
字模选项:阴码、逐列式、逆向(低位在前)、十六进制、C51 格式

四个关键项,必须和驱动对上,否则显示出来是乱的:

选项设成意思
点阵格式阴码1 表示这个点要亮
取模方式逐列式一列一列地扫
取模走向逆向(低位在前)每个字节里,最低位对应最上面那个点
自定义格式C51 格式生成的数据直接能粘进 C 代码

设好之后点确定回到主界面,再调三个地方,然后输入汉字、点**「生成字模」**:

主界面:选字体、设字宽字高,在下方输入框打字,点「生成字模」,数据出现在最下面
主界面:选字体、设字宽字高,在下方输入框打字,点「生成字模」,数据出现在最下面
位置设成说明
字体宋体笔画规整,小尺寸下最清楚
字宽 / 字高16 / 16要和代码里 LCD_ShowChinese(..., 16, 0) 的 16 对上
等比缩放勾上保证字不变形

生成的数据就在最下面那个框里,把它拷进驱动的字库文件 lcdfont.h 里。

字宽字高必须和代码里的字号一致

驱动是按字号去不同的字库数组里找的:

c
if (sizey == 12)      LCD_ShowChinese12x12(...);   // 查 tfont12
else if (sizey == 16) LCD_ShowChinese16x16(...);   // 查 tfont16
else if (sizey == 24) LCD_ShowChinese24x24(...);   // 查 tfont24
else if (sizey == 32) LCD_ShowChinese32x32(...);   // 查 tfont32

取模时设 16×16,数据就得放进 tfont16,代码里也得写 16。放错数组或者字号写错,结果都是什么都不显示。

生成出来的字库长这样,每个汉字是一条记录:

c
typedef struct {
    unsigned char Index[2];    // 索引:这个字的编码(2 字节)
    unsigned char Msk[32];     // 点阵:16×16 的点,哪些亮哪些不亮
} typFNT_GB16;

const typFNT_GB16 tfont16[] = {
    { {0xD6,0xDC}, {0x00,0x00,0xFC,0x3F, ...} },    // 周
    { {0xD2,0xBB}, {0x00,0x00,0x00,0x00, ...} },    // 一
    ...
};

为什么代码里写 0xD6, 0xDC,不直接写"周" ⭐ ​

这是这一节最容易卡住的地方,值得单独讲清楚。

先看驱动是怎么显示一个汉字的(lcd.c 里的 LCD_ShowChinese16x16):

c
for (k = 0; k < HZnum; k++) {                       // 把字库从头翻到尾
    if ((tfont16[k].Index[0] == *(s)) &&            // 比对第 1 个字节
        (tfont16[k].Index[1] == *(s + 1))) {        // 比对第 2 个字节
        ...                                         // 对上了 → 把 Msk 里的点画出来
    }
}

它干的事就是"拿编码当钥匙,去字库里对号入座":你传进去两个字节,它挨个比对字库里每条记录的 Index,哪条对上了就画哪条的点阵。对不上就什么都不画。

所以问题变成了:这把"钥匙"该用哪种编码?

答案是 GBK,原因有三条:

① 字库里的 Index 就是 GBK 码

PCtoLCD2002 是 2002 年的老软件,跑在简体中文 Windows 上,它输出的索引字节就是 GBK 机内码(GBK 是 GB2312 的扩展)。字库是它生成的,钥匙当然得用同一套编码。

② GBK 一个汉字固定 2 字节,结构上正好对得上

看驱动里那几个数字:

代码说明
unsigned char Index[2]索引只留了 2 个字节
tfont16[k].Index[0]、Index[1]只比对 2 个字节
s += 2;(LCD_ShowChinese 里)每显示完一个字,指针往后跳 2 个字节

整套驱动都是按"一个汉字 = 2 字节"写死的。 GBK 正好是定长 2 字节,严丝合缝。

③ UTF-8 装不进去

UTF-8 里一个汉字是 3 个字节:

"周"   GBK    →  D6 DC           (2 字节)
"周"   UTF-8  →  E5 91 A8        (3 字节)

3 个字节塞不进 Index[2];就算塞得下,s += 2 也会每个字错位一个字节,越往后越乱。

所以千万别直接写中文字符串

c
// ❌ 这样写,屏幕上什么都不会出现
LCD_ShowChinese(0, 0, (u8 *)"周一天气", RED, WHITE, 16, 0);

为什么:ESP-IDF 的 .c 文件是按 UTF-8 保存的,编译器会把 "周" 编译成 E5 91 A8 三个字节。驱动拿 E5 91 这两个字节去字库里找,而字库里存的是 D6 DC —— 永远对不上,循环走完什么都没画,屏幕一片空白。

不会报错,不会崩溃,就是不显示。所以必须手写十六进制:

c
// ✅ 直接写 GBK 编码,用注释标明是哪个字
static u8 weather_text[] = {
    0xD6, 0xDC,  /* zhou 周 */
    0xD2, 0xBB,  /* yi   一 */
    0xCC, 0xEC,  /* tian 天 */
    0xC6, 0xF8,  /* qi   气 */
    0x00
};

编码用在线工具查就行,比如 GBK 编码查询器:

输入「周」,机内码一栏给出 D6DC —— 正好是代码里的 0xD6, 0xDC
输入「周」,机内码一栏给出 D6DC —— 正好是代码里的 0xD6, 0xDC

网上的 STM32 例程直接写中文,为什么它能显示 ⭐

因为源码文件的保存编码不一样,跟芯片没关系。

这套驱动最早是从 STM32 移植过来的。Keil 工程在简体中文 Windows 上默认按 ANSI(也就是 GBK) 保存,所以:

源码文件编码"周" 编译进固件后是能否匹配字库的 D6 DC
STM32 / KeilGBKD6 DC✅ 对得上
ESP-IDF / VS CodeUTF-8E5 91 A8❌ 对不上

编译器不会做转换,源文件里是什么字节,字符串里就是什么字节。所以同一段代码换个编辑器保存一下,显示就坏了 —— 这也是它难查的原因。

结论:别去依赖源码文件的编码。 汉字直接写成 GBK 字节数组,字库的索引也一样:

c
{ {0xD6,0xDC}, {...点阵...} }     // ✅ 写字节,跟文件编码无关
{ "周",        {...点阵...} }     // ❌ 写汉字,换个编码保存就废了

这样不管文件存成 UTF-8 还是 GBK,都能正常显示。

也可以让编译器帮你转(不推荐)

GCC 有两个参数可以指定"源文件是什么编码"和"字符串在固件里按什么编码存":

-finput-charset=UTF-8 -fexec-charset=GBK

加上之后,UTF-8 源文件里的 "周" 会被编译器转成 D6 DC,就能直接写中文了。

但本课程不用这个办法,因为它是整个工程全局生效的:所有字符串都会变成 GBK,包括 printf 打到串口的日志 —— 而串口助手一般按 UTF-8 解码,中文日志立刻全变乱码。

为了少打几个十六进制,把日志搞乱,不划算。

为什么英文和数字就不用管这些

c
LCD_ShowString(0, 40, (const u8 *)"LCD_W:", RED, WHITE, 16, 0);   // 直接写就行

因为英文走的是另一条路。LCD_ShowChar 里是这么算的:

c
num = num - ' ';                  // 用 ASCII 码减去空格(0x20),直接算出下标
temp = ascii_1608[num][i];        // 按下标取点阵,不用查找

ASCII 字符在 GBK 和 UTF-8 里是同一个字节(都是 0x00~0x7F 原样保留),所以不管文件存成什么编码,'L' 永远是 0x4C。

这也是为什么英文显示又快又不出错 —— 不用遍历字库,直接算下标。汉字太多没法这么干,只能老老实实查表。

一个字占多少空间

一个 16×16 的汉字,点阵是 16 × 16 ÷ 8 = 32 字节,加上 2 字节索引,一个字 34 字节。

汉字字库是编译进固件的,所以:用到哪几个字就取哪几个,别把整个 GB2312 字库(六千多字,二十多万字节)塞进去。

这也是为什么示例里只取了"周一天气"这几个字。

第 5 步:用 Img2Lcd 取图片模 ​

图片同理,也要先变成数组。打开 Image2Lcd,把图片拖进去,按下图设置:

Image2Lcd 设置:C 语言数组、水平扫描、16 位真彩色、R5G6B5
Image2Lcd 设置:C 语言数组、水平扫描、16 位真彩色、R5G6B5
选项设成为什么
输出数据类型C 语言数组(*.c)生成的是能直接用的 .c 文件
扫描模式水平扫描从左到右、从上到下,和屏幕刷新顺序一致
输出灰度16 位真彩色就是 第五节 讲的 RGB565
颜色位数R:5bits, G:6bits, B:5bits同上,别选成 R5G5B5
最大宽度和高度填你的图片实际尺寸超过屏幕尺寸会显示不全
包含图像头数据⭐ 不要勾见下面

"包含图像头数据"必须取消勾选

勾上之后,Image2Lcd 会在数组开头塞几个字节,记录图片的宽、高、颜色格式。

但驱动里的 LCD_ShowPicture 是从 pic[0] 就当像素数据用的:

c
LCD_Address_Set(x, y, x + length - 1, y + width - 1);
for (...) {
    LCD_WR_DATA8(pic[k * 2]);        // ← 直接从第 0 个字节开始读
    LCD_WR_DATA8(pic[k * 2 + 1]);
    k++;
}

它不会跳过头部,宽高也是你在调用时手动传进去的(223, 73),根本不看头部里写了什么。

所以头部如果存在,会被当成前几个像素画出来,整张图往后错位。

点**「保存」**生成 .c 文件,里面是一个 gImage_xxx 数组。把它放进工程(比如做成 pic.h),代码里 #include 就能用。

图片别太大

一张 240×320 的全屏图,按 RGB565 算就是 240 × 320 × 2 = 153600 字节 ≈ 150KB。

这些数据是编译进固件里的,图放多了固件会迅速变大。只放必要的小图标。

第 6 步:写代码 ​

c
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "lcd_init.h"
#include "lcd.h"
#include "pic.h"

// "周一天气"四个字的 GBK 机内码,一个汉字两字节
static u8 weather_text[] = {
    0xD6, 0xDC,  /* zhou */
    0xD2, 0xBB,  /* yi */
    0xCC, 0xEC,  /* tian */
    0xC6, 0xF8,  /* qi */
    0x00
};

// 这次要显示上面数组里的第几个字(下标从 0 开始)
static u8 show_zhou_tian[] = {0, 1, 2};

void app_main(void)
{
    LCD_Init();                                    // 初始化屏幕
    LCD_Fill(0, 0, LCD_W, LCD_H, WHITE);           // 整屏刷白,相当于清屏

    // 显示汉字:从 (0,0) 开始,按 show_zhou_tian 挑 2 个字,红字白底,16 号字
    LCD_ShowChineseByIndexes(0, 0, weather_text, show_zhou_tian, 2, RED, WHITE, 16, 0);

    // 显示英文和数字:把屏幕的宽高打在屏上
    LCD_ShowString(0, 40, (const u8 *)"LCD_W:", RED, WHITE, 16, 0);
    LCD_ShowIntNum(48, 40, LCD_W, 3, RED, WHITE, 16);
    LCD_ShowString(80, 40, (const u8 *)"LCD_H:", RED, WHITE, 16, 0);
    LCD_ShowIntNum(128, 40, LCD_H, 3, RED, WHITE, 16);

    LCD_ShowString(0, 70, (const u8 *)"Increaseing Nun:", RED, WHITE, 16, 0);

    // 显示图片:左上角 (0,160),宽 223、高 73
    LCD_ShowPicture(0, 160, 223, 73, gImage_test2);
}

几个常用函数的参数:

函数参数含义
LCD_Fill(x1, y1, x2, y2, color)把这个矩形填成某个颜色
LCD_ShowString(x, y, str, 字色, 背景色, 字号, 是否叠加)显示一串英文/数字
LCD_ShowIntNum(x, y, num, 位数, 字色, 背景色, 字号)显示一个整数
LCD_ShowFloatNum1(x, y, num, 位数, 字色, 背景色, 字号)显示一个小数(保留两位)
LCD_ShowPicture(x, y, 宽, 高, 数组)在 (x,y) 画一张取好模的图
LCD_DrawLine / LCD_DrawRectangle / Draw_Circle画线、画矩形框、画圆

"是否叠加"那个参数(mode)

最后那个 0 / 1 决定背景怎么处理:

  • 0 = 不叠加:把字的背景整块刷成 背景色,会盖掉原来的内容
  • 1 = 叠加:只画笔画的那些点,背景保持原样

在纯色背景上写字用 0(快,而且能自动擦掉上一次的内容);想把字压在图片上用 1。

LCD_ShowChineseByIndexes 是怎么挑字的

c
static u8 weather_text[]   = {0xD6,0xDC, 0xD2,0xBB, 0xCC,0xEC, 0xC6,0xF8, 0x00};
static u8 show_zhou_tian[] = {0, 1, 2};

LCD_ShowChineseByIndexes(0, 0, weather_text, show_zhou_tian, 2, RED, WHITE, 16, 0);

weather_text 是字池(周、一、天、气,下标 0~3),show_zhou_tian 是这次要显示哪几个,2 是显示几个。

所以这一句显示的是下标 0 和 1,也就是"周一"。数组里第三个元素 2 因为 count 只写了 2,这次没用上 —— 想显示"周一天",把 count 改成 3 就行。

这么设计的好处是:字库和字池只存一份,想显示什么组合就换一组下标。天气时钟上"晴 / 阴 / 雨 / 雪"就是这么切换的。

坐标原点在左上角

(0, 0) 是屏幕左上角,x 往右增大、y 往下增大 —— 和数学课上的坐标系是上下颠倒的。

所以 LCD_ShowPicture(0, 160, ...) 是把图画在屏幕偏下的位置。

第 7 步:编译烧录 ​

操作流程见 2.2 第 4 步

在 VS Code 底部状态栏依次点:编译 → 烧录(或者直接点"编译、烧录并监视")。

烧录方式、串口、芯片型号第一次设好就不用再动了。

第 8 步:应该看到什么 ​

屏幕(白底):

  • 左上角红色汉字
  • 下面一行 LCD_W:240 LCD_H:320
  • 再下面 Increaseing Nun:
  • 屏幕中下部一张图片

串口会打印出被配置成输出的那几个引脚,正好对应上面那张引脚表:

gpio: GPIO[4]|  InputEn: 0| OutputEn: 1| ...      ← RST
gpio: GPIO[5]|  InputEn: 0| OutputEn: 1| ...      ← DC
gpio: GPIO[10]| InputEn: 0| OutputEn: 1| ...      ← CS
gpio: GPIO[11]| InputEn: 0| OutputEn: 1| ...      ← SDA(MOSI)
gpio: GPIO[12]| InputEn: 0| OutputEn: 1| ...      ← SCL
gpio: GPIO[17]| InputEn: 0| OutputEn: 1| ...      ← 背光

屏幕一片白、什么都没有就别往下走

说明 LCD_Fill 生效了但后面的显示没出来,先确认字库和图片数组有没有正确加进工程。

动手试试 ​

  1. 把屏幕点亮,全屏刷成红色。
  2. 依次刷红、绿、蓝,各停 1 秒。
  3. 只刷屏幕中间一个小方块,对比一下速度差多少。