STM32F103C8T6 · 模块演示

0.96 寸 OLED12864 显示屏

基于 SSD1306 驱动芯片、I2C 总线接口的单色有机发光显示屏。
分辨率 128×64,可显示字符、汉字、图形与位图,是嵌入式系统最常用的输出模块之一。

驱动 IC:SSD1306 分辨率:128×64 接口:I2C(4 针) 尺寸:27.6×27.3 mm

01模块简介

0.96 寸 OLED12864 是一种采用有机自发光技术的小型显示模块,相比传统 LCD, 它无需背光、对比度更高、响应更快、视角更广。

🔆 自发光

每个像素独立发光,无背光,纯黑场景下像素可完全关闭,对比度可达 10000:1。

🔌 接口简单

仅需 2 根信号线(SCL、SDA)即可与 MCU 通信,节省 GPIO 资源。

⚡ 低功耗

显示内容越简单越省电,关闭像素几乎不耗电,适合电池供电场景。

🧩 显示灵活

支持字符、汉字、位图、点线面等任意图形绘制。

📐 小巧紧凑

PCB 尺寸仅 27.6 × 27.3 mm,可嵌入各种紧凑型设备。

🎨 多种颜色

常见有白色、蓝色、黄蓝双色等,本演示使用白光的单色屏。

02外观展示

实物外观、尺寸标注与背面的 I2C 地址选择电阻。

03硬件参数

模块的关键电气与物理参数。

📊 显示参数

分辨率 128 × 64 像素
像素尺寸 0.15 × 0.15 mm
有效区域 21.744 × 10.864 mm
颜色 单色(白/蓝/黄蓝)
可视角度 > 160°

⚙ 电气参数

工作电压 3.3 V ~ 5 V
逻辑电平 3.3 V(与 STM32 兼容)
驱动 IC SSD1306
通信接口 I2C(默认 0x78)
工作温度 -40 ℃ ~ 85 ℃

04工作原理

从像素结构、显存组织到显示刷新的完整链路。

4.1 像素点阵结构

屏幕共有 128 列 × 64 行 = 8192 个像素点,每个像素对应 OLED_DisplayBuf 显存数组中的一位。 128 列分成 8 个 Page(页),每页 8 行;显存按"纵向 8 点、高位在下"组织:

显示缓冲区 OLED_DisplayBuf[8][128] Page 0(Y=0~7) Page 1(Y=8~15) Page 2(Y=16~23) Page 3(Y=24~31) Page 4(Y=32~39) Page 5(Y=40~47) Page 6(Y=48~55) Page 7(Y=56~63) = 0b10110100 = 0b01001000 = 0b11011001 每列 1 字节 = 8 个像素(纵向) 横向 128 列 = 128 字节 总显存 = 8 × 128 = 1024 字节

4.2 显存刷新流程

双缓冲思想:所有显示函数(ShowString、DrawLine 等)只修改 OLED_DisplayBuf[8][128] 这个 RAM 数组; 调用 OLED_Update() 时,程序才把整个数组通过 I2C 推送到屏幕。 这种"先画后刷"的模式,可以避免显示过程出现半成品画面。

↑ 单字节点亮示意(8 个像素纵向排列)

05I2C 通信协议

通过 SCL(时钟)与 SDA(数据)两根线,实现主机(STM32)与从机(OLED)通信。

5.1 总线时序

SCL SDA 说明 START 地址 0x78 + ACK 控制 0x00 + ACK 命令 0xAE + ACK … STOP SCL 高时 SDA ↓ = 起始 SCL 高时 SDA ↑ = 停止 ↓ SCL 高电平期间采样 SDA

5.2 通信流程

主机发送 START(起始信号)

SCL 保持高电平期间,SDA 由高拉低,标志一次传输开始。

发送 7 位从机地址 + 1 位读写位

本模块地址为 0x78(写)或 0x79(读),由背面 R5/R6 决定。

等待从机 ACK 应答

从机在第 9 个时钟周期拉低 SDA 表示应答;本驱动未严格检查 ACK。

发送控制字节 0x00(命令)或 0x40(数据)

通知 OLED 接下来收到的是命令还是显示数据。

发送命令/数据字节

逐字节写入。数据通常连续发送以提高效率。

主机发送 STOP(停止信号)

SCL 保持高电平期间,SDA 由低拉高,结束本次传输。

06引脚定义

本模块为 4 针脚版本(I2C 接口),与 STM32F103C8T6 的引脚对应关系如下:

序号模块引脚说明STM32 连接
1GND电源地GND
2VCC电源正极(3.3V ~ 5V)3V3 或 5V
3SCLI2C 时钟线PB15(开漏输出)
4SDAI2C 数据线PB14(开漏输出)
注意:使用 I2C 时,SCL 与 SDA 必须接上拉电阻(4.7 kΩ 左右)到 VCC。 本模块板载上拉电阻,因此外部无需再加;如使用裸屏,请自行外接。

07硬件连接

原理图与 PCB 封装图,确认 OLED 与 STM32 的接线。

📐 原理图

原理图

🔲 PCB 封装

PCB封装

08程序流程分析

从 main() 进入:先初始化 OLED,再进入 1 秒周期的主循环刷新画面。

8.1 整体流程图

程序开始 HAL_Init / GPIO / 时钟配置 系统初始化 OLED_Init() 发送初始化命令序列 ↻ 主循环 while(1) 距上次 ≥ 1s ? HAL_GetTick() - Time_Now 是 否 延时等待 OLED_Clear() Arrow 自增(0 ~ 3 循环) 绘制 字符串 / 数字 / 中文 / 矩形 / 位图 仅修改显存 OLED_DisplayBuf OLED_Update() 整屏刷新 返回主循环

8.2 软件分层

应用层 main.c 业务逻辑:1 秒周期调用显示函数 功能层 OLED.c 显示函数族:ShowString / DrawLine / DrawCircle … 硬件抽象层 OLED.c(驱动) GPIO / I2C 起始、终止、字节收发、命令写入 物理层 硬件 I2C 总线 PB15 / PB14 开漏输出,经上拉电阻接入 OLED 的 SCL / SDA

09关键函数解析

梳理驱动中最核心的几个函数,理解整个刷屏链路。

OLED_Init()

初始化 GPIO,再写入一长串 SSD1306 命令:关闭显示、设置时钟分频、多路复用率(0x3F = 64 行)、左右/上下方向、对比度、充电泵等。最后清屏并刷新一次。

OLED_Update()

遍历 8 个 Page,每页把 128 字节连续写入: OLED_WriteData(OLED_DisplayBuf[j], 128),把整个 1 KB 显存推到屏幕。

OLED_ShowString()

遍历字符:ASCII 调用 ShowChar 查 OLED_F8x16/OLED_F6x8 字库;中文按 GB2312 编码在 OLED_CF16x16 中查找 16×16 点阵数据。

OLED_ShowImage()

把图像数组的字节按位写入显存对应位置,跨页时通过移位 ( << Shift / >> (8-Shift) ) 分发到两页。

OLED_Clear()

将 1024 字节显存全部清零。注意:调用后不会立即显示,必须配合 OLED_Update() 才生效。

OLED_I2C_SendByte()

循环 8 次逐位发送一个字节,MSB 在前。发送完毕后多给一个时钟(不检查 ACK),符合本模块的简化协议。

9.1 OLED_Init() 关键命令对照

// 0xAE / 0xAF  → 关闭 / 开启显示
// 0xA8 0x3F    → 多路复用率 64 行
// 0xA1         → 段(列)方向正常
// 0xC8         → COM(行)方向正常
// 0xDA 0x12    → COM 引脚交替配置
// 0x81 0xCF    → 设置对比度(0x00~0xFF)
// 0x8D 0x14    → 启用电荷泵(必需!否则 3.3V 供电无显示)
// 0xAF         → 开启显示
易踩坑:忘记写 0x8D 0x14 启用电荷泵是初学者最常见的问题——程序看起来都跑通了,但屏幕一片漆黑。

10实机模拟演示

根据 demo 程序,模拟 STM32 在真实硬件上呈现的效果。 点击 ▶ 运行 启动自动刷新循环,或点击 方向 按钮手动切换。

OLED12864 模块实物
当前状态:已暂停。点击 运行 启动 1 秒周期刷新循环。
当前 Arrow:0 含义:UP(向上)

10.1 显示内容布局示意

本演示程序在同一帧内同时展示了 OLED 库几乎所有常用功能: 6×8 英文 8×16 英文 16×16 中文 数字 矩形 位图箭头 动态循环
// main.c 中的核心绘制逻辑(节选)
if(HAL_GetTick() - Time_Now >= 1000)        // 每 1 秒刷新一次
{
    OLED_Clear();                                // ① 清空显存
    (Arrow >= 3) ? (Arrow = 0) : (Arrow++); // ② 方向计数 0~3 循环

    OLED_ShowNum(120, 0, Arrow, 1, OLED_8X16);          // ③ 右上角显示数字
    OLED_ShowString(0, 0,  "Hello World!", OLED_6X8);   // ④ 顶部英文 6×8
    OLED_ShowString(0, 20, "Hello World!", OLED_8X16);  // ⑤ 中部英文 8×16
    OLED_ShowString(0, 48, "你好世界!",     OLED_8X16);  // ⑥ 中文 16×16

    OLED_DrawRectangle(94, 30, 34, 34, OLED_UNFILLED); // ⑦ 画矩形框

    switch(Arrow) {                                // ⑧ 在矩形内显示方向箭头位图
        case 0: OLED_ShowImage(95, 31, 32, 32, UP);    break;
        case 1: OLED_ShowImage(95, 31, 32, 32, RIGHT); break;
        case 2: OLED_ShowImage(95, 31, 32, 32, DOWN);  break;
        case 3: OLED_ShowImage(95, 31, 32, 32, LEFT);  break;
    }

    OLED_Update();                               // ⑨ 推送到屏幕
    Time_Now = HAL_GetTick();
}

11关键代码片段

从驱动中摘录最值得阅读的几段核心实现。

11.1 I2C 字节发送(软件模拟)

void OLED_I2C_SendByte(uint8_t Byte)
{
    uint8_t i;
    for (i = 0; i < 8; i++) {            // 循环 8 次发送 1 bit
        OLED_W_SDA(!!(Byte & (0x80 >> i)));  // MSB 在前
        OLED_W_SCL(1);                       // 从机在 SCL 高电平采样
        OLED_W_SCL(0);                       // 准备下一位
    }
    OLED_W_SCL(1);   // 额外一个时钟(不处理 ACK)
    OLED_W_SCL(0);
}

11.2 写命令 / 写数据

void OLED_WriteCommand(uint8_t Command) {
    OLED_I2C_Start();
    OLED_I2C_SendByte(0x78);    // 从机地址(写)
    OLED_I2C_SendByte(0x00);    // 控制字节:写命令
    OLED_I2C_SendByte(Command);
    OLED_I2C_Stop();
}

void OLED_WriteData(uint8_t *Data, uint8_t Count) {
    OLED_I2C_Start();
    OLED_I2C_SendByte(0x78);    // 从机地址(写)
    OLED_I2C_SendByte(0x40);    // 控制字节:写数据
    for (uint8_t i = 0; i < Count; i++)
        OLED_I2C_SendByte(Data[i]);
    OLED_I2C_Stop();
}

11.3 整屏刷新

void OLED_Update(void) {
    uint8_t j;
    for (j = 0; j < 8; j++) {                  // 8 个 Page
        OLED_SetCursor(j, 0);               // 设置光标到本页第 0 列
        OLED_WriteData(OLED_DisplayBuf[j], 128); // 连续写 128 字节
    }
}
性能参考:每次整屏刷新共发送 1 + 8 × (1 + 1 + 128) = 1041 个字节,按 I2C 100 kHz 计算约需 80 ms; 按 400 kHz 快速模式约 20 ms。本演示每 1 秒刷新一次,体验非常流畅。

12学习总结

🔑 关键概念

• 分页(Page)寻址
• 高位在下的纵向字模
• 显存双缓冲
• 软件 I2C 时序

🛠 移植要点

• 修改 SCL_Pin / SDA_Pin
• 改为硬件 I2C 提速
• 替换字库支持更多字符

🚀 进阶方向

• 滚动显示 / 帧动画
• 菜单 UI 框架
• 配合按键 / 传感器做交互