04-OLED调试.md 8.9 KB

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

常见坑点/注意事项

  1. I2C地址 — SSD1306 7位地址 0x3C,写入地址 0x78 (0x3C << 1),读取地址 0x79。部分模块 SA0=1 时地址为 0x7A
  2. Control Byte — 命令 0x00 (Co=0, D/C=0),数据 0x40 (Co=0, D/C=1)
  3. 延时不宜过短 — 软件 I2C 时钟周期 >2.5μs 最稳定,Delay_us(5) 约 100kHz,太快会导致通信失败
  4. SCL/SDA 模式 — 推荐 GPIO_Mode_Out_OD(开漏),配合板载 4.7kΩ 上拉电阻;如果用推挽,SDA 双向时需注意切换方向
  5. OLED 上电慢 — 初始化前至少 Delay_ms(100) 等待 SSD1306 内部复位完成
  6. 显示位置越界 — x: 0~127, y: 0~7(页模式,每页8像素),写超出会绕回
  7. 字库 — 字符显示需要 6x8 或 8x16 点阵字库数组,使用标准库时需自己包含 OLED_Font.c
  8. OLED_SetCursor — SSD1306 页地址模式默认水平寻址,SetCursor 需发送 0xB0+y、高4位列 0x10+(x>>4)、低4位列 x&0x0F