从0到1玩转OLED:STM32移植U8g2库(中文显示+动画+波形分析)全攻略
本文详细介绍STM32移植U8g2图形库驱动0.96寸OLED(SSD1306)的全过程。内容涵盖I2C通信协议、SSD1306工作原理、U8g2库核心结构等基础知识,硬件接线配置要点,以及STM32CubeMX软件配置步骤。重点讲解U8g2库的精简移植方法,包括实现底层I2C通信函数、微秒级延时等关键代码。最后通过基础图形显示示例验证移植效果,为后续中文显示、动画等高级功能奠定基础。全文注重实操
从0到1玩转OLED:STM32移植U8g2库(中文显示+动画+波形分析)全攻略
文章目录
一、前言
OLED(有机发光二极管)凭借自发光、高对比度、低功耗、响应速度快等优势,成为嵌入式开发中常用的显示模块。本文以0.96寸I2C接口OLED(SSD1306驱动) 和STM32F103C8T6为核心,详细讲解U8g2图形库的移植过程,以及基础图形、中文显示、滑动动画、帧动画的实现方法。
全程兼顾原理与实操,从硬件接线、软件配置到代码编写、波形验证,一步步拆解,适合嵌入式初学者入门,也可作为项目开发的参考手册。
二、核心基础知识储备
1. I2C通信协议关键要点
OLED与STM32的通信基于I2C协议,需掌握以下核心内容:
- 总线结构:仅需SDA(数据线)和SCL(时钟线),两根线均需接4.7kΩ上拉电阻至3.3V,默认空闲状态为高电平;
- 核心时序:
- 起始信号(S):SCL高电平时,SDA从高→低(下降沿),触发通信开始;
- 停止信号(P):SCL高电平时,SDA从低→高(上升沿),结束通信;
- 数据传输:SCL高电平时SDA电平稳定(数据有效),低电平时可切换电平;每字节8位(先传高位),后跟1位应答信号(ACK/NACK);
- 速率与地址:标准模式速率100kbps,OLED默认I2C地址为0x3C(部分为0x3D,可通过修改硬件引脚切换)。
2. SSD1306 OLED工作原理
- 分辨率:0.96寸屏常见128×64像素(横向128点,纵向64点);
- 显示驱动:SSD1306是专门的OLED控制芯片,负责接收I2C指令和数据,驱动像素点亮/熄灭;
- 存储结构:屏幕分为8页(每页8行像素),每页对应128字节数据(1字节控制8个纵向像素),数据按“页地址+列地址”的方式写入。
3. U8g2库核心优势与结构
U8g2是一款开源的单色图形库,适配多种嵌入式平台和显示模块,核心优势:
- 支持近百种显示驱动(含SSD1306),无需手动编写底层驱动;
- 内置丰富的字体(ASCII、中文、日文等)和图形函数(直线、矩形、圆形等);
- 支持分页显示、动画、滚动等高级功能,代码轻量化,移植性强。
U8g2库结构分为三层:
- 底层:硬件接口(I2C/SPI通信函数);
- 中层:驱动层(SSD1306等芯片的控制逻辑);
- 上层:应用层(图形绘制、文字显示等API)。
三、硬件准备与接线实操
1. 硬件清单
- 主控:STM32F103C8T6最小系统板;
- 显示模块:0.96寸I2C接口OLED(SSD1306驱动);
- 辅助配件:4.7kΩ电阻2个、杜邦线若干、USB转TTL模块(调试用)、ST-Link下载器。
2. 接线说明(核心!接错必通信失败)
| OLED引脚 | STM32引脚 | 备注说明 |
|---|---|---|
| VCC | 3.3V | 供电(严禁接5V,会烧毁OLED) |
| GND | GND | 共地(确保电平参考一致) |
| SDA | PB11 | I2C数据线(可自定义,需与软件配置一致) |
| SCL | PB10 | I2C时钟线(同上) |
关键提醒:SDA和SCL必须串联4.7kΩ上拉电阻至3.3V,否则I2C总线信号不稳定,会导致通信失败。
四、软件配置全流程(STM32CubeMX)
1. 新建工程与芯片选型
- 打开STM32CubeMX,选择“File→New Project”;
- 在搜索框输入“STM32F103C8T6”,选择对应芯片,点击“Start Project”;
- 弹出“Pinout Configuration”界面,开始配置外设。
2. 基础配置(RCC+SYS)
- RCC配置:选择“HSE”(外部晶振)作为时钟源,勾选“Crystal/Ceramic Resonator”;
- SYS配置:调试模式选择“Serial Wire”(SWD调试),避免占用串口资源。
3. I2C外设配置(核心通信接口)
- 找到PB10和PB11引脚,分别设置为“I2C1_SCL”和“I2C1_SDA”;
- 进入“I2C1”配置界面:
- Mode:选择“I2C”(而非SMBus);
- Clock Speed:设置为100kHz(标准模式);
- GPIO Settings:默认“Alternate Function Open Drain”(复用开漏模式,I2C必需);
- 其他参数保持默认(地址模式7位,无PEC校验)。
4. 时钟树配置(确保I2C速率稳定)
- 进入“Clock Configuration”界面;
- 外部晶振(HSE)设为8MHz;
- PLL倍频系数设为9,系统时钟(SYSCLK)=8MHz×9=72MHz;
- APB1预分频设为2,APB1时钟=36MHz(I2C1挂载于APB1,速率=APB1时钟/2=18MHz,需确保I2C分频后为100kHz);
- 点击“OK”保存配置。
5. 生成代码
- 进入“Project Manager”界面,设置工程名称、保存路径,工具链选择“MDK-ARM”;
- 点击“Code Generator”,勾选“Generate peripheral initialization as a pair of .c/.h files per peripheral”;
- 点击“Generate Code”生成Keil工程,生成完成后打开工程。
五、U8g2库深度移植(避坑指南)
1. 下载U8g2库
从GitHub下载U8g2源码:https://github.com/olikraus/u8g2
下载后解压,核心文件在“csrc”文件夹中。
2. 精简库文件(减小工程体积)
U8g2库支持多种驱动和字体,无需全部导入,按需精简:
- 驱动文件:保留SSD1306 128×64对应的驱动文件
u8x8_ssd1306_128x64.c,删除其他u8x8_xxx.c驱动文件; - 初始化文件:打开
u8g2_setup.c,仅保留u8g2_Setup_ssd1306_i2c_128x64_noname_f函数(适配0.96寸OLED),删除其他初始化函数; - 内存管理文件:打开
u8g2_d_memory.c,保留u8g2_m_16_f函数(u8g2_Setup_ssd1306_i2c_128x64_noname_f依赖此函数); - 字体文件:保留常用字体(如
u8g2_font_ncenB14_tr.c(ASCII字体)、u8g2_font_wqy12_t_gb2312.c(中文简体字体)),删除其他无用字体。
3. 导入库文件到Keil工程
- 在Keil工程中新建“U8g2”文件夹,下分子文件夹“src”(存放库源码)和“font”(存放字体文件);
- 将精简后的
.c文件复制到对应文件夹,在Keil中右键“Add Files to Group”添加文件; - 配置头文件路径:进入“Options for Target”→“C/C++”→“Include Paths”,添加U8g2头文件所在路径。
4. 实现U8g2硬件接口函数(关键!移植核心)
U8g2需要底层I2C通信函数支持,需在u8g2_hw.c中实现以下函数:
#include "u8g2.h"
#include "i2c.h"
// I2C发送函数(U8g2调用此函数与OLED通信)
uint8_t u8g2_hw_i2c_send(u8g2_t *u8g2, uint8_t addr, uint8_t *data, uint16_t len) {
return HAL_I2C_Master_Transmit(&hi2c1, addr<<1, data, len, 0xFFFF) == HAL_OK ? 0 : 1;
}
// 延时函数(U8g2需要微秒级延时)
void u8g2_hw_delay(uint16_t us) {
__HAL_TIM_SET_COUNTER(&htim1, 0);
while (__HAL_TIM_GET_COUNTER(&htim1) < us);
}
// U8g2初始化(绑定硬件接口)
void u8g2_init_hw(u8g2_t *u8g2) {
u8g2_SetI2CAddress(u8g2, 0x3C); // OLED的I2C地址
u8g2_SetUserPtr(u8g2, &hi2c1); // 传递I2C句柄
u8g2_SetBusClock(u8g2, 100000); // I2C速率100kHz
}
避坑提醒:微秒级延时需配置定时器(如TIM1)为计数模式,时钟频率1MHz(72MHz分频72倍),确保延时精度。
六、功能实现与效果展示
1. 基础图形显示(验证移植成功)
调用U8g2内置API绘制直线、矩形、文字,验证移植是否成功:
#include "main.h"
#include "i2c.h"
#include "tim.h"
#include "u8g2.h"
#include "u8g2_hw.h"
u8g2_t u8g2; // U8g2对象
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
MX_TIM1_Init();
HAL_TIM_Base_Start(&htim1); // 启动定时器(用于微秒延时)
// 初始化U8g2
u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8g2_hw_i2c_send, u8g2_hw_delay);
u8g2_init_hw(&u8g2);
u8g2_InitDisplay(&u8g2);
u8g2_SetPowerSave(&u8g2, 0); // 唤醒OLED
while (1) {
u8g2_FirstPage(&u8g2);
do {
// 绘制直线(左上角(0,0)到右下角(127,63))
u8g2_DrawLine(&u8g2, 0, 0, 127, 63);
// 绘制矩形框(左上角(10,10),宽50,高30)
u8g2_DrawFrame(&u8g2, 10, 10, 50, 30);
// 设置ASCII字体(14号加粗)
u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr);
// 绘制文字(坐标(2,50))
u8g2_DrawStr(&u8g2, 2, 50, "OLED Test!");
} while (u8g2_NextPage(&u8g2)); // 分页显示
HAL_Delay(500);
}
}
效果展示:OLED屏成功显示直线、矩形框和“OLED Test!”文字,无乱码、无闪烁,说明移植成功。
2. 中文显示(解决字库过大问题)
U8g2内置中文汉字库体积较大(动辄几MB),嵌入式MCU Flash可能不足,推荐自定义字模实现中文显示:
(1)生成中文点阵字模
- 打开在线字模生成器(如“点阵字库DIY”:http://www.51hei.com/biancheng/51tools/zdm/ );
- 输入需显示的中文(如“嵌入式开发”),设置参数:
- 字模格式:C语言数组;
- 点阵大小:16×16(适合0.96寸屏);
- 取模方式:逐行式、顺向;
- 生成字模数据,复制到代码中。
(2)中文显示代码实现
// 自定义16×16中文点阵字模(“嵌入式开发”)
const unsigned char chinese_font[][32] = {
// 嵌:0x8DDF
{0x00,0x00,0x7F,0xFC,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x7F,0xFC,0x01,0x00,
0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x7F,0xFC,0x00,0x00,0x00,0x00,0x00,0x00},
// 入:0x5165
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0xF8,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
// 式:0x5F0F
{0x00,0x00,0x00,0x00,0x1F,0xF0,0x10,0x10,0x10,0x10,0x10,0x10,0x1F,0xF0,0x10,0x10,
0x10,0x10,0x10,0x10,0x1F,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
// 开:0x5F00
{0x00,0x00,0x3F,0xF8,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,
0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x3F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00},
// 发:0x53D1
{0x00,0x00,0x00,0x00,0x1F,0xF0,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00},
七、效果展示
oled
八、心得体会
这次 OLED+U8g2 库移植让我深刻理解了 “分层开发” 的精髓。起初直接照搬网上的移植教程,却忽略了 U8g2 库 “底层硬件接口 - 中层驱动 - 上层应用” 的三层结构,导致通信失败。直到拆解库文件后才明白,移植的关键不是盲目复制代码,而是让底层 I2C 通信函数与 U8g2 的 API 适配 —— 比如必须实现u8g2_hw_i2c_send函数作为库与 STM32 硬件的桥梁,还要保证微秒级延时的精度。尤其是精简库文件时,删除多余驱动和字体后,工程体积从几 MB 缩小到几十 KB,既避免了 Flash 不足的问题,也让代码运行更高效。这让我意识到,嵌入式库移植不是 “拿来主义”,而是要摸清库的结构逻辑,针对性适配硬件,才能让功能稳定运行。
OLED 开发的细节里藏着很多容易忽略的 “坑”,这次实战让我印象深刻。硬件上,一开始忘记给 SDA 和 SCL 接 4.7kΩ 上拉电阻,导致 I2C 总线信号不稳定,OLED 始终无响应,用万用表测量引脚电平才发现问题;软件上,中文显示时直接使用 U8g2 内置中文字库,却因字库体积过大超出 MCU Flash 容量,编译报错。后来改用自定义字模生成器,按需生成所需汉字的点阵数据,不仅解决了容量问题,还让显示更精准。还有 I2C 地址的坑 —— 部分 OLED 地址是 0x3D 而非默认的 0x3C,初期因地址不匹配导致通信失败,修改代码后才恢复正常。这些经历让我明白,嵌入式开发容不得半点马虎,硬件接线、参数配置、库文件适配等每个细节,都可能决定项目的成败。
从基础图形显示到中文、动画效果的实现,我逐渐体会到嵌入式开发 “功能实现” 与 “体验优化” 的区别。初期实现滑动动画时,直接修改坐标值导致画面卡顿,后来通过调整滑动速度(slide_speed)和帧间隔(frame_delay),让动画更流畅;中文显示时,通过调整字体点阵大小(16×16)和显示坐标,避免了文字重叠或超出屏幕的问题。更重要的是,用虚拟逻辑分析仪验证 I2C 波形时,清晰看到了起始信号、数据传输、应答信号的完整时序,不仅验证了通信的正确性,还让我对 I2C 协议的理解从理论落到了实处。这让我意识到,嵌入式开发不只是让功能 “能用”,更要通过细节优化让体验 “好用”,而仪器工具的使用和底层原理的掌握,正是优化的核心支撑。
更多推荐



所有评论(0)