摘要:本文从零讲解 STM32 驱动 OLED 屏的完整流程,涵盖硬件接线(I2C/SPI)、SSD1306 驱动代码编写(初始化、显示字符/字符串/汉字)及 U8g2 等第三方库推荐,并附完整示例。
目录
- 1. 引言
- 2. OLED 显示屏基础知识
- 2.1 常见 OLED 屏规格
- 2.2 驱动芯片 SSD1306 简介
- 2.3 显示原理
- 3. 硬件接线
- 3.1 I2C 接口接线(推荐)
- 3.2 SPI 接口接线
- 4. 开发环境准备
- 4.1 使用 STM32CubeMX 配置 I2C
- 5. OLED 驱动代码编写
- 5.1 底层 I2C 通信函数
- 5.2 SSD1306 初始化
- 5.3 显示单个字符
- 5.4 显示字符串
- 5.5 显示汉字
- 6. 完整示例:显示传感器数据
- 7. 常用第三方库推荐
- 7.1 U8g2 库
1. 引言
OLED(Organic Light-Emitting Diode,有机发光二极管)显示屏凭借其自发光、高对比度、低功耗、超薄等优势,在嵌入式开发中得到了广泛应用。在 STM32 项目中,OLED 屏常用于显示传感器数据、系统状态、菜单界面等信息,是调试和交互的重要窗口。
本文将带你从零开始,掌握 STM32 驱动 OLED 屏的完整流程,包括硬件接线、驱动芯片选择、I2C/SPI 通信协议、常用库的使用,以及完整的代码编写示例。
2. OLED 显示屏基础知识
2.1 常见 OLED 屏规格
| 尺寸 | 0.96 英寸、1.3 英寸、1.54 英寸等 |
| 分辨率 | 128×64、128×32、96×64 等 |
| 颜色 | 单色(蓝/白/黄蓝双色)、RGB 彩色 |
| 驱动芯片 | SSD1306(0.96 寸)、SH1106(1.3 寸)等 |
| 通信接口 | I2C、SPI、并行 8080/6800 |
其中,0.96 英寸 128×64 分辨率、SSD1306 驱动芯片、I2C 接口的 OLED 屏最为常见,也是本文讲解的重点。
2.2 驱动芯片 SSD1306 简介
SSD1306 是一款单芯片 CMOS OLED/PLED 驱动器,内置 128×64 点阵的显示 RAM(GDDRAM)。它支持:
- I2C 接口(地址通常为 0x78 或 0x7A)
- SPI 接口(4 线或 3 线)
- 128×64 像素点阵显示
- 内部电荷泵,无需外部高压电源
2.3 显示原理
OLED 屏的每个像素点对应 GDDRAM 中的一个位(bit)。当某位为 1 时,对应像素点亮;为 0 时熄灭。SSD1306 将 128×64 的显示区域划分为 8 页(Page),每页 8 行像素,共 128 列。
页结构示意:
Page 0: 行 0-7
Page 1: 行 8-15
…
Page 7: 行 56-63
3. 硬件接线
3.1 I2C 接口接线(推荐)
I2C 接口只需两根数据线,占用引脚少,接线简单。
| VCC | 3.3V | 电源正极 |
| GND | GND | 电源负极 |
| SCL | PB6(I2C1_SCL) | 时钟线 |
| SDA | PB7(I2C1_SDA) | 数据线 |
注意:部分 OLED 模块板载稳压电路,可接 5V;若没有稳压,务必接 3.3V,否则可能烧毁屏幕。
3.2 SPI 接口接线
SPI 接口传输速度更快,但需要更多引脚。
| VCC | 3.3V | 电源正极 |
| GND | GND | 电源负极 |
| SCK | PB13(SPI2_SCK) | 时钟线 |
| MOSI | PB15(SPI2_MOSI) | 数据线 |
| CS | PB12 | 片选信号 |
| DC | PB11 | 数据/命令选择 |
| RES | PB10 | 复位信号 |
下面是 SPI 接口的接线示意图,箭头方向表示信号流向:
#mermaid-svg-k1gItr4hzZPumKIa{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-k1gItr4hzZPumKIa .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-k1gItr4hzZPumKIa .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-k1gItr4hzZPumKIa .error-icon{fill:#552222;}#mermaid-svg-k1gItr4hzZPumKIa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-k1gItr4hzZPumKIa .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-k1gItr4hzZPumKIa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-k1gItr4hzZPumKIa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-k1gItr4hzZPumKIa .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-k1gItr4hzZPumKIa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-k1gItr4hzZPumKIa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-k1gItr4hzZPumKIa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-k1gItr4hzZPumKIa .marker.cross{stroke:#333333;}#mermaid-svg-k1gItr4hzZPumKIa svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-k1gItr4hzZPumKIa p{margin:0;}#mermaid-svg-k1gItr4hzZPumKIa .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-k1gItr4hzZPumKIa .cluster-label text{fill:#333;}#mermaid-svg-k1gItr4hzZPumKIa .cluster-label span{color:#333;}#mermaid-svg-k1gItr4hzZPumKIa .cluster-label span p{background-color:transparent;}#mermaid-svg-k1gItr4hzZPumKIa .label text,#mermaid-svg-k1gItr4hzZPumKIa span{fill:#333;color:#333;}#mermaid-svg-k1gItr4hzZPumKIa .node rect,#mermaid-svg-k1gItr4hzZPumKIa .node circle,#mermaid-svg-k1gItr4hzZPumKIa .node ellipse,#mermaid-svg-k1gItr4hzZPumKIa .node polygon,#mermaid-svg-k1gItr4hzZPumKIa .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-k1gItr4hzZPumKIa .rough-node .label text,#mermaid-svg-k1gItr4hzZPumKIa .node .label text,#mermaid-svg-k1gItr4hzZPumKIa .image-shape .label,#mermaid-svg-k1gItr4hzZPumKIa .icon-shape .label{text-anchor:middle;}#mermaid-svg-k1gItr4hzZPumKIa .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-k1gItr4hzZPumKIa .rough-node .label,#mermaid-svg-k1gItr4hzZPumKIa .node .label,#mermaid-svg-k1gItr4hzZPumKIa .image-shape .label,#mermaid-svg-k1gItr4hzZPumKIa .icon-shape .label{text-align:center;}#mermaid-svg-k1gItr4hzZPumKIa .node.clickable{cursor:pointer;}#mermaid-svg-k1gItr4hzZPumKIa .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-k1gItr4hzZPumKIa .arrowheadPath{fill:#333333;}#mermaid-svg-k1gItr4hzZPumKIa .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-k1gItr4hzZPumKIa .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-k1gItr4hzZPumKIa .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-k1gItr4hzZPumKIa .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-k1gItr4hzZPumKIa .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-k1gItr4hzZPumKIa .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-k1gItr4hzZPumKIa .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-k1gItr4hzZPumKIa .cluster text{fill:#333;}#mermaid-svg-k1gItr4hzZPumKIa .cluster span{color:#333;}#mermaid-svg-k1gItr4hzZPumKIa div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-k1gItr4hzZPumKIa .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-k1gItr4hzZPumKIa rect.text{fill:none;stroke-width:0;}#mermaid-svg-k1gItr4hzZPumKIa .icon-shape,#mermaid-svg-k1gItr4hzZPumKIa .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-k1gItr4hzZPumKIa .icon-shape p,#mermaid-svg-k1gItr4hzZPumKIa .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-k1gItr4hzZPumKIa .icon-shape .label rect,#mermaid-svg-k1gItr4hzZPumKIa .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-k1gItr4hzZPumKIa .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-k1gItr4hzZPumKIa .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-k1gItr4hzZPumKIa :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
OLED 模块 (SSD1306)
STM32F103C8T6
时钟信号
数据信号
片选信号
数据/命令选择
复位信号
PB13 (SPI2_SCK)
PB15 (SPI2_MOSI)
PB12 (CS)
PB11 (DC)
PB10 (RES)
SCK
MOSI
CS
DC
RES
说明:SPI 为单向主从通信,STM32 作为主机(Master)向 OLED 从机(Slave)发送时钟与数据;DC 引脚用于区分当前传输的是命令还是显示数据,RES 用于复位 OLED 控制器。
3.3 SPI 驱动代码示例
SPI 模式与 I2C 模式在代码结构上保持一致,主要差异在于底层通信函数:SPI 需要额外控制 CS(片选)、DC(数据/命令选择)和 RES(复位)三个 GPIO 引脚,且写命令/写数据时不再需要 I2C 的控制字节(0x00/0x40),而是通过 DC 引脚电平来区分命令与数据。
/* oled_spi.h */
#ifndef OLED_SPI_H
#define OLED_SPI_H
#include "stm32f1xx_hal.h"
/* SPI 片选、数据/命令选择、复位引脚定义(按 3.2 节接线) */
#define OLED_CS_GPIO_Port GPIOB
#define OLED_CS_Pin GPIO_PIN_12
#define OLED_DC_GPIO_Port GPIOB
#define OLED_DC_Pin GPIO_PIN_11
#define OLED_RES_GPIO_Port GPIOB
#define OLED_RES_Pin GPIO_PIN_10
void OLED_WriteCmd(uint8_t cmd);
void OLED_WriteData(uint8_t data);
void OLED_Reset(void);
#endif
/* oled_spi.c */
#include "oled_spi.h"
extern SPI_HandleTypeDef hspi2;
/* 片选拉低,选中 OLED */
static void OLED_Select(void)
{
HAL_GPIO_WritePin(OLED_CS_GPIO_Port, OLED_CS_Pin, GPIO_PIN_RESET);
}
/* 片选拉高,释放 OLED */
static void OLED_Unselect(void)
{
HAL_GPIO_WritePin(OLED_CS_GPIO_Port, OLED_CS_Pin, GPIO_PIN_SET);
}
/* 写命令:DC 置低表示命令 */
void OLED_WriteCmd(uint8_t cmd)
{
OLED_Select();
HAL_GPIO_WritePin(OLED_DC_GPIO_Port, OLED_DC_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi2, &cmd, 1, 100);
OLED_Unselect();
}
/* 写数据:DC 置高表示数据 */
void OLED_WriteData(uint8_t data)
{
OLED_Select();
HAL_GPIO_WritePin(OLED_DC_GPIO_Port, OLED_DC_Pin, GPIO_PIN_SET);
HAL_SPI_Transmit(&hspi2, &data, 1, 100);
OLED_Unselect();
}
/* 复位:RES 拉低至少 10us 再拉高 */
void OLED_Reset(void)
{
HAL_GPIO_WritePin(OLED_RES_GPIO_Port, OLED_RES_Pin, GPIO_PIN_RESET);
HAL_Delay(10);
HAL_GPIO_WritePin(OLED_RES_GPIO_Port, OLED_RES_Pin, GPIO_PIN_SET);
HAL_Delay(100);
}
SPI 模式的 SSD1306 初始化命令序列与 I2C 模式完全相同,只需在 OLED_Init() 开头调用 OLED_Reset() 完成硬件复位,其余命令照搬即可。与 I2C 模式相比,SPI 模式的主要差异可总结为:
- 通信函数:I2C 通过 HAL_I2C_Master_Transmit 发送带控制字节的数据包;SPI 通过 HAL_SPI_Transmit 直接发送单字节,并用 DC 引脚区分命令/数据。
- 引脚控制:SPI 模式额外管理 CS、DC、RES 三个 GPIO,I2C 模式无需这些引脚。
- 初始化流程:SPI 模式在发送命令序列前需先执行 OLED_Reset() 复位;I2C 模式仅需上电延时。
- 传输速度:SPI 时钟可达数 MHz,明显快于 I2C 的 100kHz/400kHz,适合刷新较频繁的界面。
4. 开发环境准备
本文使用以下开发环境:
- 芯片型号:STM32F103C8T6(蓝板)
- 开发方式:STM32 HAL 库 + Keil MDK 或 STM32CubeIDE
- OLED 模块:0.96 英寸 I2C 接口 SSD1306
4.1 使用 STM32CubeMX 配置 I2C
5. OLED 驱动代码编写
5.1 底层 I2C 通信函数
首先编写 OLED 与 STM32 之间的底层通信函数,包括写命令和写数据。
/* oled_i2c.h */
#ifndef OLED_I2C_H
#define OLED_I2C_H
#include "stm32f1xx_hal.h"
#define OLED_ADDR 0x78 // I2C 设备地址(7 位地址 0x3C 左移一位)
void OLED_WriteCmd(uint8_t cmd);
void OLED_WriteData(uint8_t data);
#endif
/* oled_i2c.c */
#include "oled_i2c.h"
extern I2C_HandleTypeDef hi2c1;
void OLED_WriteCmd(uint8_t cmd)
{
uint8_t buf[2];
buf[0] = 0x00; // 控制字节:0x00 表示后续为命令
buf[1] = cmd;
HAL_I2C_Master_Transmit(&hi2c1, OLED_ADDR, buf, 2, 100);
}
void OLED_WriteData(uint8_t data)
{
uint8_t buf[2];
buf[0] = 0x40; // 控制字节:0x40 表示后续为数据
buf[1] = data;
HAL_I2C_Master_Transmit(&hi2c1, OLED_ADDR, buf, 2, 100);
}
5.2 SSD1306 初始化
SSD1306 上电后需要发送初始化命令序列,配置显示参数。
/* oled_ssd1306.h */
#ifndef OLED_SSD1306_H
#define OLED_SSD1306_H
#include "oled_i2c.h"
void OLED_Init(void);
void OLED_Clear(void);
void OLED_DisplayOn(void);
void OLED_DisplayOff(void);
#endif
/* oled_ssd1306.c */
#include "oled_ssd1306.h"
void OLED_Init(void)
{
HAL_Delay(100); // 上电等待
OLED_WriteCmd(0xAE); // 关闭显示
OLED_WriteCmd(0x20); // 设置内存寻址模式
OLED_WriteCmd(0x02); // 页寻址模式
OLED_WriteCmd(0xB0); // 设置页起始地址(Page 0)
OLED_WriteCmd(0xC8); // 扫描方向:从上到下
OLED_WriteCmd(0x00); // 设置列地址低 4 位
OLED_WriteCmd(0x10); // 设置列地址高 4 位
OLED_WriteCmd(0x40); // 设置显示起始行
OLED_WriteCmd(0x81); // 设置对比度
OLED_WriteCmd(0x7F); // 对比度值
OLED_WriteCmd(0xA1); // 段重映射:列地址 0 映射到 SEG0
OLED_WriteCmd(0xA6); // 正常显示(非反显)
OLED_WriteCmd(0xA8); // 设置多路复用比
OLED_WriteCmd(0x3F); // 64 路
OLED_WriteCmd(0xA4); // 关闭整个显示(使用 RAM 内容)
OLED_WriteCmd(0xD3); // 设置显示偏移
OLED_WriteCmd(0x00); // 偏移 0
OLED_WriteCmd(0xD5); // 设置时钟分频
OLED_WriteCmd(0x80); // 推荐值
OLED_WriteCmd(0xD9); // 设置预充电周期
OLED_WriteCmd(0xF1); // 推荐值
OLED_WriteCmd(0xDA); // 设置 COM 引脚硬件配置
OLED_WriteCmd(0x12); // 推荐值
OLED_WriteCmd(0xDB); // 设置 VCOMH 电压
OLED_WriteCmd(0x40); // 推荐值
OLED_WriteCmd(0x8D); // 设置电荷泵
OLED_WriteCmd(0x14); // 开启电荷泵
OLED_WriteCmd(0xAF); // 开启显示
OLED_Clear();
}
void OLED_Clear(void)
{
uint8_t page, col;
for (page = 0; page < 8; page++)
{
OLED_WriteCmd(0xB0 + page); // 设置页地址
OLED_WriteCmd(0x00); // 列低地址
OLED_WriteCmd(0x10); // 列高地址
for (col = 0; col < 128; col++)
{
OLED_WriteData(0x00); // 清空该像素
}
}
}
void OLED_DisplayOn(void)
{
OLED_WriteCmd(0xAF);
}
void OLED_DisplayOff(void)
{
OLED_WriteCmd(0xAE);
}
5.3 显示单个字符
SSD1306 本身不包含字库,需要自行准备 ASCII 字符的点阵数据。下面以 8×16 字体为例。
/* oled_font.h */
#ifndef OLED_FONT_H
#define OLED_FONT_H
extern const uint8_t F8x16[][16];
#endif
/* oled_font.c */
#include "oled_font.h"
/* 8×16 字体,每个字符 16 字节,每字节表示一列 8 个像素 */
const uint8_t F8x16[][16] =
{
/* 字符 'A' (0x41) */
{
0x00, 0x00, 0x00, 0x10, 0x28, 0x44, 0x82, 0xFE,
0x82, 0x82, 0x82, 0x82, 0x82, 0x00, 0x00, 0x00
},
/* 字符 'B' (0x42) */
{
0x00, 0x00, 0x00, 0xFC, 0x82, 0x82, 0x82, 0xFC,
0x82, 0x82, 0x82, 0x82, 0xFC, 0x00, 0x00, 0x00
},
/* 更多字符请按 ASCII 码顺序补充 */
};
提示:完整的 8×16 字库文件较长,通常作为独立头文件引入。也可以使用取模软件(如 PCtoLCD2002)生成任意字符的点阵数据。
5.4 显示字符串
/* oled_show.h */
#ifndef OLED_SHOW_H
#define OLED_SHOW_H
#include "oled_ssd1306.h"
void OLED_ShowChar(uint8_t x, uint8_t y, char ch);
void OLED_ShowString(uint8_t x, uint8_t y, const char *str);
void OLED_SetPos(uint8_t x, uint8_t y);
#endif
/* oled_show.c */
#include "oled_show.h"
#include "oled_font.h"
/* 设置显示位置:x 为列(0-127),y 为页(0-7) */
void OLED_SetPos(uint8_t x, uint8_t y)
{
OLED_WriteCmd(0xB0 + y); // 设置页地址
OLED_WriteCmd(((x & 0xF0) >> 4) | 0x10); // 列高 4 位
OLED_WriteCmd(x & 0x0F); // 列低 4 位
}
/* 显示单个字符(8×16 字体,占 2 页) */
void OLED_ShowChar(uint8_t x, uint8_t y, char ch)
{
uint8_t i;
if (ch < 0x20 || ch > 0x7E) // 仅支持可打印 ASCII
ch = ' ';
/* 上半部分(8 像素高) */
OLED_SetPos(x, y);
for (i = 0; i < 8; i++)
{
OLED_WriteData(F8x16[(ch – 0x20) * 16 + i]);
}
/* 下半部分(8 像素高) */
OLED_SetPos(x, y + 1);
for (i = 8; i < 16; i++)
{
OLED_WriteData(F8x16[(ch – 0x20) * 16 + i]);
}
}
/* 显示字符串 */
void OLED_ShowString(uint8_t x, uint8_t y, const char *str)
{
while (*str)
{
OLED_ShowChar(x, y, *str);
x += 8; // 每个字符占 8 列
if (x > 120) // 超出屏幕宽度则换行
{
x = 0;
y += 2;
}
str++;
}
}
5.5 显示汉字
汉字为 16×16 点阵,需要 2 页 × 16 列。同样使用取模软件生成点阵数据。
/* 16×16 汉字点阵示例:'中' */
const uint8_t HZ16[][32] =
{
{
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
}
/* 实际数据请用取模软件生成 */
};
void OLED_ShowChinese(uint8_t x, uint8_t y, uint8_t index)
{
uint8_t i;
OLED_SetPos(x, y);
for (i = 0; i < 16; i++)
{
OLED_WriteData(HZ16[index][i]);
}
OLED_SetPos(x, y + 1);
for (i = 16; i < 32; i++)
{
OLED_WriteData(HZ16[index][i]);
}
}
6. 完整示例:显示传感器数据
下面是一个综合示例,演示如何在 OLED 上显示 ADC 采集的电压值。
/* main.c 中的主循环示例 */
#include "oled_show.h"
#include "adc.h"
#include <stdio.h>
extern ADC_HandleTypeDef hadc1;
char buf[32];
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
MX_ADC1_Init();
OLED_Init();
OLED_ShowString(0, 0, "STM32 OLED Demo");
OLED_ShowString(0, 2, "ADC Voltage:");
while (1)
{
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 100);
uint16_t adc_val = HAL_ADC_GetValue(&hadc1);
HAL_ADC_Stop(&hadc1);
float voltage = adc_val * 3.3f / 4095.0f;
sprintf(buf, "%.2f V", voltage);
/* 先清空旧数据区域 */
OLED_SetPos(0, 4);
for (int i = 0; i < 16; i++)
OLED_WriteData(0x00);
OLED_ShowString(0, 4, buf);
HAL_Delay(500);
}
}
7. 常用第三方库推荐
除了手写驱动,也可以直接使用成熟的开源库,节省开发时间。
7.1 U8g2 库
U8g2 是目前最流行的嵌入式图形库之一,支持 SSD1306 等多种控制器,提供丰富的绘图 API。
#include "u8g2.h"
u8g2_t u8g2; // 全局 U8g2 对象,所有绘图操作都基于它
/* 初始化 U8g2 并配置 SSD1306(I2C 接口,128×64) */
void u8g2_setup(void)
{
/* 1. 绑定控制器与通信方式:SSD1306、I2C、128×64、非 RAM 缓冲模式 */
u8g2_Setup_ssd1306_i2c_128x64_noname_f(
&u8g2, U8G2_R0, // 旋转方向:0 度
u8x8_byte_sw_i2c, // 软件 I2C 字节发送函数
u8x8_gpio_and_delay); // GPIO 与延时回调
/* 2. 初始化显示控制器(发送初始化命令序列) */
u8g2_InitDisplay(&u8g2);
/* 3. 关闭省电模式,点亮屏幕 */
u8g2_SetPowerSave(&u8g2, 0);
}
/* 常用绘图操作示例:字符串、矩形、图片 */
void u8g2_demo(void)
{
/* 清空内部缓冲区,准备绘制新一帧 */
u8g2_ClearBuffer(&u8g2);
/* 设置字体(8×13 等宽字体) */
u8g2_SetFont(&u8g2, u8g2_font_8x13_tr);
/* 在 (0, 10) 处绘制字符串 */
u8g2_DrawStr(&u8g2, 0, 10, "Hello STM32!");
/* 绘制一个空心矩形:左上角 (10, 20),宽 60,高 30 */
u8g2_DrawFrame(&u8g2, 10, 20, 60, 30);
/* 绘制一个实心矩形:左上角 (80, 20),宽 40,高 30 */
u8g2_DrawBox(&u8g2, 80, 20, 40, 30);
/* 绘制一条直线:从 (10, 55) 到 (120, 55) */
u8g2_DrawLine(&u8g2, 10, 55, 120, 55);
/* 绘制一张 16×16 的位图(XBM 格式),放在 (0, 40) 处 */
/* 位图数据可用取模软件生成,这里以全 0 占位 */
static const uint8_t bitmap[32] = { 0 };
u8g2_DrawXBM(&u8g2, 0, 40, 16, 16, bitmap);
/* 将缓冲区内容一次性发送到 OLED 显示 */
u8g2_SendBuffer(&u8g2);
}
8. 常见问题与排查
在实际开发中,OLED 屏不亮、花屏、乱码等问题非常常见。下面针对典型故障给出原因分析与排查步骤。
8.1 OLED 不亮
可能原因:
- 供电不足或电压错误(未接 3.3V 而接了 5V,或电源电流不够)。
- 接线错误,VCC/GND 接反。
- 初始化命令序列未执行,或 OLED_Init() 未被调用。
- 电荷泵未开启(缺少 0x8D, 0x14 命令)。
- 对比度设置过低(0x81 后的值太小)。
排查步骤:
代码修复建议:
/* 确保初始化中包含开启电荷泵的命令 */
OLED_WriteCmd(0x8D); // 设置电荷泵
OLED_WriteCmd(0x14); // 开启电荷泵(0x10 为关闭)
OLED_WriteCmd(0xAF); // 开启显示
8.2 花屏 / 显示异常
可能原因:
- 初始化命令序列不完整或顺序错误。
- 显示缓冲区未清空,残留旧数据。
- 页地址或列地址设置错误,导致数据写入位置错乱。
- 电源纹波过大,或 SPI 模式下时钟极性/相位配置错误。
排查步骤:
代码修复建议:
/* 显示新内容前先清屏,避免残留 */
OLED_Clear();
/* 设置正确的显示位置后再写入数据 */
OLED_SetPos(0, 0);
OLED_ShowString(0, 0, "Hello");
8.3 显示乱码
可能原因:
- 字库数据与字符编码不匹配(如使用 GB2312 编码却按 ASCII 解析)。
- 取模方式错误(列行式/行列式、阴码/阳码不匹配)。
- 汉字点阵数据未正确生成或索引越界。
- 字符串编码与编译器字符集不一致。
排查步骤:
代码修复建议:
/* 使用 U8g2 显示中文时,需选择支持中文的字体 */
u8g2_SetFont(&u8g2, u8g2_font_unifont_t_chinese2);
u8g2_DrawUTF8(&u8g2, 0, 20, "你好 STM32");
8.4 I2C 通信失败
可能原因:
- I2C 地址错误(应为 0x78 或 0x7A,取决于 SA0 引脚电平)。
- SCL/SDA 上拉电阻缺失或阻值不当。
- 引脚复用配置错误(PB6/PB7 未配置为 I2C1 功能)。
- 总线被其他设备占用,或时序不满足要求。
排查步骤:
代码修复建议:
/* 检查 I2C 发送返回值,便于定位通信问题 */
HAL_StatusTypeDef status = HAL_I2C_Master_Transmit(&hi2c1, OLED_ADDR, buf, 2, 100);
if (status != HAL_OK)
{
/* 通信失败:检查地址、上拉电阻与引脚配置 */
Error_Handler();
}
8.5 常见问题对比表
| OLED 完全不亮 | 供电不足/接线错误 | 检查 VCC/GND 电压与极性,确保 3.3V 供电 |
| OLED 完全不亮 | 初始化未执行或电荷泵未开启 | 调用 OLED_Init(),确保包含 0x8D, 0x14 命令 |
| 花屏/显示错乱 | 初始化命令不完整或顺序错误 | 对照 5.2 节核对命令序列 |
| 花屏/显示错乱 | 未清屏导致残留 | 显示前调用 OLED_Clear() |
| 显示乱码 | 字库/取模方式不匹配 | 重新取模,核对阴码/阳码与列行式 |
| 显示乱码 | 汉字索引越界 | 检查 OLED_ShowChinese() 的 index 参数 |
| I2C 通信失败 | 设备地址错误 | 核对 OLED_ADDR 为 0x78 或 0x7A |
| I2C 通信失败 | 上拉电阻缺失 | 外接 4.7kΩ 上拉或开启内部上拉 |
| I2C 通信失败 | 引脚复用配置错误 | 在 CubeMX 中确认 PB6/PB7 配置为 I2C1 |






