【记录贴】STM32 I2C 控制 OLED 卡死?根源在 SR1 与 SR2 的读取操作

问题描述

最近在复用以前STM32F407控制OLED的代码,移植到STM32F103 上,使用硬件 I2C 通信方式。按照常规流程,先发送 OLED 的从机地址,OLED 有正常应答,但当发送第一个控制命令(0xAE)前的控制字节(0x00)时,程序卡在了while(!I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE))这一行,始终等待不到 TXE 标志置位。

更让人疑惑的是,同样的 OLED 模块,用 ESP32 通过 U8G2 库控制时完全正常,说明 OLED 硬件本身没问题,问题大概率出在 STM32 的 I2C 配置或通信逻辑上。

解决过程

尝试解决

一开始,我以为是控制字节发送错误,反复确认后发现发送的是正确的 0x00(命令控制字节),排除了这个可能。

接着怀疑是 I2C 地址错误,用示波器抓取波形,确认从机地址发送正确且 OLED 有应答,这一步也没问题。

然后检查 I2C 初始化配置,时钟频率、ACK 使能等参数都设置正确,GPIO 也配置成了复用开漏输出模式,硬件使用 ESP32说明也没问题。

最后把目光聚焦在 I2C 状态标志的处理上。代码流程大概是这样的:

cpp 复制代码
static void iic1_send_byte(uint8_t addr, uint8_t data)
{
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));

    // 起始信号
    I2C_GenerateSTART(I2C1, ENABLE);
    while(!I2C_GetFlagStatus(I2C1, I2C_FLAG_SB));
	
    // 发送从机地址s
    I2C_Send7bitAddress(I2C1, 0x78, I2C_Direction_Transmitter);
    while(!I2C_GetFlagStatus(I2C1, I2C_FLAG_ADDR));

    // 发送模式
    I2C_SendData(I2C1, addr);
    while(!I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE));
	
    I2C_SendData(I2C1, data); 
    while(!I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE));

    // 发送停止信号
    I2C_GenerateSTOP(I2C1, ENABLE);
}

正确的解决方法

在反复查阅 STM32 参考手册并尝试多种方法后,发现问题出在ADDR标志的清除方式上。STM32 的 I2C 硬件设计规定,ADDR标志必须通过先读取 SR1 寄存器,再读取 SR2 寄存器的方式才能清除。

修改后的代码如下:

cpp 复制代码
static void iic1_send_byte(uint8_t addr, uint8_t data)
{
    while(I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));

    // 起始信号
    I2C_GenerateSTART(I2C1, ENABLE);
    while(!I2C_GetFlagStatus(I2C1, I2C_FLAG_SB));
	
    // 发送从机地址s
    I2C_Send7bitAddress(I2C1, 0x78, I2C_Direction_Transmitter);
    while(!I2C_GetFlagStatus(I2C1, I2C_FLAG_ADDR));

    //! 清除I2C_FLAG_ADDR标志
    // ----------------------------------------------
    I2C_ReadRegister(I2C1, I2C_Register_SR1);
    I2C_ReadRegister(I2C1, I2C_Register_SR2);
    // ----------------------------------------------
	
    // 发送模式
    I2C_SendData(I2C1, addr);
    while(!I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE));
	
    I2C_SendData(I2C1, data); 
    while(!I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE));

    // 发送停止信号
    I2C_GenerateSTOP(I2C1, ENABLE);
}

增加读取 SR2 寄存器的操作后,程序不再卡死,TXE 标志能正常置位,控制命令也顺利发送给了 OLED,问题终于解决。

思考:为什么不这么做可以

解决问题后,我心中产生了一个疑问:为什么网上很多 STM32 控制 OLED 的教程代码里,都没有显式地同时读取 SR1 和 SR2 寄存器,代码却能正常工作,经过分析,主要有以下几个原因:

1. 使用 HAL 库封装了底层操作

现在很多教程采用 HAL 库进行开发,HAL 库的 I2C 相关函数(如HAL_I2C_Master_Transmit)内部已经封装了ADDR标志的清除逻辑,包括先读 SR1 再读 SR2 的操作。用户在调用这些库函数时,不需要关心底层寄存器的操作,所以教程代码里看不到相关读取操作,但实际上底层已经处理了。

2. 采用软件 I2C 方式

其实在CSDN中大部分教程也是软件I2C,为了简化操作,采用软件模拟 I2C 时序的方式控制 OLED。软件 I2C 通过直接操作 GPIO 引脚来模拟 I2C 通信,不涉及 STM32 的 I2C 硬件外设,自然不需要处理 SR1 和 SR2 寄存器。

3. 不规范代码的偶然工作

极少数情况下,一些不规范的代码(比如只读取 SR1 寄存器,甚至不读取任何寄存器)可能在特定条件下偶然工作。这可能是因为 I2C 总线速率极低,或者使用的 STM32 型号存在硬件特性,使得ADDR标志在未正确清除的情况下,状态机也能进入数据发送阶段。但这种情况很不稳定,换个环境或芯片型号可能就会出现问题。

总的来说,直接操作 STM32 I2C 硬件外设时,严格按照参考手册的要求,通过先读 SR1 再读 SR2 的方式清除ADDR标志,才是规范且可靠的做法。

相关推荐
全栈游侠2 小时前
STM32F103XX 01-存储器和总线架构
stm32·嵌入式硬件·架构
恒锐丰小吕2 小时前
屹晶微 EG2124A 中压260V三相半桥驱动芯片技术解析
嵌入式硬件·硬件工程
来自晴朗的明天2 小时前
5、CBMV321 分压式电压跟随器电路
单片机·嵌入式硬件·硬件工程
羽获飞3 小时前
从零开始学嵌入式之STM32——7.STM32的系统架构
stm32·单片机·嵌入式硬件
范纹杉想快点毕业3 小时前
嵌入式系统架构之道:告别“意大利面条”,拥抱状态机与事件驱动
java·开发语言·c++·嵌入式硬件·算法·架构·mfc
-Springer-3 小时前
STM32 学习 —— 个人学习笔记2-1(软件安装)
笔记·stm32·学习
来自晴朗的明天4 小时前
6、AD7683 单通道 AD 采样电路
单片机·嵌入式硬件·硬件工程
TEL136997627504 小时前
方寸微PT153s千兆网口方案 替代RTL8153b方案
单片机·嵌入式硬件
克莱斯勒ya4 小时前
硬件支持包下载地址
嵌入式硬件
恒锐丰小吕4 小时前
屹晶微 EG2122 中压250V半桥驱动芯片技术解析
嵌入式硬件·硬件工程