PPT 参考图 -- 调试方式
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 串口(USART) | 只需 TX/RX 两根线 | 需 PC 端串口助手,不直观 | 日志输出,数据结构 |
| OLED 屏幕 | 实时显示,无需 PC | 占 GPIO,I2C/SPI 总线 | 调试参数显示,仪表 |
| Keil Debug | 单步/断点/变量观察 | 停止运行,实时性差 | 逻辑错误,内存检查 |
| 逻辑分析仪 | 精确时序 | 需额外硬件 | 时序协议分析 |
PPT 参考图 -- OLED简介
| 参数 | 值 |
|---|---|
| 分辨率 | 128 × 64 |
| 驱动 IC | SSD1306 |
| 颜色 | 蓝/白/黄蓝双色 |
| 接口 | I2C / SPI |
| 供电 | 3.3V~5V |
| 功耗 | ~20mA |
| 可视角度 | >160° |
| 引脚 | 功能 | 连接 |
|---|---|---|
| VCC | 电源 | 3.3V |
| GND | 地 | GND |
| SCL | I2C 时钟线 | PB8 (软件I2C) |
| SDA | I2C 数据线 | PB9 (软件I2C) |
PPT 参考图 -- 硬件电路与OLED函数
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上升沿)
SSD1306 显存与页寻址:SSD1306 内置 128×64 位 GDDRAM(Graphic Display Data RAM),共 64 行(Page0~Page7,每页 8 行)× 128 列。在页寻址模式下,写入数据时先通过命令设置当前页(0xB0 + page,page=0~7)和起始列地址(低 4 位命令 0x00~0x0F,高 4 位命令 0x10~0x1F),此后每写入一个字节,列地址自动递增,当前列写满 128 字节后回到该页第 0 列。每页 128 字节,8 页共 1024 字节对应完整屏幕。
GDDRAM 写入流程:主机通过 I2C 先发送 Control Byte 0x00(表示后续为命令),再发送列地址和页地址命令;然后发送 Control Byte 0x40(表示后续为数据),连续写入 128×8 字节的显示数据,每字节的 8 位对应一列中 8 个像素的垂直排列(bit0=最上行,bit7=最下行)。
初始化序列关键命令说明:
0xAE/0xAF:关闭/开启显示,内部 DC-DC 转换器受此控制0x8D, 0x14:使能内部电荷泵,为 OLED 像素提供 ~7V 驱动电压,不加此命令屏幕全黑0x81, 0xCF:设置对比度(0x00 最暗,0xFF 最亮),寄存器值控制像素电流大小,值越大发光越亮0xC8:COM 扫描方向从 COM[N-1] 到 COM0,实现上下翻转0xA1:段重映射,列地址 127 映射到 SEG0,实现左右翻转对比度控制原理:OLED 像素为电流驱动型,对比度寄存器(0x81)设置内部电流参考源的幅值。值越大,每个像素导通时的电流越大,发光越亮。同时增大对比度会增加整体功耗。
// 起始信号
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;
}
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) |
关闭显示(省电) |
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);
}
}
#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
0x3C,写入地址 0x78 (0x3C << 1),读取地址 0x79。部分模块 SA0=1 时地址为 0x7A0x00 (Co=0, D/C=0),数据 0x40 (Co=0, D/C=1)>2.5μs 最稳定,Delay_us(5) 约 100kHz,太快会导致通信失败GPIO_Mode_Out_OD(开漏),配合板载 4.7kΩ 上拉电阻;如果用推挽,SDA 双向时需注意切换方向Delay_ms(100) 等待 SSD1306 内部复位完成OLED_Font.c0xB0+y、高4位列 0x10+(x>>4)、低4位列 x&0x0F