04-OLED调试
调试方式对比
| 方式 |
优点 |
缺点 |
适用场景 |
| 串口(USART) |
只需 TX/RX 两根线 |
需 PC 端串口助手,不直观 |
日志输出,数据结构 |
| OLED 屏幕 |
实时显示,无需 PC |
占 GPIO,I2C/SPI 总线 |
调试参数显示,仪表 |
| Keil Debug |
单步/断点/变量观察 |
停止运行,实时性差 |
逻辑错误,内存检查 |
| 逻辑分析仪 |
精确时序 |
需额外硬件 |
时序协议分析 |
OLED 模块规格 (0.96寸)
| 参数 |
值 |
| 分辨率 |
128 × 64 |
| 驱动 IC |
SSD1306 |
| 颜色 |
蓝/白/黄蓝双色 |
| 接口 |
I2C / SPI |
| 供电 |
3.3V~5V |
| 功耗 |
~20mA |
| 可视角度 |
>160° |
4针 I2C 版本引脚
| 引脚 |
功能 |
连接 |
| VCC |
电源 |
3.3V |
| GND |
地 |
GND |
| SCL |
I2C 时钟线 |
PB8 (软件I2C) |
| SDA |
I2C 数据线 |
PB9 (软件I2C) |
SSD1306 与 I2C 协议
sequenceDiagram
participant MCU as STM32 Master
participant OLED as SSD1306 Slave
MCU->>OLED: START Condition (SCL高, SDA下降沿)
MCU->>OLED: 0x78 (设备地址 0x3C<<1 + W=0)
OLED-->>MCU: ACK
MCU->>OLED: 0x00 (Control Byte: Co=0, D/C#=0 → Command)
OLED-->>MCU: ACK
MCU->>OLED: Command Byte
OLED-->>MCU: ACK
MCU->>OLED: STOP Condition (SCL高, SDA上升沿)
软件 I2C 模拟
通信协议层
// 起始信号
void I2C_Start(void)
{
GPIO_SetBits(GPIOB, GPIO_Pin_8 | GPIO_Pin_9); // SCL=H, SDA=H
Delay_us(5);
GPIO_ResetBits(GPIOB, GPIO_Pin_9); // SDA↓
Delay_us(5);
GPIO_ResetBits(GPIOB, GPIO_Pin_8); // SCL↓
}
// 停止信号
void I2C_Stop(void)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_9); // SDA=L
GPIO_SetBits(GPIOB, GPIO_Pin_8); // SCL=H
Delay_us(5);
GPIO_SetBits(GPIOB, GPIO_Pin_9); // SDA↑
Delay_us(5);
}
// 发送一个字节 (MSB first)
void I2C_SendByte(uint8_t byte)
{
for (uint8_t i = 0; i < 8; i++)
{
if (byte & 0x80)
GPIO_SetBits(GPIOB, GPIO_Pin_9);
else
GPIO_ResetBits(GPIOB, GPIO_Pin_9);
byte <<= 1;
GPIO_SetBits(GPIOB, GPIO_Pin_8); // SCL=H (从机采样)
Delay_us(2);
GPIO_ResetBits(GPIOB, GPIO_Pin_8); // SCL=L
Delay_us(2);
}
// 接收 ACK
GPIO_SetBits(GPIOB, GPIO_Pin_9); // SDA=H (释放总线)
GPIO_SetBits(GPIOB, GPIO_Pin_8);
Delay_us(2); // 等待从机拉低 SDA
// 若 SDA 未被拉低 (NACK),可在此处理
GPIO_ResetBits(GPIOB, GPIO_Pin_8);
GPIO_ResetBits(GPIOB, GPIO_Pin_9); // 恢复
}
// 接收一个字节
uint8_t I2C_ReceiveByte(void)
{
uint8_t byte = 0;
GPIO_SetBits(GPIOB, GPIO_Pin_9); // SDA=H (输入模式需设为浮空输入)
for (uint8_t i = 0; i < 8; i++)
{
byte <<= 1;
GPIO_SetBits(GPIOB, GPIO_Pin_8);
Delay_us(2);
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_9))
byte |= 0x01;
GPIO_ResetBits(GPIOB, GPIO_Pin_8);
Delay_us(2);
}
// 发送 ACK (0) 或 NACK (1)
GPIO_ResetBits(GPIOB, GPIO_Pin_9); // ACK
GPIO_SetBits(GPIOB, GPIO_Pin_8);
Delay_us(2);
GPIO_ResetBits(GPIOB, GPIO_Pin_8);
GPIO_SetBits(GPIOB, GPIO_Pin_9);
return byte;
}
OLED 驱动函数表
底层命令函数
void OLED_WriteCmd(uint8_t cmd); // 写入命令
void OLED_WriteData(uint8_t data); // 写入数据
void OLED_SetCursor(uint8_t x, uint8_t y); // 设置坐标 (x:0~127, y:0~7 页)
用户接口函数
| 函数 |
原型 |
说明 |
OLED_Init |
void OLED_Init(void) |
初始化 SSD1306 |
OLED_Clear |
void OLED_Clear(void) |
清屏 (全黑) |
OLED_ShowChar |
void OLED_ShowChar(uint8_t x, uint8_t y, char ch) |
显示一个字符 (6x8) |
OLED_ShowString |
void OLED_ShowString(uint8_t x, uint8_t y, char* str) |
显示字符串 |
OLED_ShowNum |
void OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len) |
显示十进制数字 |
OLED_ShowSignedNum |
void OLED_ShowSignedNum(uint8_t x, uint8_t y, int32_t num, uint8_t len) |
显示有符号数 |
OLED_ShowHexNum |
void OLED_ShowHexNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len) |
显示十六进制数 |
OLED_ShowBinNum |
void OLED_ShowBinNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len) |
显示二进制数 |
OLED_SetCursor |
void OLED_SetCursor(uint8_t x, uint8_t y) |
设置写位置 |
OLED_DisplayOn |
void OLED_DisplayOn(void) |
开启显示 |
OLED_DisplayOff |
void OLED_DisplayOff(void) |
关闭显示(省电) |
SSD1306 初始化序列
void OLED_Init(void)
{
Delay_ms(100); // 等待 SSD1306 上电稳定
// PB8(SCL), PB9(SDA) 推挽输出
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // 开漏,需外部上拉
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB, GPIO_Pin_8 | GPIO_Pin_9);
// SSD1306 初始化命令序列
OLED_WriteCmd(0xAE); // 关闭显示
OLED_WriteCmd(0xD5); // 设置振荡频率
OLED_WriteCmd(0x80); // 默认值
OLED_WriteCmd(0xA8); // 多路复用比
OLED_WriteCmd(0x3F); // 1/64 duty
OLED_WriteCmd(0xD3); // 显示偏移
OLED_WriteCmd(0x00);
OLED_WriteCmd(0x40); // 起始行
OLED_WriteCmd(0x8D); // 电荷泵
OLED_WriteCmd(0x14); // 开启
OLED_WriteCmd(0x20); // 内存寻址模式
OLED_WriteCmd(0x00); // 水平寻址
OLED_WriteCmd(0xA0); // 段重映射
OLED_WriteCmd(0xA1); // 列127→SEG0 (左右翻转)
OLED_WriteCmd(0xC0); // COM扫描方向
OLED_WriteCmd(0xC8); // 从COM[N-1]到COM0 (上下翻转)
OLED_WriteCmd(0xDA); // COM引脚配置
OLED_WriteCmd(0x12); // 增强驱动
OLED_WriteCmd(0x81); // 对比度
OLED_WriteCmd(0xCF); // 值
OLED_WriteCmd(0xD9); // 预充电周期
OLED_WriteCmd(0xF1);
OLED_WriteCmd(0xDB); // VCOMH解除选择级别
OLED_WriteCmd(0x40);
OLED_WriteCmd(0xA4); // 全局显示开启(跟随RAM)
OLED_WriteCmd(0xA6); // 正常显示(不反色)
OLED_WriteCmd(0x2E); // 停止滚动
OLED_WriteCmd(0xAF); // 开启显示
}
完整代码示例
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
int main(void)
{
Delay_Init();
OLED_Init();
uint32_t count = 0;
OLED_ShowString(1, 1, "STM32 OLED Demo");
OLED_ShowString(2, 1, "Count:");
OLED_ShowString(3, 1, "Hex:0x");
OLED_ShowString(4, 1, "Bin:");
while (1)
{
OLED_ShowNum(2, 7, count, 5);
OLED_ShowHexNum(3, 5, count, 8);
OLED_ShowBinNum(4, 5, count, 16);
count++;
// 显示浮点数(间接)
// OLED_ShowNum(5, 1, 1234, 4); // 整数部分
// OLED_ShowChar(5, 5, '.');
// OLED_ShowNum(5, 6, 56, 2); // 小数部分
Delay_ms(100);
}
}
OLED.H 参考实现
#ifndef __OLED_H
#define __OLED_H
#include "stm32f10x.h"
void OLED_Init(void);
void OLED_Clear(void);
void OLED_DisplayOn(void);
void OLED_DisplayOff(void);
void OLED_ShowChar(uint8_t x, uint8_t y, char ch);
void OLED_ShowString(uint8_t x, uint8_t y, char* str);
void OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len);
void OLED_ShowSignedNum(uint8_t x, uint8_t y, int32_t num, uint8_t len);
void OLED_ShowHexNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len);
void OLED_ShowBinNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len);
void OLED_SetCursor(uint8_t x, uint8_t y);
#endif
常见坑点/注意事项
- I2C地址 — SSD1306 7位地址
0x3C,写入地址 0x78 (0x3C << 1),读取地址 0x79。部分模块 SA0=1 时地址为 0x7A
- Control Byte — 命令
0x00 (Co=0, D/C=0),数据 0x40 (Co=0, D/C=1)
- 延时不宜过短 — 软件 I2C 时钟周期
>2.5μs 最稳定,Delay_us(5) 约 100kHz,太快会导致通信失败
- SCL/SDA 模式 — 推荐
GPIO_Mode_Out_OD(开漏),配合板载 4.7kΩ 上拉电阻;如果用推挽,SDA 双向时需注意切换方向
- OLED 上电慢 — 初始化前至少
Delay_ms(100) 等待 SSD1306 内部复位完成
- 显示位置越界 — x: 0~127, y: 0~7(页模式,每页8像素),写超出会绕回
- 字库 — 字符显示需要 6x8 或 8x16 点阵字库数组,使用标准库时需自己包含
OLED_Font.c
- OLED_SetCursor — SSD1306 页地址模式默认水平寻址,SetCursor 需发送
0xB0+y、高4位列 0x10+(x>>4)、低4位列 x&0x0F