运用 U8G2 与 TFT_eSPI 玩转 UINIO-Monitor 显示屏

UINIO-Monitor 同时拼接有 128×64 分辨率 SSD1315 驱动的 0.96 英寸 OLED 显示屏,160×80 分辨率 ST7735 驱动的 0.96 英寸 LCD 显示屏,240×240 分辨率 ST7789 驱动的 1.3 英寸 LCD 显示屏。以及采用相同驱动芯片,但是分辨率分别为 240×320240×280 的 2.4 英寸以及 1.69 英寸 LCD 显示屏。所有屏幕全部板载有 0.5mm 间距的 FPC 柔性排线连接器,同时还引出 2.54mm 间距的直插排针,便于通过杜邦线快速搭建实验电路。

之前由我设计制作并且开源出来的 UINIO-MCU-ESP32C3UINIO-MCU-ESP32S3 两款核心板,分别基于乐鑫科技的 ESP32-C3 (RISC-V) 与 ESP32-S3 (Xtensa) 微控制器(更多玩法可以参考之前撰写的 《基于 UINIO-MCU-ESP32 的 Arduino 进阶教程》 一文)。而本篇文章就会采用这两款核心板,以及乐鑫官方的 Arduino-ESP32 板级支持包,结合 U8G2TFT_eSPI 两款开源显示库,帮助大家快速上手 UINIO-Monitor 系列里的 5 款显示屏。

0.96 英寸 OLED 显示屏

UINIO-Monitor 系列显示屏幕当中的 0.96 英寸 OLED(有机发光二极管,Organic Light-Emitting Diode)显示屏,分辨率为 128 × 64 像素,使用 I²C 总线进行通信,驱动集成电路采用的是香港晶门半导体(Solomon Systech)的 SSD1315(可以同时兼容 SSD1306)。

注意:如果焊接上丝印为 0x78 的电阻 R20,就会把 I²C 从设备的地址配置为 0x78。相应的,如果焊接上 0x7A 丝印的电阻 R19,则表示从设备地址为 0x7A。当使用 Arduino-ESP32U8G2 库进行通信时,需要焊接上 R20,而 R19 位置留空。

该屏幕模组采用了日本特瑞仕(TOREX)的 XC6206P332MR 低压差线性稳压芯片,可以同时兼容 3.3V5V 两种工作电压。屏幕在全亮状态下的工作电流约为 25mA,而全部熄灭黑屏状态下的待机电流约为 1.5mA。正是由于该屏幕工作电流较小,为了防止正负极反接导致稳压芯片损坏,所以串接了一枚型号为 1N5819 的肖特基二极管(额定正向电流为 1A)作为防反接设计,具体电路设计可以参考如下的原理图(鼠标双击可以放大):

开始上手实践之前,需要把 UINIO-Monitor 上 0.96 英寸 OLED 显示屏的 SCKSDAGNDVCC 引脚,分别与 UINIO-MCU-ESP32S3 核心板的 GPIO16GPIO175VGND 引脚进行连接,后续 U8G2 库相关的示例代码都将会沿用这个连接关系:

注意:为了文章撰写与阅读的方便直观,UINIO-Monitor 当中的 0.96 英寸 OLED 显示屏,在后续 U8G2 库相关章节的内容里全部直接简称为 UINIO-Monitor

U8G2 库开发速成

U8G2 是一款运行在嵌入式设备上的 Arduino 单色显示库,可以通过 Arduino IDE 的【库管理器】直接进行安装。U8G2 库包含有 文字位图线/框/圆 的绘制方法,并且可以支持多种字体,显示内容时需要使用到微控制器的 RAM 作为缓冲区。除此之外,U8G2 库还内嵌有一个小巧的 U8x8 库,该库只能输出文本内容,并且只能显示固定像素大小的字体,不过显示内容时无需再使用微控制器的 RAM 存储器作为缓冲区。关于两个库的更多介绍,可以参考 《U8g2 Reference Manual》《U8x8 Reference Manual》 两份官方文档。

u8g2() 构造函数

使用 U8G2 库的第一步是要根据当前使用的屏幕规格与总线通信方式,选择对应的 u8g2() 构造函数,从而实例化出相应的 u8g2 对象。本文以 UINIO-Monitor 当中 0.96 英寸 OLED 显示屏所需要使用到的 U8G2_SSD1306_128X64_NONAME_F_HW_I2C 硬件 I²C 类型和 U8G2_SSD1306_128X64_NONAME_F_SW_I2C 软件 I²C 类型为例进行讨论:

1
2
3
4
5
/* 采用 SSD1306 驱动芯片,分辨率为 128*X*64,通信方式为软件 I²C 总线 */
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ 16, /* data=*/ 17, /* reset=*/ U8X8_PIN_NONE);

/* 采用 SSD1306 驱动芯片,分辨率为 128*X*64,通信方式为硬件 I²C 总线 */
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 16, /* data=*/ 17);

注意:硬件 I²C 相比于软件 I²C 总线的显示刷新频率更高,渲染动画效果的时候更加顺滑。

事实上,U8G2 库的 Arduino C++ 构造函数 u8g2(),其返回值类型都遵循着统一的命名规则:

前缀 屏幕驱动芯片型号 分辨率 生产品牌 缓冲区大小 通信方式
U8G2 SSD1306 128X64 NONAME F HW_I2C
U8G2 SSD1306 128X64 NONAME F SW_I2C

接下来的三个表格,分别展示了上述表格当中缓冲区大小通信方式显示旋转方向的具体参数信息:

缓冲区大小 功能描述
1 占用 1 页的微控制器 RAM 作为缓冲区。
2 占用 2 页的微控制器 RAM 作为缓冲区(可以获得更快的显示刷新速度)。
F 在微控制器 RAM 当中保存完整的显示帧(推荐在 RAM 存储空间足够大的场景下使用)。
显示旋转方向 功能描述
U8G2_R0 不旋转,横向显示。
U8G2_R1 顺时针 90° 度旋转。
U8G2_R2 顺时针 180° 度旋转。
U8G2_R3 顺时针 270° 度旋转。
U8G2_MIRROR 不旋转,横向显示,但是内容会被镜像。
通信方式 功能描述
4W_SW_SPI 四线制(Clock\Data\CS\DC)的软件模拟 SPI。
4W_HW_SPI 四线制(Clock\Data\CS\DC)的硬件 SPI。
2ND_4W_HW_SPI 第 2 个四线制的硬件 SPI。
3W_SW_SPI 三线制(Clock\Data\CS)的软件模拟 SPI。
SW_I2C 软件模拟的 I²C 总线通信。
HW_I2C 硬件 I²C 总线通信。
2ND_HW_I2C 第 2 个硬件 I²C 通信总线。
6800 采用 6800 协议的 8 位并行接口。
8080 采用 8080 协议的 8 位并行接口。

注意:如果当前没有连接重置输入引脚,那么就可以将构造函数中的 reset 参数,直接填写为 U8X8_PIN_NONE

采用 u8g2() 构造函数创建 u8g2 类的时候,需要传入一系列的参数,下面表格就展示了这些参数的具体信息:

引脚参数 数据手册名称 功能描述
clock SCL / SCLK ... SPI 或者 I²C 总线的时钟线。
data SDA / MOSI / SDIN ... SPI 或者 I²C 总线的数据线。
d0 ... d7 D0 ... D7 并行接口的数据线。
cs CS 片选信号线。
dc D/C / A0 / RS, ... 数据/命令选择线。
enable 8080:WR / 6800:E 8080 接口的 Write 写入线,6800 接口的 Enable 使能线。
reset - 重置信号线。

U8G2 库默认使用 8 位显示模式(即 256 色),如果需要使用 16 位显示模式,则必须在 u8g2.h 头文件当中添加如下注释:

1
#define U8G2_16BIT

注意:16 位显示模式下,保存 U8G2 像素坐标的数据类型也会从 8 位变换为 16 位。

接下来,就会通过 Arduino C++U8G2 库,结合 UINIO-MCU-ESP32S3UINIO-Monitor 实现一个显示 Hello UinIO.com! 字符串的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <Arduino.h>
#include <SPI.h>
#include <U8g2lib.h>

/* U8G2 构造函数 */
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE, /* clock=*/16, /* data=*/17);

void setup(void) {
u8g2.begin(); // 设置与初始化显示
}

void loop(void) {
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_ncenB10_tr);
u8g2.drawStr(0, 20, "Hello UinIO.com!");
} while (u8g2.nextPage());
delay(1000);
}

u8x8() 构造函数

U8G2 所包含的 U8x8 库无需占用微控制器的 RAM 存储空间,可以用于直接显示一些文本信息。但是需要注意 u8x8() 构造函数的参数构成,与 u8g2() 构造函数并不相同:

1
U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8(/* clock=*/16, /* data=*/17, /* reset=*/U8X8_PIN_NONE);

U8x8 库的 Arduino C++ 构造函数 u8x8(),其返回值类型都遵循着如下的命名规则:

前缀 屏幕驱动芯片型号 分辨率 生产品牌 通信方式
U8G2 SSD1306 128X64 NONAME HW_I2C
... ... ... ... ... ... ... SW_I2C

观察可以发现,除了没有缓冲区大小设置相关的参数之外,u8x8() 构造函数的返回类型命名方式,与 U8G2 库的 u8g2() 构造函数基本保持一致。接下来同样可以通过 Arduino C++U8x8 库,基于 UINIO-MCU-ESP32S3UINIO-Monitor 实现一个 Hello UinIO.com! 字符串显示的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <Arduino.h>
#include <SPI.h>
#include <U8x8lib.h>

/* u8x8 构造函数 */
U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8(/* clock=*/16, /* data=*/17, /* reset=*/U8X8_PIN_NONE);

void setup(void) {
u8x8.begin();
}

void loop(void) {
u8x8.setFont(u8x8_font_chroma48medium8_r);
u8x8.drawString(0, 0, "Hello UinIO.com!");
delay(1000);
}

初始化工作

u8g2 类的 begin() 函数用于简化 Arduino 环境下的显示设置步骤,该函数在底层会依次调用 initDisplay()clearDisplay()setPowerSave() 三个函数:

1
bool begin(void)

begin() 函数还可以用于绑定按键检测事件(最高可以绑定 6 个按键),如果没有连接相应的按键,则对应的参数可以设置为 U8X8_PIN_NONE

1
2
3
4
5
6
7
8
bool begin(
uint8_t menu_select_pin,
uint8_t menu_next_pin,
uint8_t menu_prev_pin,
uint8_t menu_up_pin = U8X8_PIN_NONE,
uint8_t menu_down_pin = U8X8_PIN_NONE,
uint8_t menu_home_pin = U8X8_PIN_NONE
)

除了 begin() 函数之外,u8g2 类还提供有如下的屏幕显示初始化函数:

API 方法 功能描述
void setBusClock(uint32_t clock_speed); 设置总线通信的时钟频率,I²C 总线可以尝试 200000 或者 400000,SPI 总线可以尝试 1000000 或者 8000000
void setContrast(uint8_t value) 设置显示对比度,取值范围从 0 ~ 255

字体配置

U8G2 库使用的是点阵字体,使用时需要通过 setFont() 函数配置当前所要显示的字体,其中字体名称 u8g2_font_字体类型与字符集 的最后两个字符定义了字体的类型字符集

字体名称 助记词 字体类型
u8g2_xxx_tx Transparent 具有可变宽度的透明字体。
u8g2_xxx_mx Monospace 等宽字体。
u8g2_xxx_hx Height 具有可变宽度和共同高度的字体。
u8g2_xxx_8x 8x8 位于 8x8 盒子当中的等宽字体。
字体名称 助记词 字符集
u8g2_xxx_xe Extended 包含 Unicode 编码 32 ~ 701 的字符。
u8g2_xxx_xf Full 包含 Unicode 编码 32 ~ 255 的字符。
u8g2_xxx_xr Restricted 包含 Unicode 编码 32 ~ 127 的字符。
u8g2_xxx_xu Uppercase 只包含有数字和大写字母。
u8g2_xxx_xn Numbers 包含日期和时间表达的数值与额外字符。
u8g2_xxx_x_something - 特殊字体。

注意U8G2 库不支持直接设置字体的大小,而是通过选用不同尺寸的字体来完成字体大小的控制。

如果需要通过 U8G2 库显示中文,则必须在 begin() 调用之后,print() 调用之前执行下面的方法,从而使能 UTF8 编码字符的显示输出,具体说明请参见下表所示:

API 方法 功能描述
void enableUTF8Print(void) 使能 UTF8 显示支持,从而可以通过 u8g2.print() 打印中文;
void disableUTF8Print(void) 失能 UTF8 显示支持,默认状态。

目前 U8G2 库已经包含了中文的文泉驿字体,可以同时支持 1213141516 像素大小的字体:

  • u8g2_font_wqy(12~16)_t_chinese1:只包含 U8G2 官方提供的小字符集。
  • u8g2_font_wqy(12~16)_t_chinese2:只包含 U8G2 官方提供的小字符集。
  • u8g2_font_wqy(12~16)_t_chinese3:只包含 U8G2 官方提供的小字符集。
  • u8g2_font_wqy(12~16)_t_gb2312:包含有完整的 GB2312 中文简体字符集。
  • u8g2_font_wqy(12~16)_t_gb2312a:仅包含 GB2312 的 010216 ~ 55 以及部分 08 区编码,没有包含全角标点符号。
  • u8g2_font_wqy(12~16)_t_gb2312b:仅包含 GB2312 的 1 ~ 55 区编码,其中 10 ~ 15 属于空区,相比于 gb2312a 会多出一些额外的符号。

注意:使用上述字体时,只需要将 (12~16) 部分替换为当前所需的像素大小即可。

除此之外,U8G2 库还可以支持 GNU 的 Unifont 点阵黑中文字体,不过这些字体的美观程度明显逊色于文泉驿字体:

  • u8g2_font_unifont_t_chinese1:包含 U8G2 官方提供的小字符集。
  • u8g2_font_unifont_t_chinese2:包含 U8G2 官方提供的小字符集。
  • u8g2_font_unifont_t_chinese3:包含 U8G2 官方提供的小字符集。

下面的示例代码,就将会分别使用 u8g2_font_wqy16_t_gb2312u8g2_font_wqy13_t_gb2312 两种字体,在 UINIO-Monitor 上面显示 "Hello UinIO.com!""你好,电子技术博客!" 两组字符串内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <Arduino.h>
#include <U8g2lib.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE, /* clock=*/16, /* data=*/17);

void setup(void) {
u8g2.begin();
u8g2.enableUTF8Print(); // 使能 UTF8 显示支持
}

void loop(void) {
u8g2.setFontDirection(0); // 从左至右进行显示
u8g2.firstPage();
do {
/* 使用高度为 16 像素,包含完整 GB2312 字符集的文泉驿字体 */
u8g2.setFont(u8g2_font_wqy16_t_gb2312);
u8g2.setCursor(0, 16);
u8g2.print("Hello UinIO.com!");

/* 使用高度为 13 像素,包含完整 GB2312 字符集的文泉驿字体 */
u8g2.setFont(u8g2_font_wqy13_t_gb2312);
u8g2.setCursor(0, 36);
u8g2.print("你好,电子技术博客!");
} while (u8g2.nextPage());
delay(1000);
}

除了上面介绍的方法之外,U8G2 库还额外提供有如下几个字体显示相关的工具函数:

API 方法 功能描述
u8g2_uint_t getMaxCharHeight(void) 返回指定点阵字体里,最大的字体高度
u8g2_uint_t getMaxCharWidth(void) 返回指定点阵字体里,最大的字体宽度
void setDrawColor(uint8_t color) 参数 color0 表示字体不亮背景亮,为 1 表示背景不亮字体亮(默认)。

显示坐标系统

显示坐标系统是 U8G2 库当中比较重要的概念,运用显示相关的函数时,需要特别关注其显示起始的坐标。当使用 u8g2 类的 print() 函数显示内容时,需要先运用 setCursor() 函数设置显示内容在 x 轴与 y 轴的起始像素坐标位置:

API 方法 功能描述
void setCursor(u8g2_uint_t x, u8g2_uint_t y) 设置显示内容的起始像素坐标位置。
void home(void) 将光标放置到屏幕的左上角。
void clear(void) 清除屏幕和缓冲区上的内容,并且将光标放置到左上角。

U8G2 库将屏幕的左上角作为坐标原点 (0, 0),显示内容将会沿着起始坐标位置分别向上向右进行输出,例如下图左侧的代码分别将显示的起始坐标设置为 x = 0y = 15,最终渲染显示出来的结果如下面右图所示:

屏幕刷新方法

U8G2 库提供有 clearBuffer()sendBuffer() 这组屏幕显示刷新的方法(刷新速度比较快,但是 RAM 空间占用较大):

API 方法 功能描述
void clearBuffer(void) 清除微控制器 RAM 帧缓冲区当中的所有内容。
void sendBuffer(void) 将微控制器 RAM 帧缓冲区当中的内容发送至屏幕进行显示。

显示缓冲区操作相关的代码,都必须放置到 clearBuffer()sendBuffer() 函数之间的区域:

1
2
3
4
5
6
7
void loop(void) {
u8g2.clearBuffer();
/* ................................................ */
/* 显示缓冲区相关的操作代码都放置到这里 */
/* ................................................ */
u8g2.sendBuffer();
delay(1000);

除此之外,U8G2 库还提供了 firstPage()nextPage() 来刷新屏幕显示内容(消耗的 RAM 空间相对较小):

API 方法 功能描述
void firstPage(void) 该命令是内容渲染循环的一部分,需要与 nextPage() 配合使用。
uint8_t nextPage(void) 该命令是内容渲染循环的一部分,需要与 firstPage() 配合使用。

使用时即可以采用 do...while() 循环的方式来组合调用 firstPage()nextPage() 函数:

1
2
3
4
5
6
u8g2.firstPage();
do {
/* ....................................................... */
/* 所有显示内容的代码,都必须出现在该循环体内 */
/* ....................................................... */
} while (u8g2.nextPage());

也可以把 firstPage() 放置到 Arduino 草图代码的 setup() 函数当中,而 nextPage() 函数放置到 loop() 循环的内部:

1
2
3
4
5
6
7
8
9
10
void setup() {
u8g2.firstPage();
}

void loop() {
/* ....................................................... */
/* 显示内容到屏幕的相关代码都必须出现在这里 */
/* ....................................................... */
u8g2.nextPage();
}

文本输出函数

除了之前示例代码当中使用过的 print()drawStr() 之外,U8G2 库还提供有如下一系列可以用于显示内容输出的方法:

API 方法 功能描述
u8g2_uint_t drawStr(u8g2_uint_t x, u8g2_uint_t y, const char *s) 在指定的坐标位置绘制字符串(不能绘制编码大于或等于 256 的字符),使用前必须指定字体。
u8g2_uint_t drawStrX2(u8g2_uint_t x, u8g2_uint_t y, const char *s) 功能同上,只是绘制的字体大小加倍。
u8g2_uint_t drawUTF8(u8g2_uint_t x, u8g2_uint_t y, const char *s) 绘制一个编码为 UTF-8 的字符串(中文),该函数能够绘制编码值大于 127 的字符。
u8g2_uint_t drawUTF8X2(u8g2_uint_t x, u8g2_uint_t y, const char *s) 功能同上,只是绘制的字体大小加倍。
u8g2_uint_t drawGlyph(u8g2_uint_t x, u8g2_uint_t y, uint16_t encoding) 在指定的坐标位置绘制图像字符,需要配合特殊的图像字体一起使用。
u8g2_uint_t drawGlyphX2(u8g2_uint_t x, u8g2_uint_t y, uint16_t encoding) 功能同上,只是绘制的字体大小加倍。
void print(...) 向当前的光标位置(通过 setCursor() 设置)写入指定字体(通过 setFont() 设置)的文本(需要调用 enableUTF8Print() 使能 UTF-8 编码)。

接下来的示例代码,就会分别采用上面表格当中介绍的各种工具函数,测试输出各种显示内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <Arduino.h>
#include <U8g2lib.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 16, /* data=*/ 17);

void setup() {
Serial.begin(115200);
u8g2.setBusClock(800000); // 设置 I²C 总线时钟频率
u8g2.begin(); // 初始化显示相关的一系列设置
u8g2.enableUTF8Print(); // 使能 UTF8 编码支持
}

void loop() {
static unsigned int start = millis(); // 代码开始执行时候的毫秒数
u8g2.clearBuffer();

/* 设置一个英文字体,并且调用 drawStr() 函数显示英文 */
u8g2.setFont(u8g2_font_adventurer_tf);
u8g2.drawStr(0, 13, "Hello UinIO.com!");

/* 设置一个中文字体,并且调用 print() 函数显示中文 */
u8g2.setFont(u8g2_font_wqy13_t_gb2312b);
u8g2.setCursor(0, 30);
u8g2.print("你好,电子技术博客!");

/* 调用 drawUTF8() 绘制 UTF8 编码的雪人字符 */
u8g2.setFont(u8g2_font_unifont_t_symbols);
u8g2.drawUTF8(0, 55, "Snowman:☃");

/* 调用 drawGlyph() 绘制太阳图标 */
u8g2.setFont(u8g2_font_open_iconic_weather_2x_t);
u8g2.drawGlyph(100, 57, 0x0045);

u8g2.sendBuffer();
static unsigned int end = millis(); // 代码结束执行时候的毫秒数
Serial.println(end - start); // 向串口打印上述代码执行所花费的毫秒数
}

绘制位图

XBM(X-Bitmap)是一种通用的图像文件格式,可以通过一个 16 进制数组来表示二进制图像。U8G2 库提供的 drawXBM() 函数,可以直接用来绘制单色位图(图片在使用之前需要进行单值化处理和取模):

API 方法 功能描述
void drawXBM(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h, const uint8_t *bitmap) 绘制 XBM 格式的位图,参数 xy 表示位图的左上角,而 wh 表示位图的宽高,参数 bitmap 表示取模之后得到的单色位图数组。

注意:通常会将 bitmap 数组变量定义为 PROGMEM 类型,表示将其存储在 Flash 存储器当中,便于保存一些较大的位图数据,并且节省微控制器 RAM 的存储空间。

位图取模

drawXBM() 中的单色位图数组参数 bitmap,可以通过取模工具软件 PCtoLCD2002 获取,具体操作步骤如下面列表所示:

  1. 调整分辨率:将图片转换为适配 OLED 屏幕的 128*64 分辨率。
  2. 转换单色位图:再将转换分辨率之后的图片处理为 .bmp 单色位图格式。
  3. 生成 XBM 数组:使用 PCtoLCD2002 工具软件对该单色位图进行取模,得到 XBM 数组。
  4. U8G2 绘制位图:调用 u8g2 类的 drawXBM() 函数显示位图。

注意:可以采用更为方便的在线工具 image-to-bitmap-array 进行取模操作。

首先使用 Windows 操作系统自带的画图工具打开目标图片,然后点击顶部工具栏的【重新调整大小】,在弹出的对话框中选择【像素】,再将水平和垂直高度分别设置为 64 个像素:

完成位图尺寸的调整之后,鼠标依次点击 Windows 画图工具顶部菜单栏的【文件 → 另存为 → BMP 图片】:

在接下来弹出的【保存为】对话框当中,选择保存类型为【单色位图】的 .bmp 文件:

接着打开 PCtoLCD2002 取模软件,点击顶部工具栏上的【字模生成和液晶面板选项】按钮,在弹出的【字模选项】对话框当中进行如下设置:

最后,鼠标再次点击顶部工具栏上的【打开一个 BMP 图像】按钮,将刚才得到的 .bmp 单色位图文件导入,再点击【生成字模】按钮,就会在界面的底部区域得到 U8G2 库绘图所需的 XBM 数组:

把这里得到的位图数组赋值给下面示例代码的 Hank 变量,然后调用 drawXBM() 函数就可以进行位图的显示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <Arduino.h>
#include <U8g2lib.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE, /* clock=*/16, /* data=*/17);

PROGMEM const uint8_t Hank[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x03, 0x00, 0x00,
0x00, 0x00, 0x80, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x1F, 0x00, 0x00,
0x00, 0x00, 0xF8, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x7F, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00,
0x00, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x00,
0x00, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x00,
0x00, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x00,
0x00, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x00,
0x00, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x00,
0x00, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x00,
0x00, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0xFF, 0xFE, 0x3F, 0x00,
0x00, 0xFC, 0xFF, 0xFF, 0x3F, 0xFC, 0x3F, 0x00, 0x00, 0xFC, 0xBF, 0xFF, 0x07, 0xFC, 0x3F, 0x00,
0x00, 0xFC, 0x0F, 0x00, 0x00, 0xF8, 0x1F, 0x00, 0x00, 0xFC, 0x03, 0x00, 0x00, 0xF0, 0x1F, 0x00,
0x00, 0xF8, 0x03, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x00, 0xF0, 0x01, 0x00, 0x00, 0xC0, 0x1F, 0x00,
0x00, 0xF0, 0x01, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0xE0, 0x03, 0xC0, 0x87, 0x0F, 0x00,
0x00, 0xF0, 0x00, 0x06, 0x60, 0x80, 0x0F, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00,
0x00, 0x88, 0x3C, 0x1F, 0xF8, 0x3C, 0x13, 0x00, 0x00, 0x8C, 0xFB, 0xA7, 0xE5, 0xDF, 0x31, 0x00,
0x00, 0x84, 0xCA, 0x21, 0x84, 0xD3, 0x21, 0x00, 0x00, 0x84, 0x02, 0x20, 0x04, 0xC0, 0x21, 0x00,
0x00, 0x84, 0x02, 0x90, 0x08, 0x80, 0x21, 0x00, 0x00, 0x84, 0xFD, 0x8F, 0xF0, 0x9F, 0x31, 0x00,
0x00, 0x88, 0x01, 0x80, 0x00, 0x80, 0x10, 0x00, 0x00, 0x18, 0x01, 0x40, 0x00, 0x80, 0x18, 0x00,
0x00, 0xE0, 0x01, 0x80, 0x00, 0x80, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0xC0, 0x00, 0x00,
0x00, 0x00, 0x02, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x06, 0x03, 0xC0, 0x60, 0x00, 0x00,
0x00, 0x00, 0x0C, 0xFC, 0x3F, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00,
0x00, 0x00, 0x30, 0xC0, 0x03, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x06, 0x00, 0x00,
0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x78, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xF7, 0xEF, 0x01, 0x00, 0x00,
0x00, 0x00, 0xC0, 0x8F, 0xF1, 0x03, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xDF, 0xF9, 0x07, 0x00, 0x00,
0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0x07, 0x00, 0x00,
0x00, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0x00, 0x00,
0x00, 0x00, 0xF8, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 0x1F, 0x00, 0x00,
0x00, 0x00, 0xF8, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0xEC, 0xFF, 0xFF, 0x17, 0x00, 0x00,
};

void setup() {
u8g2.setBusClock(800000); // 设置 I²C 总线时钟频率
u8g2.begin(); // 初始化显示相关的一系列设置
u8g2.enableUTF8Print(); // 使能 UTF8 编码支持
}

void loop() {
u8g2.clearBuffer();
u8g2.drawXBM(32, 0, 64, 64, Hank); // 调用 drawXBM() 函数绘制位图
u8g2.sendBuffer();
}

将上述代码下载到 UINIO-MCU-ESP32S3 核心板执行之后,显示到 UINIO-Monitor 的 OLED 屏幕内容如下面所示:

汉字取模

除了支持把位图转换为 XBM 数组之外,PCtoLCD2002 还能够把字符转换为 XBM 数组。首先在打开 PCtoLCD2002 取模软件之后,选择顶部菜单栏上的【模式 → 字符模式】:

接下来,依然需要点击顶部工具栏上的【字模生成和液晶面板选项】按钮,在弹出的【字模选项】对话框当中进行如下设置:

然后,选择字体为 楷体,每一个字的宽度与高度都设置为 64 个像素,并且在中间的输入框填写汉字 成都,点击【生成字模】按钮:

最后,把上述步骤得到的两个 XBM 数组,分别赋予如下示例代码当中的 ChengduDu 两个变量,再分别调用 drawXBM() 函数就可以完成显示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <Arduino.h>
#include <U8g2lib.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE, /* clock=*/16, /* data=*/17);

PROGMEM const uint8_t Cheng[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xF0, 0x80, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x3F, 0x00, 0x00,
0x00, 0x00, 0x00, 0xF0, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x7C, 0x00, 0x00,
0x00, 0x00, 0x00, 0xE0, 0x01, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x80, 0xC3, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x0F, 0x00, 0x00,
0x00, 0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFE, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x7F, 0x0F, 0x00, 0x00, 0x00,
0x00, 0xF0, 0xFF, 0x07, 0x0E, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x0E, 0x00, 0x00, 0x00,
0x00, 0xE0, 0x01, 0x00, 0x1E, 0x20, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x1C, 0xE0, 0x00, 0x00,
0x00, 0xE0, 0x01, 0x00, 0x1C, 0xE0, 0x01, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x3C, 0xE0, 0x03, 0x00,
0x00, 0xC0, 0x01, 0x00, 0x38, 0xE0, 0x03, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x78, 0xE0, 0x03, 0x00,
0x00, 0xC0, 0x01, 0x04, 0x70, 0xE0, 0x01, 0x00, 0x00, 0xC0, 0x01, 0x1F, 0x70, 0xF0, 0x00, 0x00,
0x00, 0xC0, 0x81, 0x7F, 0xF0, 0xF0, 0x00, 0x00, 0x00, 0xC0, 0xC1, 0x7F, 0xE0, 0x78, 0x00, 0x00,
0x00, 0xC0, 0xF9, 0x7D, 0xE0, 0x79, 0x00, 0x00, 0x00, 0xC0, 0x3F, 0x3C, 0xC0, 0x3D, 0x00, 0x00,
0x00, 0xC0, 0x07, 0x3C, 0xC0, 0x3F, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x3C, 0x80, 0x1F, 0x00, 0x00,
0x00, 0xE0, 0x01, 0x3C, 0x80, 0x1F, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x3C, 0x00, 0x0F, 0x00, 0x00,
0x00, 0xE0, 0x01, 0x3C, 0x80, 0x0F, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x3C, 0xC0, 0x1F, 0x00, 0x00,
0x00, 0xF0, 0x00, 0x3C, 0xC0, 0x3F, 0x00, 0x00, 0x00, 0x70, 0x00, 0x1C, 0xE0, 0x3D, 0x00, 0x00,
0x00, 0x78, 0x00, 0x1E, 0xF0, 0x7C, 0x00, 0x00, 0x00, 0x38, 0x00, 0x1E, 0x78, 0xF8, 0x00, 0x00,
0x00, 0x3C, 0x18, 0x1E, 0x1C, 0xF0, 0x01, 0x04, 0x00, 0x1C, 0xF0, 0x1F, 0x0E, 0xF0, 0x01, 0x06,
0x00, 0x0E, 0xE0, 0x0F, 0x03, 0xE0, 0x03, 0x06, 0x00, 0x07, 0xC0, 0x07, 0x00, 0xC0, 0x07, 0x06,
0x80, 0x03, 0xC0, 0x07, 0x00, 0x80, 0x1F, 0x07, 0xC0, 0x01, 0x80, 0x03, 0x00, 0x00, 0x7F, 0x07,
0x60, 0x00, 0x80, 0x01, 0x00, 0x00, 0xFE, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x07,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x07,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*"成",0*/
};

PROGMEM const uint8_t Du[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xC0, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x0E, 0x00, 0x00, 0x00,
0x00, 0x00, 0xC0, 0x01, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x1E, 0x00, 0x78, 0x00,
0x00, 0x00, 0xC0, 0x7F, 0x1F, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xE0, 0x7F, 0x8F, 0x87, 0xFF, 0x03,
0x00, 0xC0, 0xFF, 0x8F, 0x87, 0xFF, 0xFB, 0x03, 0x00, 0xC0, 0xFF, 0x81, 0x03, 0x0F, 0xF0, 0x03,
0x00, 0x00, 0xC7, 0xC1, 0x03, 0x0F, 0xF8, 0x00, 0x00, 0x00, 0xC0, 0xC1, 0x01, 0x0F, 0x78, 0x00,
0x00, 0x00, 0xC0, 0xE1, 0x00, 0x0F, 0x3C, 0x00, 0x00, 0x00, 0xC0, 0xE1, 0x00, 0x0F, 0x1C, 0x00,
0x00, 0x00, 0xC0, 0x71, 0x3C, 0x0F, 0x0E, 0x00, 0x00, 0x00, 0xC0, 0xF9, 0x7F, 0x0F, 0x06, 0x00,
0x00, 0x00, 0xC0, 0xFF, 0x1F, 0x0F, 0x03, 0x00, 0x00, 0x00, 0xFF, 0x1F, 0x00, 0x0F, 0x01, 0x00,
0xF0, 0xFF, 0xFF, 0x0E, 0x00, 0x8F, 0x01, 0x00, 0xF0, 0xFF, 0x07, 0x0F, 0x00, 0x0F, 0x03, 0x00,
0xE0, 0x7F, 0x00, 0x07, 0x00, 0x0F, 0x06, 0x00, 0x80, 0x03, 0x80, 0x03, 0x00, 0x0F, 0x0C, 0x00,
0x00, 0x00, 0xC0, 0x01, 0x00, 0x0F, 0x18, 0x00, 0x00, 0x00, 0xE0, 0xF0, 0x00, 0x0F, 0x30, 0x00,
0x00, 0x00, 0x73, 0xFC, 0x03, 0x0F, 0x70, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x07, 0x0F, 0xE0, 0x00,
0x00, 0x00, 0xFE, 0xE1, 0x07, 0x0F, 0xC0, 0x01, 0x00, 0x00, 0x0E, 0xC0, 0x03, 0x0F, 0xC0, 0x01,
0x00, 0x00, 0x0F, 0xC0, 0x03, 0x0F, 0xC0, 0x03, 0x00, 0x80, 0x0F, 0xC0, 0x01, 0x0F, 0xC0, 0x03,
0x00, 0xC0, 0x0D, 0xC0, 0x01, 0x0F, 0x80, 0x03, 0x00, 0xE0, 0x0C, 0xC0, 0x01, 0x8F, 0x87, 0x07,
0x00, 0x70, 0x8C, 0xC7, 0x01, 0x0F, 0xFF, 0x07, 0x00, 0x38, 0xFC, 0xC7, 0x01, 0x0F, 0xFC, 0x07,
0x00, 0x1E, 0xFC, 0xC3, 0x01, 0x0F, 0xF8, 0x07, 0x00, 0x0F, 0x0C, 0xC0, 0x01, 0x0F, 0xF0, 0x03,
0x80, 0x03, 0x0C, 0xC0, 0x01, 0x0F, 0xE0, 0x03, 0xC0, 0x01, 0x0C, 0xC0, 0x01, 0x0F, 0xE0, 0x01,
0x60, 0x00, 0x0C, 0xC0, 0x01, 0x0F, 0xC0, 0x00, 0x10, 0x00, 0x0C, 0xC0, 0x01, 0x0F, 0x00, 0x00,
0x00, 0x00, 0x0E, 0xEF, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x01, 0x0F, 0x00, 0x00,
0x00, 0x00, 0xFE, 0xFF, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00, 0x0E, 0xE0, 0x01, 0x07, 0x00, 0x00,
0x00, 0x00, 0x0C, 0xE0, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xC0, 0x01, 0x07, 0x00, 0x00,
0x00, 0x00, 0x04, 0xC0, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*"都",1*/
};

void setup() {
u8g2.setBusClock(800000); // 设置 I²C 总线时钟频率
u8g2.begin(); // 初始化显示相关的一系列设置
u8g2.enableUTF8Print(); // 使能 UTF8 编码支持
}

void loop() {
u8g2.clearBuffer();
u8g2.drawXBM(0, 0, 64, 64, Cheng); // 调用 drawXBM() 函数绘制 "成" 字
u8g2.drawXBM(64, 0, 64, 64, Du); // 调用 drawXBM() 函数绘制 "都" 字
u8g2.sendBuffer();
}

这里同样将上述代码下载到 UINIO-MCU-ESP32S3 核心板运行,此时 UINIO-Monitor 的 OLED 屏幕显示结果如下面所示:

绘制基本图形

矩形绘制

U8G2 库提供了 drawBox()drawFrame() 两个函数用于矩形的绘制:

矩形绘制 API 功能描述
void drawBox(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h) 绘制一个实心的矩形,参数 xy 表示矩形左上角的起始位置,而 wh 分别表示其宽度与高度。
void drawFrame(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h) 绘制一个空心的矩形,参数 xy 表示矩形左上角的起始位置,而 wh 分别表示其宽度与高度。

下面的示例代码会在 UINIO-Monitor 屏幕的左右两侧,分别绘制一个实心矩形和一个空心矩形:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <Arduino.h>
#include <U8g2lib.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE, /* clock=*/16, /* data=*/17);

void setup() {
u8g2.begin();
}

void loop() {
u8g2.clearBuffer();

u8g2.drawBox(16, 16, 32, 32); // 绘制实心矩形
u8g2.drawFrame(80, 16, 32, 32); // 绘制空心矩形

u8g2.sendBuffer();
}

圆形绘制

U8G2 库提供了 drawCircle()drawDisc() 两个函数用于圆形的绘制:

圆形绘制 API 功能描述
void drawCircle(u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rad, uint8_t opt = U8G2_DRAW_ALL) (x0, y0) 位置为圆心,绘制一个半径为 rad空心圆,参数 opt 用于指定只绘制圆形的哪些部分。
void drawDisc(u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rad, uint8_t opt = U8G2_DRAW_ALL) (x0, y0) 位置为圆心,绘制一个半径为 rad实心圆,参数 opt 用于指定只绘制圆形的哪些部分。

下面的示例代码会在 UINIO-Monitor 屏幕的左右两侧,分别绘制一个实心圆形和一个空心圆形:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <Arduino.h>
#include <U8g2lib.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE, /* clock=*/16, /* data=*/17);

void setup() {
u8g2.begin();
}

void loop() {
u8g2.clearBuffer();

u8g2.drawCircle(32, 32, 16); // 绘制实心圆形
u8g2.drawDisc(96, 32, 16); // 绘制空心圆形

u8g2.sendBuffer();
}

椭圆形绘制

U8G2 库提供了 drawEllipse()drawFilledEllipse() 两个函数用于椭圆形的绘制:

椭圆形绘制 API 功能描述
void drawEllipse(u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rx, u8g2_uint_t ry, uint8_t opt) (x0, y0) 作为圆心,位置绘制水平半径为 rx,垂直半径为 ry空心椭圆(8 位显示模式下,两者取值必须小于 512)。
void drawFilledEllipse(u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t rx, u8g2_uint_t ry, uint8_t opt) (x0, y0) 作为圆心,位置绘制水平半径为 rx,垂直半径为 ry实心椭圆(8 位显示模式下,两者取值必须小于 512)。

下面的示例代码会在 UINIO-Monitor 屏幕的左右两侧,分别绘制一个实心椭圆形和一个空心椭圆形:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <Arduino.h>
#include <U8g2lib.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE, /* clock=*/16, /* data=*/17);

void setup() {
u8g2.begin();
}

void loop() {
u8g2.clearBuffer();

u8g2.drawEllipse(32, 30, 30, 16); // 绘制实心椭圆形
u8g2.drawFilledEllipse(96, 30, 30, 16); // 绘制空心椭圆形

u8g2.sendBuffer();
}

直线绘制

U8G2 库提供了 drawHLine()drawVLine() 以及 drawLine() 三个函数来绘制直线:

直线绘制 API 功能描述
void drawHLine(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w) 基于 (x, y) 位置从左至右绘制一条长度为 w 像素的水平直线。
void drawVLine(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t h) 基于 (x, y) 位置从下至上绘制一条长度为 w 像素的垂直直线。
void drawLine(u8g2_uint_t x0, u8g2_uint_t y0, u8g2_uint_t x1, u8g2_uint_t y1) 基于两个像素点的位置绘制一条直线,参数 (x0, y0) 是第 1 个点的坐标,而 (x1, y1) 则是第 2 个点的坐标。

下面的示例代码会在 UINIO-Monitor 屏幕上面,绘制呈现字形交错的 1 条水平直线和 1 条垂直直线,以及 2 条斜线(类似于英国国旗的图案):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <Arduino.h>
#include <U8g2lib.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE, /* clock=*/16, /* data=*/17);

void setup() {
u8g2.begin();
}

void loop() {
u8g2.clearBuffer();

u8g2.drawLine(0, 0, 128, 64); // 绘制一条从屏幕左上角到右下角的斜线
u8g2.drawLine(128, 0, 0, 64); // 绘制一条从屏幕右上角到左下角的斜线
u8g2.drawHLine(0, 32, 128); // 绘制一条水平直线
u8g2.drawVLine(64, 0, 64); // 绘制一条垂直直线

u8g2.sendBuffer();
}

像素点绘制

U8G2 库提供了 drawPixel() 函数来绘制一个像素点:

像素点绘制 API 功能描述
void drawPixel(u8g2_uint_t x, u8g2_uint_t y) (x, y) 位置绘制一个像素点。

下面的示例代码会在 UINIO-Monitor 屏幕当中,每间隔 8 个像素绘制一个点,最终形成一条水平的虚线效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <Arduino.h>
#include <U8g2lib.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE, /* clock=*/16, /* data=*/17);

void setup() {
u8g2.begin();
}

void loop() {
u8g2.clearBuffer();

/* 从左侧第 8 个像素位置开始,每间隔 8 个像素绘制出一个点 */
for (int index = 8; index <= 128; index += 8) {
u8g2.drawPixel(index, 32); // 绘制一条由像素点组成的虚线
};

u8g2.sendBuffer();
}

圆角矩形绘制

U8G2 库提供了 drawRBox()drawRFrame() 两个函数用于圆角矩形的绘制:

直线绘制 API 功能描述
void drawRBox(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h, u8g2_uint_t r) (x, y) 位置作为左上角,绘制一个宽高度分别为 wh 的圆角实心矩形,圆角的半径为 r
void drawRFrame(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t w, u8g2_uint_t h, u8g2_uint_t r) (x, y) 位置作为左上角,绘制一个宽高度分别为 wh 的圆角空心矩形,圆角的半径为 r

下面的示例代码会在 UINIO-Monitor 屏幕的左右两侧,分别绘制一个实心和一个空心的圆角矩形:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <Arduino.h>
#include <U8g2lib.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE, /* clock=*/16, /* data=*/17);

void setup() {
u8g2.begin();
}

void loop() {
u8g2.clearBuffer();

u8g2.drawRBox(30, 16, 32, 32, 8); // 绘制实心圆角矩形
u8g2.drawRFrame(66, 16, 32, 32, 8); // 绘制空心圆角矩形

u8g2.sendBuffer();
}

绘制三角形

U8G2 库提供了 drawTriangle() 函数用于绘制实心的三角形:

直线绘制 API 功能描述
void drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2) 分别以 (x0, y0)(x1, y1)(x2, y2) 作为顶点绘制一个实心的三角形。

下面的示例代码,分别以屏幕顶部中间点 (64, 0)、屏幕左下角 (0, 64)、屏幕右下角 (128, 64) 作为顶点,绘制出了一个实心的三角形:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <Arduino.h>
#include <U8g2lib.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE, /* clock=*/16, /* data=*/17);

void setup() {
u8g2.begin();
}

void loop() {
u8g2.clearBuffer();

u8g2.drawTriangle(64, 0, 0, 64, 128, 64); // 绘制实心三角形

u8g2.sendBuffer();
}

MUI 图形界面库

MUI 是一款基于 U8G2 库的单色图形用户界面库,提供了诸如 事件处理用户界面绘图丰富的预定义用户元素静态菜单定义 等特性。详细信息可以参考 U8G2 库作者提供的 《MUI 手册》《MUI 参考》 两份文档。

0.96/1.3/1.69/2.4 英寸 LCD 显示屏

UINIO-Monitor 里的 2.4 英寸显示屏幕,采用了 320 × 240 分辨率的薄膜晶体管(TFT,Thin Film Transistor)屏幕材质,总线通信方式为 SPI,驱动芯片型号是台湾矽创电子(Sitronix)ST7789,属于本系列当中显示尺寸最大的屏幕:

UINIO-Monitor 里的 1.3 英寸显示屏幕,采用了 240 × 240 分辨率的薄膜晶体管(TFT,Thin Film Transistor)屏幕材质,总线通信方式为 SPI,驱动芯片型号是台湾矽创电子(Sitronix)ST7789

UINIO-Monitor 里的 1.69 英寸圆角显示屏幕,采用了 280 × 240 分辨率的薄膜晶体管(TFT,Thin Film Transistor)屏幕材质,总线通信方式为 SPI,驱动芯片型号是台湾矽创电子(Sitronix)ST7789

UINIO-Monitor 里的 0.96 英寸显示屏幕,采用了 160 × 80 分辨率的薄膜晶体管(TFT,Thin Film Transistor)屏幕材质,总线通信方式为 SPI,驱动芯片型号是台湾矽创电子(Sitronix)ST7735

由于 UINIO-Monitor 系列的 TFT 屏幕都采用了台湾矽创电子(Sitronix)的主控方案,因而原理图设计方面基本上大同小异,不过建议线性稳压芯片采用带反接保护的德州仪器 LP2992IM5-3.3,虽然价格相对于国产微盟的 ME6211C33M5G 更贵,不过一分钱一分货,总比烧坏了成本更高的 TFT 屏幕要强:

同样在开始上手实践之前,需要把 UINIO-Monitor 当中 TFT 显示屏的 BLKCSD/CRSTSCLSDAGNDVCC 引脚,分别与 UINIO-MCU-ESP32S3 核心板的 3V3GPIO15GPIO16GPIO17GPIO18GPIO19GND5V 引脚进行连接,后续 TFT_eSPI 库相关的示例代码都将会沿用这个连接关系,下面的连接示意图以 280 × 240 分辨率的 1.69 英寸圆角显示屏幕为例:

TFT_eSPI 库开发速成

TFT_eSPI 是一款可以运行在 32 位微控制器上的 TFT 屏幕图形与字体显示库,本文撰写时的最新版本为 v2.5.0,相关的 API 函数可以参考 《TFT_eSPI 库用户手册》,该库对于如下一系列微控制器进行了专门的性能优化:

  • 树莓派 Pico 上的 RP2040 微控制器。
  • 乐鑫科技ESP8266ESP32ESP32-S2ESP32-C3ESP32-S3 微控制器。
  • 意法半导体STM32F1xxSTM32F2xxSTM32F4xxSTM32F767 微控制器(推荐采用 RAM 空间较大的型号)。

上述的微控制器在 TFT_eSPI 库当中,可以支持如下表格当中的接口类型:

微控制器 4 线制 SPI 8 位并行总线 16 位并行总线 DMA 支持
ESP32 C3
ESP32 S3 是 (仅 SPI)
ESP32 S2
ESP32 是 (仅 SPI)
RP2040 是 (全部)
ESP8266
STM32Fxxx 是 (仅 SPI)
其它

TFT_eSPI 库能够支持如下一系列 TFT 液晶显示屏驱动芯片(官方推荐使用内置有 ILI9341ST7796 两款驱动芯片的 SPI 屏幕):

型号 型号 型号 型号 型号
ILI9163 ILI9225 ILI9341 ILI9342 ILI9481
ILI9486 ILI9488 HX8357B HX8357C HX8357D
GC9A01 R61581 RM68120 RM68140 S6D02A1
SSD1351 SSD1963 ST7735 ST7789 ST7796

配置 User_Setup.h

通过 Arduino IDE 的【库管理器】安装完成 TFT_eSPI 库之后,还需要对 D:\Workspace\Workspace_Arduino\libraries\TFT_eSPI 路径下面的 User_Setup.h 头文件进行相应的编辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#define USER_SETUP_INFO "UINIO_Monitor_Setup" // 用于测试和诊断的用户自定义信息

/* 配置 TFT 液晶屏驱动芯片型号,只能预定义一个驱动芯片型号 */
#define ST7735_DRIVER // ST7735 的配置选项
#define ST7789_DRIVER // ST7789 的完整配置选项
#define ST7789_2_DRIVER // ST7789 的最小配置选项

/* 以纵向排列的方式(高度大于宽度),配置 TFT 屏幕的像素高度与宽度 */
#define TFT_WIDTH 240 // 2.4 英寸 ST7789 屏幕的宽度
#define TFT_HEIGHT 320 // 2.4 英寸 ST7789 屏幕的高度

#define TFT_WIDTH 240 // 1.3 英寸 ST7789 屏幕的宽度
#define TFT_HEIGHT 240 // 1.3 英寸 ST7789 屏幕的宽度

#define TFT_WIDTH 240 // 1.69 英寸 ST7789 屏幕的宽度
#define TFT_HEIGHT 280 // 1.69 英寸 ST7789 屏幕的宽度

#define TFT_WIDTH 80 // 0.96 英寸 ST7735 屏幕的宽度
#define TFT_HEIGHT 160 // 0.96 英寸 ST7735 屏幕的宽度

/* 配置 TFT 液晶屏当前所使用的 SPI 通信总线 */
#define TFT_MOSI 19 // 从机输出/主机输入,即 UINIO-Monitor 上丝印为 SDA 的引脚
#define TFT_SCLK 18 // 时钟引脚,即 UINIO-Monitor 上丝印为 SCL 的引脚
#define TFT_RST 17 // 重置引脚,如果已经连接至 UINIO-MCU-EESP32 的 RST 引脚,那么可以将其配置为 -1
#define TFT_DC 16 // 数据/命令控制引脚
#define TFT_CS 15 // 片选引脚
// #define TFT_BL // LED 背光控制引脚,可以接入 UINIO-MCU-ESP32S3 的 3V3 输出

/* TFT_eSPI 库自带的字体(非中文),加载全部字体会消耗约 17kb 的 Flash 存储空间,可以按需进行启用(ESP32-C3 和 ESP32-S3 的存储空间足够保存所有字体) */
#define LOAD_GLCD // 8 像素字体,占用约 1820 bytes 的 Flash 存储空间
#define LOAD_FONT2 // 16 像素字体,占用约 3534 bytes 的 Flash 存储空间
#define LOAD_FONT4 // 16 像素字体,占用约 5848 bytes 的 Flash 存储空间
#define LOAD_FONT6 // 48 像素字体,占用约 2666 bytes 的 Flash 存储空间,仅包含字符 1234567890:-.apm
#define LOAD_FONT7 // 7 段 48 像素字体,占用约 2438 bytes 的 Flash 存储空间,仅包含字符 1234567890:-.
#define LOAD_FONT8 // 75 像素字体,占用约 3256 bytes 的 Flash 存储空间,仅包含字符 1234567890:-.
// #define LOAD_FONT8N // 用于代替上面的 LOAD_FONT8 字体,稍微窄一点,适合 3 个数字 160 像素的 TFT 屏幕
#define LOAD_GFXFF // 免费字体,包括 Adafruit_GFX 免费字体,以及 FF1 至 FF48 的自定义字体

/* 用于停止加载 SPIFFS 文件系统和平滑字体代码,这样会节省约 20 kbytes 的 Flash 存储空间 */
#define SMOOTH_FONT

/* 定义 SPI 总线时钟频率,该参数会影响到图形的渲染速度,超过 27MHz 会导致使用 ST7735 驱动芯片的屏幕显示异常 */
#define SPI_FREQUENCY 27000000 // 可以选择的参数有 1000000、5000000、10000000、20000000、40000000、55000000、80000000
#define SPI_READ_FREQUENCY 20000000 // 定义 SPI 读取 TFT 屏幕的频率
#define SPI_TOUCH_FREQUENCY 2500000 // 触摸信号的 SPI 通信频率(触摸芯片 XPT2046 需要使用 2.5MHz 的时钟信号频率)

如果 UINIO-Monitor 屏幕显示出现异常,可以尝试通过调整如下的选项进行修复:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 只针对 ST7735、ST7789、ILI9341 有效,如果显示屏上红色与蓝色发生互换,那么可以重新调整其颜色顺序(只能启用一个选项)*/
#define TFT_RGB_ORDER TFT_RGB // 颜色顺序 Red-Green-Blue
#define TFT_RGB_ORDER TFT_BGR // 颜色顺序 Blue-Green-Red

/* 如果显示的颜色发生反转(白色显示为黑色),可以尝试注释下面当中的其中一项 */
#define TFT_INVERSION_ON
#define TFT_INVERSION_OFF

/* 定义显示类型(仅针对 ST7735 有效),如果屏幕不能正确显示图形(例如颜色错误、镜像、边缘杂散像素),那么可以尝试下面这些选项 */
#define ST7735_INITB
#define ST7735_GREENTAB
#define ST7735_GREENTAB2
#define ST7735_GREENTAB3
#define ST7735_GREENTAB128 // 仅 128 x 128 分辨率显示有效
#define ST7735_GREENTAB160x80 // 仅 160 x 80 分辨率显示有效
#define ST7735_ROBOTLCD // 只针对某些 RobotLCD 开发板
#define ST7735_REDTAB
#define ST7735_BLACKTAB
#define ST7735_REDTAB160x80 // 用于 24 像素偏移的 160 × 80 分辨率显示屏

开始上手

Arduino IDE 的【库管理器】可以直接安装 TFT_eSPI 库,安装完成之后就可以在 Arduino 草图代码当中包含 #include <TFT_eSPI.h> 头文件,下面代码展示了 TFT_eSPI 库的基本使用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <SPI.h>
#include <TFT_eSPI.h>

TFT_eSPI tft = TFT_eSPI(); // 创建 TFT_eSPI 对象

void setup(void) {
tft.init(); // 重置然后初始化 TFT 显示相关的寄存器
tft.setRotation(0); // 调整屏幕方向,参数为 1 表示旋转 90° 度,为 2 表示旋转 180° 度,为 3 表示旋转 270° 度
tft.fillScreen(TFT_BLACK); // 设置 TFT 屏幕的背景颜色
tft.setTextColor(TFT_WHITE, TFT_BLACK); // 设置 TFT 屏幕显示文本字体的颜色以及显示背景色
/* ... 其它 TFT_eSPI 配置代码 ... */
}

void loop() {
/* ... 需要循环执行的 TFT_eSPI 显示控制代码 ... */
}

颜色模式

TFT_eSPI 库所采用的颜色模式为 RGB565,即每一个像素占据着 2 Byte 个字节的数据量(即 2 个字节的无符号整型数据),其中红色占据 5 bit 位,绿色占据 6 bit 位,蓝色占据 5 bit 位。TFT_eSPI 库提供了一个便捷的 color565() 方法,可以将普通的 RGB 颜色转换为 RGB565 模式的颜色:

归属类与返回值 API 函数 功能描述
uint16_t TFT_eSPI:: color565(uint8_t r, uint8_t g, uint8_t b) 用于将普通 RGB 颜色转换为 RGB565 模式的颜色。

下面的示例代码,通过 color565() 函数将绿 4 种 RGB 颜色,分别转换为了 RGB565 模式的颜色值:

1
2
3
4
uint16_t RGB_Red = tft.color565(255, 0, 0);
uint16_t RGB_Yellow = tft.color565(255, 255, 0);
uint16_t RGB_Blue = tft.color565(0, 0, 255);
uint16_t RGB_Green = tft.color565(0, 255, 0);

TFT_eSPI 库安装目录下的 TFT_eSPI.h 源文件里,已经预定义了如下一系列的默认颜色,代码当中可以直接进行使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define TFT_BLACK       0x0000      /*   0,   0,   0 */
#define TFT_NAVY 0x000F /* 0, 0, 128 */
#define TFT_DARKGREEN 0x03E0 /* 0, 128, 0 */
#define TFT_DARKCYAN 0x03EF /* 0, 128, 128 */
#define TFT_MAROON 0x7800 /* 128, 0, 0 */
#define TFT_PURPLE 0x780F /* 128, 0, 128 */
#define TFT_OLIVE 0x7BE0 /* 128, 128, 0 */
#define TFT_LIGHTGREY 0xD69A /* 211, 211, 211 */
#define TFT_DARKGREY 0x7BEF /* 128, 128, 128 */
#define TFT_BLUE 0x001F /* 0, 0, 255 */
#define TFT_GREEN 0x07E0 /* 0, 255, 0 */
#define TFT_CYAN 0x07FF /* 0, 255, 255 */
#define TFT_RED 0xF800 /* 255, 0, 0 */
#define TFT_MAGENTA 0xF81F /* 255, 0, 255 */
#define TFT_YELLOW 0xFFE0 /* 255, 255, 0 */
#define TFT_WHITE 0xFFFF /* 255, 255, 255 */
#define TFT_ORANGE 0xFDA0 /* 255, 180, 0 */
#define TFT_GREENYELLOW 0xB7E0 /* 180, 255, 0 */
#define TFT_PINK 0xFE19 /* 255, 192, 203 */
#define TFT_BROWN 0x9A60 /* 150, 75, 0 */
#define TFT_GOLD 0xFEA0 /* 255, 215, 0 */
#define TFT_SILVER 0xC618 /* 192, 192, 192 */
#define TFT_SKYBLUE 0x867D /* 135, 206, 235 */
#define TFT_VIOLET 0x915C /* 180, 46, 226 */

文本显示

下面表格当中的函数用于控制 TFT_eSP::print/printf/println() 系列文本输出函数(可以自动换行)的坐标系统:

API 方法 功能描述
void setCursor(int16_t x, int16_t y) tft.print() 设置文本光标的 (x, y) 坐标位置。
void setCursor(int16_t x, int16_t y, uint8_t font) tft.print() 设置文本光标的 (x, y) 坐标位置和字体。
int16_t getCursorX(void) 获取当前 tft.print() 文本光标在 x 轴的坐标位置。
int16_t getCursorY(void) 获取当前 tft.print() 文本光标在 y 轴的坐标位置。

而接下来表格当中的这些方法,则用于控制 TFT_eSP::drawXxx() 系列文本输出函数(无法自动换行)的参考基准点:

API 方法 功能描述
uint8_t getTextDatum(void) 获取文本输出基准点。
void setTextDatum(uint8_t d) 设置文本输出基准点。

上面表格当中的 setTextDatum(uint8_t d) 函数的参数 d 表示基准点的位置,可供选择的枚举参数有如下这些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define TL_DATUM    0  // 基准点位于输出文本的左上角(Top left)位置,默认参数
#define TC_DATUM 1 // 基准点位于输出文本的中间顶部(Top Centre)位置
#define TR_DATUM 2 // 基准点位于输出文本的右侧顶部(Top Right)位置
#define ML_DATUM 3 // 基准点位于输出文本的左侧中间(Middle Left)位置
#define CL_DATUM 3 // 同上
#define MC_DATUM 4 // 基准点位于输出文本横向与纵向的中间(Middle Centre)位置
#define CC_DATUM 4 // 同上
#define MR_DATUM 5 // 基准点位于输出文本的右侧中间(Middle Right)位置
#define CR_DATUM 5 // 同上
#define BL_DATUM 6 // 基准点位于输出文本的底部左侧(Bottom Left)位置
#define BC_DATUM 7 // 基准点位于输出文本的底部中间(Bottom Centre)位置
#define BR_DATUM 8 // 基准点位于输出文本的底部右侧(Bottom Right)位置
#define L_BASELINE 9 // 左侧字符基准线(Left Character Baseline)
#define C_BASELINE 10 // 中间字符基准线(Centre Character Baseline)
#define R_BASELINE 11 // 右侧字符基准线(Right Character Baseline)

除此之外,TFT_eSPI 库还提供了如下一系列文本输出相关的辅助函数:

API 方法 功能描述
void setTextFont(uint8_t f) 设置当前所要显示文本的字体。
void setTextSize(uint8_t s) 设置文本放大倍数(自定义字体无效),参数 s 的取值范围介于 1 ~ 7 之间。
void setTextColor(uint16_t c) 设置字体颜色(背景为透明)。
void setTextColor(uint16_t c, uint16_t b) 设置字体的颜色以及其背景色。
void setTextWrap(bool wrapX, bool wrapY) 设置文本是否自动换行。
void setTextPadding(uint16_t x_width) 设置填充宽度(以像素为单位),将会擦除之前的文本内容。
void getTextPadding(void) 获取填充宽度(以像素为单位)。

下面的示例代码,会向 UINIO-Monitor 屏幕从上至下依次打印 Hello UinIO.com 字符串内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <SPI.h>
#include <TFT_eSPI.h>

TFT_eSPI tft = TFT_eSPI(); // 创建 TFT_eSPI 对象

void setup(void) {
tft.init(); // 重置然后初始化 TFT 显示相关的寄存器
tft.setRotation(0); // 调整屏幕方向,参数为 1 表示旋转 90° 度,为 2 表示旋转 180° 度,为 3 表示旋转 270° 度
tft.fillScreen(TFT_BLACK); // 设置 TFT 屏幕的背景颜色
tft.setTextColor(TFT_WHITE, TFT_BLACK); // 设置 TFT 屏幕显示文本字体的颜色以及显示背景色
tft.setTextSize(2); // 设置文本字体的放大倍数

/* print/f/ln 系列文本打印函数 ... */
tft.println("Hello UinIO.com"); // 换行打印字符串
tft.printf("Hello UinIO.com\n"); // 格式化打印字符串
tft.print("Hello UinIO.com"); // 直接打印字符串

/* drawXxx() 系列文本绘制函数 */
tft.drawString("Hello UinIO.com", 0, 50, 2); // 绘制字符串
tft.drawFloat(2023.12, 2, 0, 90); // 绘制符点数
tft.drawNumber(610000, 0, 115); // 绘制数字
}

void loop() {
/* ... 需要循环执行的 TFT_eSPI 显示控制代码 ... */
}

自定义字体

TFT_eSPI 库在其安装目录 TFT_eSPI\Tools\Create_Smooth_Font\Create_font 下面提供了处理自定义字体的 Create_font 工具,其中包含有如下源文件和目录:

  • data 目录:用于存放 .ttf 以及 .otf 字体文件。
  • FontFiles 目录:保存的是转换处理之后所获得的 .vlw 字体文件。
  • Create_font.pde 工程文件:用于将自定义字体,从 Unicode 编码转换为 .vlw 格式的字体文件(后续需要再进一步转换为 .h 文件)。

Processing 生成 .vlw 字体文件

Processing 是一款用于图像处理的开源编程语言与开发环境,通过其可以打开位于 TFT_eSPI 库安装目录 TFT_eSPI\Tools\Create_Smooth_Font\Create_font 下面,用于制作自定义字体的 Processing 工程文件 Create_font.pde

将下面的代码复制到 Create_font.pde 的用户配置参数注释 USER CONFIGURED PARAMETERS 所在的位置,并且替换掉原来的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//                       >>>>>>>>>> USER CONFIGURED PARAMETERS START HERE <<<<<<<<<<
int fontNumber = -1; // 字体编号,值为 -1 表示使用下面的 fontName 设置,>=0 则表示使用系统字体编号

String fontName = "SourceHanSansSC-Bold"; // 自定义字体文件的名称
// String fontType = ".ttf";
String fontType = ".otf"; // 自定义字体文件的后缀

int fontSize = 14; // 字体的像素大小

int displayFontSize = 14; // Processing 草图弹出窗口的字体大小(可以和上面的 fontSize 不同)

/* 当前所使用 Unicode 编码的起始码与结束码(适用于字符较少的英文或拉丁字体)*/
static final int[] unicodeBlocks = {
0x0021, 0x007E, // 该 Unicode 块包含了常见的符号与所有的大小写英文字母
};

/* 输入需要生成相应字体的字符的编码值(适用于中文字体)*/
static final int[] specificUnicodes = {
0x6210, 0x90fd, // 中文汉字 "成都" 的 Unicode 编码
};
// >>>>>>>>>> USER CONFIGURED PARAMETERS END HERE <<<<<<<<<<

接下来,就可以根据下面列出的步骤,依次进行相关的处理和操作:

  1. 使用在线中文转 Uinicode 工具,获得当前所需中文汉字的 Unicode 编码,然后将转换结果当中的 \u 替换为 0x,最后将结果填写到 Create_font.pde 工程源文件的 specificUnicodes() 函数里。
  2. 紧接着把思源黑体的字体文件 SourceHanSansSC-Bold.otf 复制到 TFT_eSPI\Tools\Create_Smooth_Font\Create_font 路径下面的 data 目录,同时把 Create_font.pde 里的 fontName 变量修改为字体的文件名称 SourceHanSansSC-Bold,而 fontType 变量修改为 .otf
  3. 完成上述步骤之后,点击 Processing 工具顶部的【运行】按钮,就会弹出下面的提示框,展示当前生成完毕的字体内容。

与此同时,就会在 TFT_eSPI\Tools\Create_Smooth_Font\Create_font 路径下面的 FontFiles 目录里,发现刚才已经转换完成了的 SourceHanSansSC-Bold14.vlw 文件:

1
2
3
4
D:\Workspace\Workspace_Arduino\libraries\TFT_eSPI\Tools\Create_Smooth_Font\Create_font\FontFiles
λ ls

SourceHanSansSC-Bold14.vlw System_Font_List.txt

将 .vlw 转换为 .h 头文件

接下来,继续通过在线文件十六进制转换器, 或者该网站上提供的 bin2hex.exe 程序,把前面生成的 SourceHanSansSC-Bold14.vlw 文件转换为十六进制的格式:

此时可以打开 Arduino IDE 新建一个草图工程,接着在工程文件的根目录再新建一个 font_chengdu.h 头文件(用于保存上述十六进制编码),把上面获得的十六进制编码拷贝到下面的 font_chengdu 数组变量当中,就成功创建出了可供 TFT_eSPI 库使用的 font_chengdu.h 字体头文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <pgmspace.h>

const uint8_t font_chengdu[] PROGMEM = {
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x62, 0x10, 0x00, 0x00, 0x00, 0x10,
0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFD, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0E,
0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xE9, 0x3E, 0xE3, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xC2, 0xF8, 0x2C, 0xD7, 0xFF, 0x5A, 0x00, 0x00, 0x4A, 0xA3, 0xA3, 0xA3, 0xA3,
0xA3, 0xE8, 0xFE, 0xA3, 0xAF, 0xFE, 0xAD, 0x42, 0x00, 0x73, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x67, 0x00, 0x73, 0xFF, 0x4B, 0x00, 0x00, 0x00, 0x95, 0xFF, 0x25,
0x05, 0x27, 0x00, 0x00, 0x00, 0x73, 0xFF, 0xA8, 0x83, 0x83, 0x63, 0x76, 0xFF, 0x4A, 0x58, 0xFF,
0x79, 0x00, 0x00, 0x76, 0xFF, 0xFF, 0xFF, 0xFF, 0xBD, 0x57, 0xFF, 0x6F, 0xC4, 0xFD, 0x20, 0x00,
0x00, 0x82, 0xFF, 0x3F, 0x00, 0xE4, 0xB7, 0x2E, 0xFF, 0xC4, 0xFF, 0xB8, 0x00, 0x00, 0x00, 0x8F,
0xFF, 0x29, 0x00, 0xEB, 0xB1, 0x03, 0xF5, 0xFF, 0xFF, 0x38, 0x00, 0x00, 0x00, 0x9D, 0xFF, 0x13,
0x01, 0xFA, 0xA5, 0x00, 0xC3, 0xFF, 0xA8, 0x00, 0x59, 0x00, 0x00, 0xC1, 0xFC, 0x5B, 0x95, 0xFF,
0x8B, 0x44, 0xF8, 0xFF, 0x67, 0x00, 0xF5, 0x6C, 0x06, 0xF3, 0xCF, 0x6E, 0xFF, 0xE4, 0x89, 0xF5,
0xFD, 0xFC, 0xDB, 0x3D, 0xFF, 0x58, 0x5D, 0xFF, 0x8D, 0x00, 0x01, 0x43, 0xFF, 0xF1, 0x4C, 0x74,
0xFF, 0xFF, 0xFB, 0x19, 0x2C, 0xD8, 0x26, 0x00, 0x00, 0x00, 0x7A, 0x29, 0x00, 0x00, 0x5C, 0xA4,
0x56, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x03, 0xDB, 0x74, 0x00, 0x1E, 0x15, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x5B,
0x6E, 0xFF, 0xBA, 0x68, 0xB1, 0xE2, 0xE1, 0xFF, 0xFF, 0xFF, 0xED, 0x4A, 0x00, 0xD7, 0xFF, 0xFF,
0xFF, 0xFD, 0xFD, 0x7C, 0xDF, 0xE6, 0x93, 0xCD, 0xFF, 0x55, 0x00, 0x00, 0x03, 0xFF, 0x87, 0xAB,
0xF9, 0x17, 0xDF, 0xC3, 0x00, 0xBD, 0xF1, 0x08, 0x34, 0x73, 0x76, 0xFF, 0xC7, 0xFE, 0xDB, 0x4D,
0xDF, 0xC3, 0x10, 0xFA, 0x95, 0x00, 0x73, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAB, 0xDF, 0xC3,
0x66, 0xFF, 0x2C, 0x00, 0x00, 0x00, 0x4A, 0xE7, 0xF2, 0x2F, 0x00, 0x00, 0xDF, 0xC3, 0x7B, 0xFD,
0x40, 0x00, 0x0C, 0x99, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x33, 0xDF, 0xC3, 0x01, 0xC4, 0xE0, 0x05,
0x9F, 0xFF, 0xFF, 0x94, 0x53, 0x94, 0xFF, 0x33, 0xDF, 0xC3, 0x00, 0x67, 0xFF, 0x31, 0x33, 0xF6,
0xFF, 0x87, 0x3F, 0x87, 0xFF, 0x33, 0xDF, 0xC3, 0x00, 0x48, 0xFF, 0x51, 0x00, 0x31, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x33, 0xDF, 0xC5, 0x91, 0xD7, 0xFF, 0x33, 0x00, 0x23, 0xFF, 0x94, 0x53, 0x94,
0xFF, 0x33, 0xDF, 0xC3, 0xC1, 0xF7, 0x8E, 0x00, 0x00, 0x23, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x33,
0xDF, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xE3, 0x55, 0x00, 0x3E, 0xA7, 0x22, 0xCE, 0xB4,
0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0xE6, 0x80, 0x9D, 0xE6, 0xBA, 0x90, 0xE9, 0xBB, 0x91, 0xE4,
0xBD, 0x93, 0x20, 0x42, 0x6F, 0x6C, 0x64, 0x00, 0x14, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x48,
0x61, 0x6E, 0x53, 0x61, 0x6E, 0x73, 0x53, 0x43, 0x2D, 0x42, 0x6F, 0x6C, 0x64, 0x01
};

显示自定义字体

在刚才新建的 Arduino 草图工程源代码里边,通过预处理命令 #include 包含上面建立的 font_chengdu.h 字体头文件,然后使用 loadFont() 函数加载字体,再分别通过 print()drawString() 函数向屏幕输出思源黑体的成都,最后在使用完成之后调用 unloadFont() 函数卸载字体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <TFT_eSPI.h>

#include "font_chengdu.h" // 包含自定义字库头文件

TFT_eSPI tft = TFT_eSPI();

void setup() {
/* 初始化 TFT_eSPI 设置 */
tft.init();
tft.setRotation(0);
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE);

tft.loadFont(font_chengdu); // 加载 font_chengdu 字库

tft.print("成都"); // 第 1 行输出汉字
tft.drawString("成都", 0, 16); // 第 2 行输出汉字
tft.unloadFont(); // 卸载字库资源
}

void loop() {}

TFT_eWidget 图形界面库

TFT_eSPI 库的作者还提供了一个简单小巧的 TFT_eWidget 图形界面库,不过 Arduino 当中通常使用 LVGL 结合 TFT_eSPI 来绘制图形界面,所以该库的运用并不广泛,本文就不再赘述,有需要的朋友可以直接参考开源项目当中的说明文档。

运用 U8G2 与 TFT_eSPI 玩转 UINIO-Monitor 显示屏

http://www.uinio.com/Project/UINIO-Monitor/

作者

Hank

发布于

2023-09-01

更新于

2024-09-22

许可协议