Tasmota 中的 Adafruit GFX 图形核心库:绘图原语、字体系统与显示驱动集成实战
【免费下载链接】TasmotaAlternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at项目地址: https://gitcode.com/GitHub_Trending/ta/Tasmota
Adafruit GFX 是 Adafruit 全系显示屏共用的核心图形库,为点、线、矩形、圆、位图与文本等提供统一的高层绘制 API,而把与具体屏幕硬件打交道的底层工作留给各硬件专属库。本篇文章以仓库内随 Tasmota 分发的 Adafruit-GFX-Library-1.5.6 版本(lib/lib_display/Adafruit-GFX-Library-1.5.6-gemu-1.0/)为核心,结合 Tasmota 源码中真实使用该库的显示驱动(如 8×8 LED 点阵与七段数码管),讲清类层次、核心 API、字体与位图工作流,以及如何在你的固件或 Tasmota 工程中落地使用。
一、库的定位:图形层与硬件层分离
按照库自身的定义(见 README.md),Adafruit GFX 是"所有显示屏的核心图形库",提供一整套通用图形原语(点、线、圆等)。它必须与每个显示设备对应的硬件专属库配对使用——硬件库负责底层时序、寄存器与总线通信,GFX 负责上层几何绘制。这种分层让同一套绘图代码可以在 LCD、OLED、LED 点阵、七段数码管等多种设备上无差别运行。
在 Tasmota 仓库中,该库位于lib/lib_display/目录,其配套的硬件层库Adafruit_LEDBackpack(8×8 点阵与 7 段数码管驱动器)位于同目录下的Adafruit_LED_Backpack-1.1.6。版本信息见 library.properties:
name=Adafruit GFX Library version=1.5.6 category=Display architectures=*architectures=*表示该库不绑定特定 MCU 架构;在 Adafruit_GFX.cpp 中可以看到它对 AVR、ESP8266、ESP32 等平台做了统一的pgm_read_*宏适配(第 40~67 行),使同一套代码能跨平台编译——这正是它能在 ESP8266/ESP32 的 Tasmota 固件中稳定工作的前提。
二、安装方式
README 给出的标准安装路径有两条:
- Arduino IDE Library Manager:较新的 Arduino IDE 已内置库管理器,直接搜索 "Adafruit GFX Library" 一键安装。
- 手动 ZIP 安装:下载 ZIP 压缩包,解压后将文件夹重命名为
Adafruit_GFX,确认目录内含Adafruit_GFX.cpp与Adafruit_GFX.h,再把整个文件夹放入ArduinoSketchFolder/Libraries/(首次安装可能需要自建Libraries子目录),最后重启 IDE。
注意:README 特别强调,任何再分发都必须保留文件头部的版权声明与 BSD 许可文本("All text above must be included in any redistribution")。Tasmota 仓库内的 Adafruit_GFX.cpp 第 1~32 行完整保留了这一 BSD 许可头。
在 Tasmota 的 PlatformIO 工程体系中,库的引入方式略有不同:Tasmota 将第三方库直接托管在lib/目录树下,由platformio.ini的lib_deps/lib_extra_dirs机制解析,无需手动操作 Arduino IDE。
三、类层次与核心 API
3.1 从纯虚函数drawPixel()出发
Adafruit_GFX.h 中Adafruit_GFX继承自Print,其核心约束只有一个:
class Adafruit_GFX : public Print { public: Adafruit_GFX(int16_t w, int16_t h); // 子类必须实现:向屏幕/帧缓冲写入一个像素 virtual void drawPixel(int16_t x, int16_t y, uint16_t color) = 0; ... };也就是说,一个硬件驱动库只要实现drawPixel(),就自动获得了 GFX 提供的全部高级绘图能力;其余全部方法(画线、矩形、圆、文本、位图等)都以它为地基在 Adafruit_GFX.cpp 中通用实现。例如writeLine()使用经典的 Bresenham 直线算法逐点调用writePixel()(进而落到drawPixel()),并在 ESP8266 平台上通过yield()让出 CPU 以避免看门狗复位(第 132~173 行)。
3.2 分层 API 一览
头文件将 API 分为几组,子类可逐层覆写以换取性能:
| 分组 | 方法 | 说明 |
|---|---|---|
| 核心 | drawPixel() | 纯虚函数,子类必实现 |
| Transaction API | startWrite()/writePixel()/writeFillRect()/writeFastVLine()/writeFastHLine()/writeLine()/endWrite() | 事务式批量写入,适合一次开启总线会话连续画多个图元 |
| Control API | setRotation()/invertDisplay() | 旋转与颜色反相,默认通用实现可覆写 |
| Basic Draw API | drawFastVLine()/drawFastHLine()/fillRect()/fillScreen()/drawLine()/drawRect() | 基础几何,推荐子类覆写以使用硬件加速 |
| 高级图元 | drawCircle()/fillCircle()/drawRoundRect()/fillRoundRect()/drawTriangle()/fillTriangle()/drawCircleHelper()/fillCircleHelper() | 仅由 GFX 提供,通常无需覆写 |
| 位图 | drawBitmap()/drawXBitmap()/drawGrayscaleBitmap()/drawRGBBitmap()(均有多重重载) | 1/1 位 XBM、灰度与 RGB565 位图 |
| 文本 | drawChar()/setCursor()/setTextColor()/setTextSize()/setTextWrap()/setFont()/getTextBounds()/cp437()/write() | 内置等宽字体或自定义 GFXfont |
3.3 构造与默认状态
构造函数(Adafruit_GFX.cpp 第 108~120 行)初始化了以下状态,理解它们有助于排查显示异常:
- 宽高:
WIDTH/HEIGHT为原始尺寸(旋转后不变),_width/_height随setRotation()交换; - 光标:
cursor_x = cursor_y = 0; - 文本缩放:
textsize_x = textsize_y = 1(默认不放大); - 文本颜色:
textcolor = textbgcolor = 0xFFFF(白色); - 换行:
wrap = true(超宽自动折行,否则右缘裁剪); - CP437 字符集:
_cp437 = false(默认兼容旧行为的"错误"索引表); - 自定义字体:
gfxFont = NULL(使用内置等宽字体)。
四、文本系统:内置字体、CP437 与自定义字体
4.1 内置等宽字体与 CP437 兼容位
glcdfont.c(位于 lib/lib_display/Adafruit-GFX-Library-1.5.6-gemu-1.0/glcdfont.c)提供 5×7 点阵的内置固定宽度字体,包含 255 个字符。README 与头文件特别提示了一个历史坑:glcdfont.c长期缺少第 176 号字符('light shade' 块),导致其后的字符索引全部错位。为兼容海量已发布的老代码,库默认采用"旧的错误索引"行为;如要按正确的 Code Page 437 字符值取用字符,调用:
display.cp437(true); // 启用正确的 CP437 字符索引(默认关闭)4.2 自定义字体:GFXfont 结构
自 1.1 版本起支持抗锯齿化的位图字体。字体的数据组织定义在 gfxfont.h:
// 每个字形(glyph)的元数据 typedef struct { uint16_t bitmapOffset; // 指向 GFXfont->bitmap 的偏移 uint8_t width; // 字形位图宽(像素) uint8_t height; // 字形位图高(像素) uint8_t xAdvance; // 绘制后光标前进距离(x 轴) int8_t xOffset; // 光标位置到字形左上角的 x 偏移 int8_t yOffset; // 光标位置到字形左上角的 y 偏移 } GFXglyph; // 整字体的描述 typedef struct { uint8_t *bitmap; // 所有字形位图首尾相接 GFXglyph *glyph; // 字形数组 uint8_t first; // 起始 ASCII 码 uint8_t last; // 结束 ASCII 码 uint8_t yAdvance; // 换行时 y 轴前进距离 } GFXfont;使用方法(README 原文要点):在 sketch 中#include对应字体.h文件,把GFXfont结构体的地址传给setFont();传NULL则回退到经典的固定宽度位图字体:
#include <Fonts/FreeSans18pt7b.h> display.setFont(&FreeSans18pt7b); // 切换自定义字体 display.print("Hello Tasmota"); display.setFont(); // 传 NULL,回到内置等宽字体4.3 Fonts 目录:随库分发的 50 个字型
仓库内 Fonts/ 目录包含三个家族的常规/粗体/斜体/粗斜体,以及若干装饰与极简字体,全部以pt7b结尾(7-bit ASCII 提取版):
- FreeMono(等宽):9、12、18、24 pt × 4 字重;
- FreeSans(无衬线):9、12、18、24 pt × 4 字重;
- FreeSerif(衬线):9、12、18、24 pt × 4 字重;
- 装饰/极简:
Org_01、Picopixel、Tiny3x3a2pt7b、TomThumb。
4.4 fontconvert:TTF 转 GFX 头文件的命令行工具
fontconvert/ 目录提供把 TrueType 字体转换为 Adafruit_GFX 头文件格式的命令行工具(注意不是 Arduino sketch,需在桌面环境编译运行)。它由 Peter Jakobs 的 Adafruit_ftGFX 分支工具派生而来,依赖 FreeType 库。
典型用法(README 与 fontconvert.c 第 8~9 行):
cd lib/lib_display/Adafruit-GFX-Library-1.5.6-gemu-1.0/fontconvert make # 需要系统装有 FreeType 开发库 ./fontconvert ~/Library/Fonts/FreeSans.ttf 18 > FreeSans18pt7b.h生成的FreeSans18pt7b.h放入项目的Fonts/目录即可#include使用。该工具目前只提取可打印的 7-bit ASCII 字符(first=' '到last='~',见源码第 52 行),并固定按 Adafruit 2.8 英寸 TFT 的分辨率以DPI 141换算字号(第 29 行)。转换流程为:用 FreeType 打开字体 → 逐字形渲染为灰度位图 → 按位打包成十六进制数组 → 同时输出GFXglyph索引表。makefonts.sh脚本与 fontconvert_win.md(Windows 版说明)可供参考。
五、位图绘制:drawBitmap 系列与配套工具
5.1 六类位图 API
Adafruit_GFX.h 声明了三族位图接口,每族都有只读const与可写指针两个重载(后两者通常配合从 RAM 中动态生成/修改的帧缓冲):
drawBitmap(x, y, bitmap[], w, h, color)与带bg背景色的重载:1 位单色位图,按前景/背景色渲染;drawXBitmap(x, y, bitmap[], w, h, color):X BitMap(xbm)格式,逐字节低位在前,可直接使用 GIMP 导出的.xbm文件内容;drawGrayscaleBitmap(x, y, bitmap[], w, h)(以及带mask[]透明掩膜的重载):8 位灰度图;drawRGBBitmap(x, y, bitmap[], w, h)(以及带掩膜重载):16 位 RGB565 真彩图。
5.2 配套工具:Image2Code 与 GIMP
README 提供的两条实用工作流:
- Image2Code:Java GUI 小工具,把 BMP 文件转换为可供
drawBitmap()直接使用的数组代码(项目代码位于 Adafruit 官方仓库的Img2Code分支); - GIMP + drawXBitmap:用 GIMP 将图片另存为
.xbm文件,直接使用文件中的数组调用drawXBitmap()即可绘制单色位图。
六、离屏画布 GFXcanvas 与按钮控件
6.1 三种位深画布
Adafruit_GFX.h 内置三个继承Adafruit_GFX的离屏渲染上下文,用于在 RAM 中预绘制再整体上屏(可配合部分只有帧缓冲接口的屏、或实现简单动画合成):
GFXcanvas1(w, h):1 位/像素,getBuffer()返回uint8_t *;GFXcanvas8(w, h):8 位/像素,额外覆写了writeFastHLine()以加速水平填充;GFXcanvas16(w, h):16 位/像素,getBuffer()返回uint16_t *,并提供byteSwap()(RGB565 大小端交换)。
它们重写了drawPixel()与fillScreen()以写入各自的内部缓冲。注意 16 位画布在大屏上会显著占用 RAM,使用时需评估 ESP8266/ESP32 的可用堆。
6.2 触摸按钮 Adafruit_GFX_Button
Adafruit_GFX_Button是一个绘制在 GFX 之上的简单按钮 UI 组件,同样定义在 Adafruit_GFX.h:
button.initButton(&display, cx, cy, w, h, outline, fill, textcolor, label, textsize); button.drawButton(); button.contains(touch_x, touch_y); // 命中检测 button.press(true/false); // 由触摸回调设置状态 button.justPressed(); // 边沿检测 button.justReleased(); button.isPressed();initButton()(以中心+尺寸定位)与initButtonUL()(以左上角+尺寸定位)两种初始化方式,文本缩放支持textsize与(textsize_x, textsize_y)重载,标签内部缓冲为 10 字节。
七、在 Tasmota 中的真实应用:点阵与七段数码管驱动
该库在 Tasmota 固件中的直接使用者是显示驱动层tasmota_xdsp_display/下的两个驱动文件,二者都采用"GFX 核心库 + Adafruit_LEDBackpack 硬件库"的配对模式:
7.1 8×8 LED 点阵(xdsp_03_matrix)
xdsp_03_matrix.ino 实现 Tasmota 的XDSP_03显示驱动,用于驱动 HT16K33 类 8×8 点阵:
#include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_LEDBackpack.h> // 8x8 Matrix Adafruit_8x8matrix *matrix[8]; // 最多级联 8 块点阵它基于 GFX 的setCursor()/print()/clear()组合实现了三种显示模式:
MatrixFixed(txt):固定显示,第 i 块矩阵的光标 x 偏移-i * 8以拼接长文本;MatrixCenter(txt):按"总宽度 - 文本像素宽度"居中,其中每字符按 6 像素宽估算((len < 8) ? ((mtx_matrices*8) - (len*6)) / 2 : 0);MatrixScrollLeft(txt, loop):状态机驱动的左滚动,借助disp_refresh计数分频节流刷新率,每步mtx_x--左移 1 像素,滚到-(len*6)即回到起点。
每次刷新还会调用setBrightness(GetDisplayDimmer16()),把 Tasmota 的Dimmer命令(0~100%)映射到点阵亮度,实现与 Tasmota 灯光系统的联动。
7.2 七段数码管(xdsp_11_sevenseg)
xdsp_11_sevenseg.ino 是XDSP_11驱动,同样引入Adafruit_GFX.h与Adafruit_LEDBackpack.h,创建Adafruit_7segment *sevenseg[8]对象数组以支持多块级联。该驱动通过Adafruit_7segment的print()/writeDisplay()/setBrightness()等继承自 GFX 的接口把 Tasmota 的传感器数据(温湿度等)渲染为数码管显示。
这两个驱动证明了一个事实:Tasmota 对 Adafruit GFX 的使用完全遵循"核心库 + 硬件库"的设计初衷——GFX 提供文本与绘图逻辑,硬件库负责 I2C 时序,Tasmota 只负责业务调度。
八、Roadmap 与兼容性红线
README 最后一部分明确了两条维护原则,使用该库时值得留意:
- 向后兼容是最高优先级(Prime Directive):大量老 sketch 托管在其他位置、甚至印在纸质出版物上无法修改,因此库已有机生长多年,偶尔会陷入设计死角而只能靠"略显笨拙"的兼容手段兜底;
- 不再新增字体/位图格式:目前已有两种字体格式(内置等宽 + GFXfont),代码体量已偏大,官方明确表示几乎不可能合入新的或不兼容的字体格式;需要更复杂字体能力的场景,官方建议 fork 一份按需改造。出于同样原因,也不会再增加位图格式。
九、快速上手示例
结合仓库内的 examples/mock_ili9341/mock_ili9341.ino(基于 ILI9341 SPI 屏的完整演示,包含屏幕填充、文本、直线、矩形、圆等基准测试流程),一个最小可用的 sketch 骨架如下:
#include <SPI.h> #include <Adafruit_GFX.h> #include <Adafruit_ILI9341.h> // 换成你的硬件库 #define TFT_DC 9 #define TFT_CS 10 Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC); void setup() { Serial.begin(9600); tft.begin(); tft.fillScreen(ILI9341_BLACK); tft.setCursor(0, 0); tft.setTextColor(ILI9341_CYAN); tft.setTextSize(2); tft.println("Hello GFX!"); tft.drawLine(0, 40, 240, 40, ILI9341_RED); tft.fillCircle(120, 120, 30, ILI9341_MAGENTA); } void loop() {}要点:硬件对象构造 →begin()→ 直接调用 GFX 高层 API;颜色常量(ILI9341_RED等)与文本 API 均由 Adafruit_GFX 统一提供。
结语
Adafruit GFX 的价值在于用"实现一个drawPixel()"的极低接入成本,换来一整套经过十余年验证的跨平台图形能力。在 Tasmota 中,它以 1.5.6 版本随固件分发,为 8×8 点阵、七段数码管等显示驱动提供统一的文本与绘图基础;其GFXfont字体系统、fontconvert工具链与drawBitmap系列接口,也为二次开发 LED 面板、仪表盘等显示方案提供了可复用的完整工作流。深入研究可继续阅读 Adafruit_GFX.h、Adafruit_GFX.cpp 与 Tasmota 的两个实际驱动 xdsp_03_matrix.ino、xdsp_11_sevenseg.ino。
【免费下载链接】TasmotaAlternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at项目地址: https://gitcode.com/GitHub_Trending/ta/Tasmota
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考