
简介u8glib_arduino 是面向 Arduino 开发者的轻量级图形显示库专为在低功耗、小内存的嵌入式平台上驱动 LCD 与 OLED 屏幕而设计。它支持 SSD1306、SH1106、PCF8574 字符型 LCD 等多种显示设备覆盖 I2C、SPI 与串行接口提供文本输出、图形绘制、位图显示等丰富功能适合制作仪表盘、数据可视化界面、小型游戏等交互项目对刚接触嵌入式图形编程的爱好者与进阶开发者均有参考价值。资源包共 129 个文件以 105 个 c 源文件为核心辅以 18 个 ino 示例、2 个 h 头文件、1 个 cpp 及若干 txt、changelog 说明文档压缩包约 1.05MB目录结构清晰便于按模块查阅底层驱动与示例代码。目前已有 724 人学习下载。通过研读源码与示例读者可掌握 u8g 对象初始化、drawStr/drawCircle/drawBox 等绘图命令的调用方式理解 firstPage 与 nextPage 的刷新机制并积累内存管理、显示效率与功耗控制方面的排错思路。1. 老屏点亮那点事u8glib_arduino 到底解决什么问题手里攒了几块 12864 单色液晶ST7920、SSD1306、PCD8544 各来一块想用 Arduino 统一驱动结果每个屏的初始化序列、显存布局、字库格式都不一样光看数据手册就能耗掉一整个周末。u8glib_arduino 就是冲着这个场景来的它把几十种单色屏的底层时序、页地址映射、读写方向全部封装成统一的绘图 API你只调 drawStr、drawLine、drawBitmap换屏时改一行构造参数就能跑。它适合谁适合手上有老式单色屏、想在资源受限的 8 位单片机上做菜单、波形、仪表盘又不想为每块屏重写驱动的嵌入式开发者。这个库不是新东西但它在低端屏驱动这块的覆盖面至今没有几个库能完全替代。2. 把 u8glib_arduino 装进 Arduino IDE从库目录到第一行像素2.1 库的获取与目录结构确认u8glib_arduino 的发布形式是标准的 Arduino 库压缩包解压后目录名通常带版本后缀。Arduino IDE 只认根目录下有src或直接有.h/.cpp的库文件夹所以解压后如果多套了一层同名目录要手动把内层提出来。常见做法是放到用户目录下的libraries里而不是 IDE 安装目录这样升级 IDE 不会丢库。# 以 Linux 为例Arduino 用户库目录通常在 ~/Arduino/libraries # 假设压缩包解压后得到 u8glib_arduino 文件夹 unzip u8glib_arduino.zip -d ~/Arduino/libraries/ # 确认目录结构根目录下应直接看到 src 或 .h 文件 ls ~/Arduino/libraries/u8glib_arduino/ # 期望输出类似src examples library.properties README.md逻辑说明Arduino IDE 1.5 以后支持src子目录递归编译所以库根目录下有src是正常结构。如果解压出来是u8glib_arduino/u8glib_arduino/srcIDE 会找不到头文件必须把内层目录整体移到libraries下。参数上唯一要注意的是目录名不要带空格和中文否则 Windows 下编译会报路径错误。2.2 构造函数的选型U8GLIB_* 类名怎么挑u8glib_arduino 不给一个通用构造函数而是为每种屏和接线方式预定义了类名比如U8GLIB_ST7920_128X64_1X、U8GLIB_SSD1306_128X64、U8GLIB_PCD8544。类名里的数字和字母编码了分辨率、接口类型和是否使用硬件 SPI。选错类名不会报编译错误但屏幕会白屏或花屏这是新手最容易翻车的地方。#include U8glib.h // ST7920 128x64使用软件 SPICS 接 pin 10 // 类名后缀 _1X 表示单屏、软件模拟 SPI 时序 U8GLIB_ST7920_128X64_1X u8g(10); // SSD1306 128x64I2C 接口地址默认 0x3C // U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE); void setup() { // 设置显示旋转方向0 为默认 u8g.setRot180(); } void loop() { u8g.firstPage(); do { u8g.setFont(u8g_font_6x10); u8g.drawStr(0, 12, Hello u8glib); } while (u8g.nextPage()); }逻辑说明firstPage()和nextPage()构成一个 do-while 循环这是 u8glib 特有的分页渲染机制。它不维护完整显存缓冲而是把绘制命令分页写入屏幕控制器所以内存占用极低但代价是每帧都要重新执行整个绘制循环。参数上构造函数括号里的数字是 CS 或 DC 引脚号I2C 屏则传U8G_I2C_OPT_NONE或指定地址。setRot180()在初始化阶段调用一次即可放在loop里会反复翻转导致闪烁。2.3 最小验证让屏幕出现可读字符烧录后如果屏幕全黑先别怀疑库用三用表量背光和对比度引脚。ST7920 这类屏需要 VLCD 引脚有合适电压才能出字很多模块板载了电位器但出厂位置不一定对。软件侧可以先用drawBox画一个填充矩形确认控制器在响应再换drawStr排查字库问题。void loop() { u8g.firstPage(); do { // 先画一个实心矩形验证像素寻址是否正常 u8g.drawBox(0, 0, 20, 20); // 再画字符验证字库和坐标 u8g.setFont(u8g_font_unifont); u8g.drawStr(25, 15, OK); } while (u8g.nextPage()); delay(500); }逻辑说明drawBox(x, y, w, h)的坐标原点在左上角x 向右增y 向下增。如果矩形出现但字符不出现问题在字库选择或setFont未生效如果矩形位置偏移检查setRot180或屏幕初始化方向。u8g_font_unifont是内置的 Unicode 字体体积较大8 位机上如果 flash 紧张可以换u8g_font_6x10。延时 500ms 是为了让分页刷新肉眼可见实际产品中应去掉。3. 绘图 API 的边界坐标、字体与显存分页的真实约束3.1 坐标系统与裁剪行为u8glib 的坐标是 16 位有符号整数但实际有效范围由屏幕分辨率决定。超出边界的绘制不会崩溃而是被裁剪掉这个特性在画滚动波形时很有用但也会掩盖坐标计算错误。比如你想在 128 宽屏幕上画一条从 -10 到 140 的线库会静默裁掉两端看起来像线变短了实际是坐标越界。// 画一条超出屏幕范围的线观察裁剪行为 u8g.drawLine(-20, 32, 150, 32); // 实际渲染结果从 x0 到 x127 的横线逻辑说明裁剪在库内部通过边界判断实现不消耗额外显存。参数上drawLine的四个参数都是u8g_uint_t类型传负数会被隐式转换所以不要依赖负数坐标做偏移应该在调用前自己 clamp。常见误用是用drawStr时 y 坐标传 0结果字符顶部被切掉因为 y 是基线位置不是顶部。3.2 字体选择的取舍flash 占用与可读性u8glib 内置字体从 4x6 到 10x20 不等还有 Unicode 字体。8 位 AVR 的 flash 通常只有 32KB选u8g_font_unifont会占掉好几 KB留给程序的空间就很紧张。我的习惯是菜单和状态用u8g_font_6x10标题用u8g_font_7x14B只有必须显示中文时才上 Unicode 字体并且把中文字库单独裁剪。字体名字符宽x高大致 flash 占用适用场景u8g_font_4x64x6约 0.5KB密集数据、小屏u8g_font_6x106x10约 1KB通用菜单、状态u8g_font_7x14B7x14约 1.5KB标题、强调u8g_font_unifont可变约 8KB 以上多语言、中文逻辑说明字体数据存放在 flash 中运行时通过setFont切换指针。参数上setFont只改变后续绘制使用的字体不影响已绘制内容。如果 flash 实在不够可以用u8g_font_6x10配合自定义点阵数组只取需要的几个汉字。3.3 分页渲染与内存占用的关系u8glib 的核心设计是分页渲染它不分配全屏缓冲而是把屏幕按页通常是 8 行像素一页分多次写入。firstPage重置页指针nextPage推进到下一页并返回是否还有页。这意味着你的绘制代码会被执行多次每次只渲染当前页的内容。如果绘制代码里有副作用比如改变量、发串口会重复执行这是血泪经验。int counter 0; // 全局变量 void loop() { u8g.firstPage(); do { counter; // 错误每页都会加一次 u8g.drawStr(0, 10, String(counter).c_str()); } while (u8g.nextPage()); }逻辑说明上面的代码在 128x64 屏幕上会执行 8 次counter因为屏幕分 8 页。正确做法是把状态更新放在firstPage之前绘制循环里只读不写。参数上nextPage返回 0 时表示所有页已渲染完循环退出。这个机制让 u8glib 在 2KB RAM 的 ATmega328P 上也能驱动 128x64 屏代价是绘制逻辑必须无副作用。4. 避坑与排查u8glib_arduino 最常见的五类翻车4.1 屏幕全黑但背光亮现象烧录后背光正常屏幕没有任何像素变化。原因对比度电压不对或者构造函数选错接口类型。解决先调模块上的电位器用drawBox画实心块确认控制器响应如果仍无反应检查类名是否匹配屏型号比如把 I2C 屏误用成 SPI 类名。ST7920 在并行模式下还需要检查 PSB 引脚电平。4.2 显示内容闪烁或残影现象每帧刷新时旧内容没清干净出现叠字。原因firstPage/nextPage循环里没有清屏或者清屏命令放在循环外。解决u8glib 不会自动清屏需要在绘制前调用u8g.drawBox(0, 0, width, height)填充背景或者用u8g.setColorIndex(0)后画全屏矩形。注意清屏也要放在分页循环内否则只清当前页。4.3 编译报错 “U8glib.h: No such file”现象IDE 提示找不到头文件。原因库目录层级不对或者库文件夹名带特殊字符。解决确认libraries/u8glib_arduino/下直接有U8glib.h或src/U8glib.h。如果是从 GitHub 下载的源码包可能缺少library.properties手动补一个或者把src内容提到根目录。Windows 下路径不要有中文和空格。4.4 汉字显示乱码或方块现象英文正常中文变成乱码。原因源文件编码不是 UTF-8或者字体不支持对应汉字。解决Arduino IDE 默认保存为 UTF-8但某些编辑器会存成 GBK。用u8g_font_unifont时字符串里的汉字必须是 UTF-8 编码。如果字体里没有该字会显示空白或方块需要换用带该字的字库或自己取模。4.5 刷新率低到肉眼可见现象波形更新卡顿一帧要几百毫秒。原因软件 SPI 时序慢或者每帧绘制内容太多。解决换用硬件 SPI 类名如U8GLIB_ST7920_128X64_1X换成带HW后缀的版本减少每帧绘制图元数量避免在循环里用String拼接。I2C 屏可以降低 I2C 速率到 400kHz 以下反而更稳但刷新率会进一步下降需要权衡。5. 进阶技巧用 u8glib 做流畅波形与低内存菜单5.1 环形缓冲实现滚动波形在 128x64 屏上画实时波形常见做法是维护一个环形缓冲每次新采样到来时覆盖最旧数据然后全屏重绘。u8glib 的分页机制下重绘 128 个点大约需要 8 次页循环在 16MHz AVR 上可以做到 20fps 以上。关键是把采样和绘制分离采样在定时器中断里做绘制在主循环里做。#define WAVE_LEN 128 uint8_t waveBuf[WAVE_LEN]; uint8_t writeIdx 0; void loop() { // 模拟新采样实际应来自 ADC 或传感器 waveBuf[writeIdx] analogRead(A0) 2; // 10位转8位 writeIdx (writeIdx 1) % WAVE_LEN; u8g.firstPage(); do { for (uint8_t x 0; x WAVE_LEN; x) { uint8_t idx (writeIdx x) % WAVE_LEN; uint8_t y 63 - (waveBuf[idx] 1); // 映射到0-63 u8g.drawPixel(x, y); } } while (u8g.nextPage()); }逻辑说明writeIdx指向下一个要写入的位置绘制时从writeIdx开始读保证最旧数据在左边。analogRead返回 0-1023右移 2 位得到 0-255再右移 1 位映射到 0-63 的 y 坐标。参数上WAVE_LEN等于屏幕宽度这样每个 x 对应一个采样点。如果屏幕更宽可以增大缓冲但 RAM 占用也会增加ATmega328P 的 2KB RAM 下 128 字节缓冲是安全的。5.2 低内存菜单的状态机写法用 u8glib 做多级菜单不要用递归或大数组存菜单项而是用状态机加函数指针。每个菜单页是一个函数负责绘制和响应按键。这样 flash 占用小RAM 几乎不增加。按键扫描放在主循环用millis做消抖。enum MenuState { MENU_MAIN, MENU_SETTING, MENU_ABOUT }; MenuState state MENU_MAIN; void drawMain() { u8g.setFont(u8g_font_6x10); u8g.drawStr(0, 12, Main); u8g.drawStr(0, 24, Setting); u8g.drawStr(0, 36, About); } void loop() { u8g.firstPage(); do { switch (state) { case MENU_MAIN: drawMain(); break; case MENU_SETTING: /* drawSetting() */ break; case MENU_ABOUT: /* drawAbout() */ break; } } while (u8g.nextPage()); // 按键处理略根据按键改变 state }逻辑说明state变量决定当前绘制哪个菜单页切换菜单只改变量不重建界面。参数上每个绘制函数内部用setFont设定自己的字体互不干扰。按键消抖建议用 20ms 的millis窗口避免在分页循环里读按键导致重复触发。这种写法在 32KB flash 的板子上可以塞下十几个菜单页。5.3 验证刷新率与内存占用的实用手段想知道当前配置下 u8glib 的实际刷新率和 RAM 余量可以在loop里翻转一个空闲 IO用示波器测周期RAM 余量则用freeMemory()函数需自己实现读__brkval和栈指针在串口打印。我的习惯是每加一个绘制元素就测一次帧时间超过 50ms 就说明该优化了。另外u8g.setFont调用次数越少越好字体切换有开销能在初始化时定好就定好。注意分页渲染下任何在firstPage/nextPage循环内的串口打印都会重复多次调试信息务必放在循环外。我自己的教训是早期做菜单时把String拼接放在绘制循环里结果每页都重新分配内存跑几分钟就死机。后来改成固定字符数组问题消失。这个库不复杂但它的分页模型和常规显存驱动完全不同理解这一点后面就顺了。希望帮到你。本文还有配套的精品资源点击获取