stm32/display/oled-sh1106.md

OLED 屏幕(SH1106,I2C 128x64)

知识库 · STM32

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_SCLPB6OLED SCL(SCK)需上拉(模块自带则免)
I2C_SDAPB7OLED SDA(DIN)需上拉(模块自带则免)
VCC3.3VOLED VCC3.3V(勿接 5V)
GNDGNDOLED GND必须共地

#三、踩坑清单

  1. 芯片型号确认:SH1106 不是 SSD1306! 本板 OLED 是 SH1106(内部 132 列,128x64 模块从列 2 起始),与 SSD1306 初始化命令基本兼容但列偏移不同。驱动必须设 SH1106_COL_OFFSET 2,否则画面整体左移 2 列、右侧有残影。
  2. I2C 地址:7 位地址 0x3C(SA0 接地)或 0x3D(SA0 接 VCC)。写入时按 7 位处理(左移 1 位即 0x78 8 位地址)。可写地址扫描遍历 0x00~0x7F 用 ACK 确认实际地址。
  3. 必须发完整初始化序列:SH1106 需要完整命令序列(关显示→设置显示参数→Charge Pump 开启 0x8D 0x14→开显示)。漏掉 Charge Pump 时 I2C 正常但不亮。
  4. 页式寻址:bit0=顶、1=白:SH1106 内存是页式(8 行一页,共 8 页 × 128 列),数据字节 bit0 对应每页最顶像素。取模软件输出的位图可能反相(1=黑)需翻转。
  5. 软件 I2C 时序:SCL/SDA 用推挽输出或开漏+上拉,写数据前设输出、读 ACK 前切输入,延时循环要够(本工程 i2c_delay 20 次空操作,约满足 100kHz)。杜邦线过长需降速。
  6. 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)