目标:通过 I2C1 驱动 OLED 显示屏(SSD1306,地址 0x78)
I2C 使用两根线(SDA + SCL)连接多个设备:
主机 (STM32) ←→ SDA ──────────────────
SCL ──────────────────
│ │ │
从机1 从机2 从机3
(OLED) (传感器) (其他)
两根线都必须外部上拉到 VDD(3.3V 或 5V,模块通常自带)
为什么需要开漏+上拉?
每一帧 I2C 通信由主机发起:
主机发→从机收(写操作):
START → [7位地址 + W(0)] → ACK ← 从机 → DATA1 → ACK → DATA2 → ACK → ... → STOP
主机收←从机发(读操作):
START → [7位地址 + R(1)] → ACK ← 从机 → DATA1 → ACK ← 主机 → ... → NAK ← 主机 → STOP
关键点:
7 位地址 → 8 位地址:I2C 规范中从机地址是 7 位,但通信时左移 1 位,最低位表示读写:
| 函数 | 说明 |
|---|---|
HAL_I2C_Master_Transmit(&hi2c, DevAddr, pData, Size, Timeout) |
主机发送数据到从机 |
HAL_I2C_Master_Receive(&hi2c, DevAddr, pData, Size, Timeout) |
主机从从机接收数据 |
HAL_I2C_Mem_Write(&hi2c, DevAddr, MemAddr, MemAddrSize, pData, Size, Timeout) |
向从机指定寄存器写入 |
HAL_I2C_Mem_Read(&hi2c, DevAddr, MemAddr, MemAddrSize, pData, Size, Timeout) |
从从机指定寄存器读取 |
DevAddr = 7 位地址左移 1 位,例如 0x3C << 1 = 0x78。
SSD1306 的控制字节格式:Co(1) | D/C#(1) | 0(6)。其中:
0x00 = 后续字节是命令(Co=0, D/C#=0)0x40 = 后续字节是数据(Co=0, D/C#=1)
/* 写命令 */
static void OLED_WriteCmd(uint8_t cmd)
{
uint8_t data[2] = {0x00, cmd}; // 控制字节 + 命令
HAL_I2C_Master_Transmit(&hi2c1, 0x78, data, 2, HAL_MAX_DELAY);
}
/* 写数据 */
static void OLED_WriteData(uint8_t dat)
{
uint8_t data[2] = {0x40, dat}; // 控制字节 + 数据
HAL_I2C_Master_Transmit(&hi2c1, 0x78, data, 2, HAL_MAX_DELAY);
}
/* 初始化 —— 按功能分组注释 */
static void OLED_Init(void)
{
HAL_Delay(100); // 等待 SSD1306 上电稳定
OLED_WriteCmd(0xAE); // [1] 关闭显示(配置期间保持关闭)
OLED_WriteCmd(0xD5); // [2] 振荡器频率
OLED_WriteCmd(0x80);
OLED_WriteCmd(0xA8); // [3] 多路复用比 (MUX)
OLED_WriteCmd(0x3F); // 128×64: MUX=63
OLED_WriteCmd(0xD3); // [4] 显示偏移
OLED_WriteCmd(0x00);
OLED_WriteCmd(0x40); // [5] 起始行 = 0
OLED_WriteCmd(0x8D); // [6] 电荷泵
OLED_WriteCmd(0x14); // 使能(内部升压到 ~7V)
OLED_WriteCmd(0x20); // [7] 内存地址模式
OLED_WriteCmd(0x00); // 水平模式
OLED_WriteCmd(0xA1); // [8] 段重映射(列 127→SEG0)
OLED_WriteCmd(0xC8); // [9] COM 扫描方向(从上到下)
OLED_WriteCmd(0xDA); // [10] COM 引脚配置
OLED_WriteCmd(0x12);
OLED_WriteCmd(0x81); // [11] 对比度
OLED_WriteCmd(0xCF);
OLED_WriteCmd(0xD9); // [12] 预充电周期
OLED_WriteCmd(0xF1);
OLED_WriteCmd(0xDB); // [13] VCOMH 电压
OLED_WriteCmd(0x40);
OLED_WriteCmd(0xA4); // [14] 全局显示恢复
OLED_WriteCmd(0xA6); // [15] 正常显示(非反色)
OLED_WriteCmd(0x2E); // [16] 停止水平滚动
OLED_WriteCmd(0xAF); // [17] 开启显示!
}
清屏(逐个像素写入 0x00):
static void OLED_Clear(void)
{
for (uint8_t page = 0; page < 8; page++) // 128×64 = 8 pages × 128 cols
{
OLED_WriteCmd(0xB0 + page); // 设置页地址
OLED_WriteCmd(0x00); // 列低位
OLED_WriteCmd(0x10); // 列高位
for (uint8_t col = 0; col < 128; col++)
OLED_WriteData(0x00); // 该列全部熄灭
}
}
uint8_t reg_addr = 0x00;
uint8_t value;
/* 写寄存器地址(不发送 STOP,而是 Repeated Start):
发 START→从机地址+W→ACK→寄存器地址→ACK → (Repeated Start)
发 START→从机地址+R→ACK→接收数据→NAK→STOP
*/
HAL_I2C_Mem_Read(&hi2c1, 0x78, reg_addr, I2C_MEMADD_SIZE_8BIT, &value, 1, HAL_MAX_DELAY);