从 0 到 1 玩转 OLED:STM32 移植 U8g2 库实现图形、中文与动画显示全攻略
本文将从原理到实战,详细记录整个过程,包括 I2C 协议理解、OLED 工作原理、U8g2 库移植步骤,以及最终实现的显示效果,适合嵌入式初学者参考。U8g2是GitHub上一款十分优秀的开源图形库GUI库),其本质是嵌入式设备的单色图形库。在 Github 上超过3.2K Star,2.6K Commit。其开发语言90%为C语言,且代码简洁干练便于移植与后期修改。支持多种显示器(包括 OLED
文章目录
前言
本文将从原理到实战,详细记录整个过程,包括 I2C 协议理解、OLED 工作原理、U8g2 库移植步骤,以及最终实现的显示效果,适合嵌入式初学者参考。
一.基础知识储备
在动手之前,先搞懂几个核心概念,这对后续调试至关重要。
1.I2C协议基本原理
I2C 是一种两线制串行通信协议,仅需 SDA(数据线)和 SCL(时钟线)即可实现多设备通信。
- 核心时序:
- 起始信号(S):SCL 高电平时,SDA 从高→低跳变;
- 停止信号(P):SCL 高电平时,SDA 从低→高跳变;
- 数据传输:每字节 8 位,后跟 1 位应答(ACK,低电平有效);
- 速率:本次实验用 100kbps(标准模式)。
2.0.96 寸 OLED 屏工作原理
本次使用的是 SSD1306 驱动的 128×64 分辨率 OLED 屏,I2C 地址通常为 0x3C(或 0x3D)。
- 显示原理:自发光二极管阵列,每个像素对应 1 位数据(1 亮 0 暗),无需背光,功耗低;
- 缓冲区:屏幕按 “页” 划分(共 8 页,每页 8 行),数据通过 I2C 写入缓冲区后刷新显示;
3. U8g2 库简介
U8g2是GitHub上一款十分优秀的开源图形库(GUI库),其本质是嵌入式设备的单色图形库。在 Github 上超过3.2K Star,2.6K Commit。其开发语言90%为C语言,且代码简洁干练便于移植与后期修改。支持多种显示器(包括 OLED),内置字体、图形绘制函数,简化了显示逻辑。核心优势:
- 支持中文字体(需加载对应字库);
- 提供分页显示、滑动等高级功能;
- 可适配多种硬件平台(如 STM32、Arduino)。
二.硬件准备与接线
-
主控:STM32F103C8T6(最小系统板);
-
OLED 屏:0.96 寸 I2C 接口(SSD1306);
-
接线方式:
OLED 引脚 STM32 引脚 说明 VCC 3.3V 电源(勿接 5V) GND GND 地 SDA PB10 I2C 数据线 SCL PB11 I2C 时钟线
三.软件设计步骤
在移植U8g2库之前,我们需要使用CubeMX软件配置一下MCU的部分功能。
1.RCC配置

2.SYS配置

3.I2C2配置:OLED的通讯方式

4.TIM1配置:U8g2图形库需要us级延迟

5.时钟树配置

四.U8g2移植
4.1准备U8g2库文件
移植U8g2图像库需要准备好,U8g2的源码是在GitHub上开源的。 进入源码地址后下载整个U8g2图形库的压缩包。以下是下载下来的文件夹:

4.2精简U8g2库文件
U8g2支持多种显示驱动的屏幕,因为源码中也包含了各个驱动对应的文件,为了减小整个工程的代码体积,在移植U8g2时,可以删除一些无用的文件。

去掉无用的驱动文件: 这些驱动文件通常是u8x8_d_xxx.c,xxx包括驱动的型号和屏幕分辨率。ssd1306驱动芯片的OLED,使用u8x8_ssd1306_128x64_noname.c这个文件,其它的屏幕驱动和分辨率的文件可以删掉。

精简u8g2_d_setup.c:因为OLED使用的是IIC接口,所以只保留u8g2_Setup_ssd1306_i2c_128x64_noname_f这个函数就好

精简u8g2_d_memory.c:用到的u8g2_Setup_ssd1306_i2c_128x64_noname_f函数中,只调用了u8g2_m_16_8_f这个函数,所以留下这个函数


4.3将精简后的U8g2库添加到keil


五.功能实现与效果展示
5.1 U8g2 Demo 例程
调用库中自带的图形绘制函数,实现基本图形显示:
#include "stm32_u8g2.h"//main.c函数
#include "test.h"
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C2_Init();
MX_TIM1_Init();
u8g2_t u8g2;
u8g2Init(&u8g2);
while (1)
{
u8g2_FirstPage(&u8g2);
do
{
u8g2DrawTest(&u8g2);
} while (u8g2_NextPage(&u8g2));
}
}
调用库中自带的图形绘制函数,实现基本图形显示:
void u8g2DrawTest(u8g2_t *u8g2)
{
//demo例程
u8g2_DrawLine(u8g2, 0, 0, 127, 63); // 斜线
u8g2_DrawCircle(u8g2, 64, 32, 20, U8G2_DRAW_ALL); // 圆形
u8g2_DrawFrame(u8g2, 10, 10, 40, 20); // 矩形框
u8g2_SetFont(u8g2, u8g2_font_ncenB14_tr); // 设置字体
u8g2_DrawStr(u8g2, 20, 50, "U8g2 Demo"); // 字符串
}
效果展示:

5.2 显示编号和名字
注意:这里加载中文字体(如u8g2_font_wqy12_t_gb2312),字库较大,编译器不支持,所以对与中文字体,我们需要自己取模再进行显示。
首先在字模生成器中取出对应中文的编码(笔者这里用的是b站博主keysking开发的取模网站)附网站地址:波特律动LED字模生成器
进入网站后我们输入需求文字,然后选择逐行式取模即可。

将取得的编码复制到代码中:
主要代码如下:
// 自定义“廖”字16x16点阵
const unsigned char liao[] U8X8_PROGMEM = {0x00,0x01,0xfc,0x7f,0x04,0x00,0xf4,0x3e,0x94,0x24,0xa4,0x28,0x94,0x25,0x84,0x06,0x64,0x18,0x1c,0x61,0xc4,0x04,0x34,0x02,0x82,0x11,0x72,0x0c,0x81,0x03,0x70,0x00,};
// 自定义“佳”字16x16点阵
const unsigned char jia[] U8X8_PROGMEM = {0x10,0x02,0x10,0x02,0xd0,0x3f,0x08,0x02,0x08,0x02,0x0c,0x02,0xfc,0x7f,0x0a,0x00,0x09,0x02,0x08,0x02,0xc8,0x3f,0x08,0x02,0x08,0x02,0x08,0x02,0xf8,0x7f,0x08,0x00,};
// 自定义“乐”字16x16点阵
const unsigned char le[] U8X8_PROGMEM={0x00,0x04,0x00,0x0f,0xf8,0x00,0x08,0x00,0x88,0x00,0x84,0x00,0x84,0x00,0xfc,0x3f,0x80,0x00,0x90,0x04,0x90,0x08,0x88,0x10,0x84,0x20,0x82,0x20,0xa0,0x00,0x40,0x00,};
void u8g2DrawTest(u8g2_t *u8g2)
{
//显示自己的学号和名字
u8g2_ClearBuffer(u8g2);
u8g2_SetFont(u8g2, u8g2_font_ncenB12_tf); // 加粗字体
u8g2_DrawStr(u8g2, 10, 14, "ID: 123456"); // 学号
u8g2_DrawXBMP(u8g2, 10, 30, 16, 16, liao);
u8g2_DrawXBMP(u8g2, 26, 30, 16, 16, jia );
u8g2_DrawXBMP(u8g2, 42, 30, 16, 16, le );
}
效果展示:

5.3 上下 / 左右滑动显示
通过逐帧更新坐标实现滑动,先进行上下滑动在进行左右滑动
主要代码如下:
int16_t pos_x = 0; // 水平滑动位置(初始在最左)
int16_t pos_x1=16;
int16_t pos_x2=32;
int16_t pos_y = 50; // 垂直滑动位置(初始在最上)
int16_t pos_y1=44;
uint8_t state=0;
uint8_t slide_speed = 4; // 滑动步长(越大越快)
void u8g2DrawTest(u8g2_t *u8g2)
{ //上下左右滑动显示
if(state==0){
pos_y -= slide_speed; // 向上滑动
pos_y1 -= slide_speed;
}
else if(state==1){
pos_y += slide_speed; //向下滑动
pos_y1 += slide_speed;
}
else if(state==2){
pos_x-=slide_speed;
pos_x1-=slide_speed;
pos_x2-=slide_speed;
}
else {
pos_x+=slide_speed;
pos_x1+=slide_speed;
pos_x2+=slide_speed;
}
u8g2_ClearBuffer(u8g2);
u8g2_SetFont(u8g2, u8g2_font_ncenB12_tf); // 加粗字体
u8g2_DrawStr(u8g2, pos_x, pos_y1, "ID: 123456"); // 学号
u8g2_DrawXBMP(u8g2, pos_x, pos_y, 16, 16, liao);
u8g2_DrawXBMP(u8g2, pos_x1, pos_y, 16, 16, jia );
u8g2_DrawXBMP(u8g2, pos_x2, pos_y, 16, 16, le );
if (pos_y < 0&&state==0) { // 最后一行初始y=40,滑出屏幕上边界(y<0)后重置
state=1;
}
if(pos_y>48&&state==1){
state=2;
}
if(pos_x<-20&&state==2){
state=3;
}
效果展示:
上下左右
5.4 动态图案生成
将图案分解为多帧点阵,通过切换帧实现动画。以火柴人跑步为例(只用了三帧)
1.生成帧数据:用取模软件生成三帧的数据,并保存为数组;
2.多帧切换代码实现:
uint8_t current_frame = 0; // 当前帧索引
const uint16_t frame_delay = 50; // 每帧停留50ms
while (1) { // 无限循环播放动画
u8g2_FirstPage(u8g2); // 开始绘制
do {
u8g2_ClearBuffer(u8g2); // 清屏(清除缓冲区)
// 根据当前帧索引绘制对应图案
switch (current_frame) {
case 0:
// 绘制第1帧,确保宽高与点阵实际尺寸一致
u8g2_DrawXBMP(u8g2, 20, 10, 70, 64, tractor_bitmap);
break;
case 1:
// 绘制第2帧
u8g2_DrawXBMP(u8g2, 20, 10, 50, 64, tractor_bitmap1);
break;
case 2:
// 绘制第3帧
u8g2_DrawXBMP(u8g2, 20, 10, 62, 64, tractor_bitmap2);
break;
}
} while (u8g2_NextPage(u8g2)); // 刷新屏幕,完成当前帧显示
// 切换到下一帧(循环0→1→2→0)
current_frame = (current_frame + 1) % 3;
// 控制动画速度(每帧停留100ms)
HAL_Delay(frame_delay);
}
u8g2_DrawXBMP(u8g2, 20, 10, 62, 64, tractor_bitmap2);
break;
}
} while (u8g2_NextPage(u8g2)); // 刷新屏幕,完成当前帧显示
// 切换到下一帧(循环0→1→2→0)
current_frame = (current_frame + 1) % 3;
// 控制动画速度(每帧停留100ms)
HAL_Delay(frame_delay);
}
效果展示:
动图
六.虚拟逻辑分析仪去采集 I2C 引脚SDA的波形
当使用 Keil 的虚拟逻辑分析仪采集 I2C 的 SDA 引脚波形时,正常情况下应该呈现出符合 I2C 协议规范的波形特征:
- 起始信号(Start):当 SCL 为高电平时,SDA 从高电平跳转为低电平(一个下降沿)。
- 数据传输:在 SCL 的每个高电平期间,SDA 保持稳定的电平状态,代表传输的一位数据(高电平为 1,低电平为 0)。每传输 8 位数据后,会有一个 ACK/NACK 位,从机拉低 SDA 表示 ACK,保持高电平表示 NACK。
- 停止信号(Stop):当 SCL 为高电平时,SDA 从低电平跳转为高电平(一个上升沿)。
- 空闲状态:当 I2C 总线空闲时,SDA 和 SCL 都保持高电平(由上拉电阻维持)。
仿真实验结果如下:

如果在 Keil 虚拟逻辑分析仪中无法采集到符合上述特征的 I2C 波形,可以考虑以下替代方法:
- 硬件逻辑分析仪 / 示波器
这是最直接有效的方法。将逻辑分析仪或示波器的探头连接到 SDA 和 SCL 引脚(注意共地),可以实时采集真实的硬件电平变化。
- 优势:能够完全反映真实的硬件状态,不受软件仿真的限制。可以清晰地观察起始 / 停止信号、数据位、ACK/NACK 等,直接判断 I2C 协议是否正确执行。
- 适用场景:适合排查硬件连接问题(如上拉电阻缺失)、电气特性问题(如信号抖动)等。
- I2C 外设寄存器监控
在 Keil 仿真环境中,可以通过 “Peripherals → I2Cx”(如 I2C2)查看 I2C 外设的寄存器状态,间接验证 I2C 通信过程:
- SR1 寄存器:检查
SB位(起始信号发送成功)、ADDR位(地址发送成功)、TXE位(发送缓冲区空)、RXNE位(接收缓冲区非空)等。 - SR2 寄存器:检查
MSL位(主机模式)、BUSY位(总线忙)、ACK位(是否收到应答)等。
如果这些寄存器的状态符合预期(如发送地址后ADDR置位,且ACK有效),说明 I2C 协议在软件层面已经正确执行,问题可能出在虚拟逻辑分析仪的配置或硬件上。
- GPIO 模拟 I2C 调试
如果使用硬件 I2C 外设难以排查问题,可以临时改用软件模拟 I2C(用 GPIO 手动控制 SCL 和 SDA)。通过在代码中插入延时和断点,逐步验证每一步的时序:
例如,在发送起始信号、每一位数据、ACK 位后设置断点,通过虚拟逻辑分析仪观察 SDA/SCL 电平是否符合预期,逐步定位时序错误。
七.总结
本文详细记录了基于 STM32F103C8T6 与 0.96 寸 SSD1306 OLED 屏的 U8g2 库移植及应用过程。首先介绍了 I2C 协议、OLED 工作原理和 U8g2 库的核心知识,随后讲解了硬件接线(OLED 的 SDA 接 PB10、SCL 接 PB11)与 CubeMX 配置(含 RCC、SYS、I2C2、TIM1 及时钟树),并分步说明 U8g2 库的精简(保留 SSD1306 驱动相关文件)与移植步骤。最终通过实例实现了基础图形显示、中文姓名与学号显示(自定义字模)、滑动动画及动态图案生成,并补充了 I2C 波形的采集与调试方法,为嵌入式初学者提供了完整的 OLED 显示开发指南。
源码地址:https://gitee.com/liao-jiale-lls/oled1.git
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐

所有评论(0)