stm32/display/oled-sh1106.md
OLED 屏幕(SH1106,I2C 128x64)
OLED 屏幕(SH1106,I2C 128x64)
2026-08-19 实测验证:Demo00 工程(洋桃1号开发板 + STM32F103C8T6 + 软件 I2C)菜单/开机画面/动画全部正常显示
#一、元信息
| 字段 | 值 |
|---|---|
| 芯片/平台 | STM32F103C8T6(HAL + FreeRTOS) |
| 接口类型 | I2C(软件模拟,SCL=PB6,SDA=PB7) |
| 验证状态 | ✅ 已验证(2026-08-19) |
| 验证环境 | 洋桃1号开发板 / STM32CubeIDE 2.2.0 / OpenOCD / 2026-08-19 |
| 驱动方式 | 软件 I2C(i2c_sw.c)+ SH1106 命令序列 |
#二、接线表
| 信号 | MCU 引脚 | 模块引脚 | 备注 |
|---|---|---|---|
| I2C_SCL | PB6 | OLED SCL(SCK) | 需上拉(模块自带则免) |
| I2C_SDA | PB7 | OLED SDA(DIN) | 需上拉(模块自带则免) |
| VCC | 3.3V | OLED VCC | 3.3V(勿接 5V) |
| GND | GND | OLED GND | 必须共地 |
#三、踩坑清单
- 芯片型号确认:SH1106 不是 SSD1306! 本板 OLED 是 SH1106(内部 132 列,128x64 模块从列 2 起始),与 SSD1306 初始化命令基本兼容但列偏移不同。驱动必须设
SH1106_COL_OFFSET 2,否则画面整体左移 2 列、右侧有残影。 - I2C 地址:7 位地址
0x3C(SA0 接地)或0x3D(SA0 接 VCC)。写入时按 7 位处理(左移 1 位即 0x78 8 位地址)。可写地址扫描遍历 0x00~0x7F 用 ACK 确认实际地址。 - 必须发完整初始化序列:SH1106 需要完整命令序列(关显示→设置显示参数→Charge Pump 开启 0x8D 0x14→开显示)。漏掉 Charge Pump 时 I2C 正常但不亮。
- 页式寻址:bit0=顶、1=白:SH1106 内存是页式(8 行一页,共 8 页 × 128 列),数据字节 bit0 对应每页最顶像素。取模软件输出的位图可能反相(1=黑)需翻转。
- 软件 I2C 时序:SCL/SDA 用推挽输出或开漏+上拉,写数据前设输出、读 ACK 前切输入,延时循环要够(本工程
i2c_delay20 次空操作,约满足 100kHz)。杜邦线过长需降速。 - I2C 总线共挂多设备:本工程 OLED(0x3C) 与 LM75A(0x4F) 共用同一软件 I2C 总线,多任务访问需互斥锁保护,避免争抢总线造成花屏/丢帧。
#四、驱动代码(Demo00 工程 oled.c 核心逻辑,已通过验证)
#4.1 oled.h
c#ifndef OLED_DRV_H
#define OLED_DRV_H
#include "main.h"
#define OLED_W 128
#define OLED_H 64
#define OLED_PAGES (OLED_H / 8)
#define OLED_ADDR 0x3C /* 7 位地址,SA0 接地 */
#define OLED_ADDR_ALT 0x3D /* SA0 接 VCC 时的备用地址 */
/* SH1106 内部 132 列,128x64 模块从列 2 起始,列偏移=2 */
#define SH1106_COL_OFFSET 2
void oled_init(void);
void oled_clear(void);
void oled_update(void);
void oled_draw_bmp(uint8_t x, uint8_t y, const uint8_t *data);
void oled_draw_string8x16(uint8_t x, uint8_t y, const char *str);
void oled_draw_cn_string(uint8_t x, uint8_t y, const char *str);
#endif
#4.2 oled.c 初始化与关键函数
c#include "oled.h"
#include "i2c_sw.h"
static uint8_t oled_fb[OLED_W * OLED_PAGES]; /* 128x64 帧缓冲 */
static void oled_cmd(uint8_t cmd)
{
uint8_t d[2] = { 0x00, cmd }; /* 0x00 = 命令模式控制字节 */
i2c_sw_write(OLED_ADDR, d, 2);
}
void oled_init(void)
{
oled_cmd(0xAE); /* 关显示 */
oled_cmd(0xD5); oled_cmd(0x80); /* 时钟分频 */
oled_cmd(0xA8); oled_cmd(0x3F); /* 复用率 64 */
oled_cmd(0xD3); oled_cmd(0x00); /* 显示偏移 */
oled_cmd(0x40); /* 起始行 0 */
oled_cmd(0x8D); oled_cmd(0x14); /* Charge Pump 开启(关键) */
oled_cmd(0xA1); /* 段重映射(列地址 127→0) */
oled_cmd(0xC8); /* 扫描方向 */
oled_cmd(0xDA); oled_cmd(0x12); /* COM 配置 */
oled_cmd(0x81); oled_cmd(0xCF); /* 对比度 */
oled_cmd(0xAF); /* 开显示 */
oled_clear();
oled_update();
}
void oled_clear(void)
{
memset(oled_fb, 0, sizeof(oled_fb));
}
void oled_update(void)
{
for (uint8_t page = 0; page < OLED_PAGES; page++)
{
oled_cmd(0xB0 + page); /* 选页 */
oled_cmd(0x00 + SH1106_COL_OFFSET); /* 列低 4 位 */
oled_cmd(0x10); /* 列高 4 位(0x10=列 0 区域,配合偏移 2 覆盖 0..127) */
i2c_sw_write(OLED_ADDR, &oled_fb[page * OLED_W], 128);
}
}
/* 按页式布局绘制位图(data 为 1024 字节,页式、bit0=顶、1=白) */
void oled_draw_bmp(uint8_t x, uint8_t y, const uint8_t *data)
{
for (uint8_t page = 0; page < OLED_PAGES; page++)
{
for (uint8_t col = 0; col < OLED_W; col++)
{
oled_fb[page * OLED_W + col] |= data[page * OLED_W + col];
}
}
}
#五、调用示例
c/* main() 初始化顺序 */
HAL_Init();
SystemClock_Config();
i2c_sw_init(); /* 软件 I2C 引脚初始化 PB6/PB7 */
oled_init(); /* SH1106 初始化 + 清屏 */
/* 显示欢迎语(8x16 英文字符串) */
oled_clear();
oled_draw_string8x16(24, 20, "Hello STM32");
oled_update();
#六、参考库/链接
- 洋桃1号官方 OLED 示例(本地):
D:\学习资料\【用户资料】洋桃1号开发板(2)\B5-教学视频示例程序\1-2-21-OLED显示汉字和图片显示实验\(SH1106 驱动参考) - SH1106 数据手册:搜索 "SH1106 datasheet PDF"(中景园/中显微电子的 128x64 模块常用)
- 通用单色屏库 U8g2:github.com/olikraus/u8g2(内置 SH1106 支持)
- 软件 I2C 驱动:本工程
Core/Src/app/i2c_sw.c/i2c_sw.h(SCL=PB6/SDA=PB7)