4. STM32的I2C外设详解:模块框图、工作模式与寄存器
说实话,很多初学者用STM32的I2C外设时,都是直接抄库函数。一旦出问题,就完全抓瞎了。我个人觉得,要想真正搞定I2C通信,必须把外设的骨架摸清楚——模块框图、工作模式、关键寄存器。这就像修车,你得知道发动机长什么样,才能判断哪里漏油。
这一章,我们就来拆解STM32F103的I2C外设。我会结合自己调试时的经验,把那些容易踩坑的地方指出来。
4.1 I2C模块框图:看看里面到底有什么
先看整体结构。下图是I2C模块的内部框架,我把它简化成了几个核心部分。
从框图可以看出,I2C外设主要由这几大块组成:时钟控制决定通信速率,数据寄存器是收发数据的缓冲区,控制逻辑负责配置模式,状态标志告诉我们当前通信进行到哪一步了,地址比较器用于从机模式下的地址匹配,SCL/SDA控制则管理引脚输出。
4.2 I2C工作模式:主机与从机
STM32的I2C外设支持两种工作模式:主机模式和从机模式。说白了,主机是发号施令的,从机是听指挥的。
4.2.1 主机模式
主机模式下,外设负责产生时钟信号(SCL)、发起通信、发送地址和数据。我们驱动OLED屏幕时,STM32就是作为主机。
主机模式的关键流程:
- 产生起始条件:SCL高电平时,SDA从高变低
- 发送从机地址:7位地址 + 读写位
- 等待应答:检查从机是否回应ACK
- 发送/接收数据:每字节后等待应答
- 产生停止条件:SCL高电平时,SDA从低变高
4.2.2 从机模式
从机模式下,外设不产生时钟,而是监听总线上的地址。当地址匹配时,自动参与通信。这个模式在项目中用得少一些,但做多机通信时很关键。
从机模式的关键点:
- 地址配置:通过OAR1和OAR2寄存器设置本机地址
- 地址匹配:硬件自动比较,匹配后产生中断
- 数据收发:根据读写位决定方向
4.3 I2C寄存器功能解析
寄存器是操作外设的直接入口。下面我挑几个最常用的,结合实战经验讲清楚。
4.3.1 控制寄存器CR1
CR1是I2C外设的总开关。关键位如下:
| 位 | 名称 | 功能 |
|---|---|---|
| 0 | PE | 外设使能。必须置1才能工作 |
| 1 | SMBus | SMBus模式选择,一般I2C用不到 |
| 8 | START | 产生起始条件。发送完自动清零 |
| 9 | STOP | 产生停止条件。发送完自动清零 |
| 10 | ACK | 应答使能。接收模式下必须置1 |
| 15 | SWRST | 软件复位。总线卡死时救命用的 |
4.3.2 控制寄存器CR2
CR2主要配置时钟频率和中断。关键位:
- FREQ[5:0]:设置APB时钟频率,范围2-36MHz。这个必须配准,否则SCL时序全乱
- ITBUFEN:缓冲区中断使能
- ITEVTEN:事件中断使能
- ITERREN:错误中断使能
我个人习惯在调试阶段把所有中断都打开,方便定位问题。量产时再关掉不必要的,减少中断开销。
4.3.3 状态寄存器SR1
SR1是调试时最常用的寄存器。它实时反映外设的工作状态。
| 位 | 名称 | 含义 |
|---|---|---|
| 0 | SB | 起始条件已发送(主机模式) |
| 1 | ADDR | 地址已发送(主机)或地址匹配(从机) |
| 2 | BTF | 字节发送完成,DR已空 |
| 6 | RxNE | 数据寄存器非空,有数据可读 |
| 7 | TxE | 数据寄存器空,可以写入新数据 |
| 8 | BUSY | 总线忙。1表示总线上有通信 |
| 9 | MSL | 主从标志。1为主机,0为从机 |
| 10 | AF | 应答失败。从机没回应 |
| 14 | TIMEOUT | 超时错误。SCL被拉低太久 |
4.3.4 状态寄存器SR2
SR2主要包含从机模式下的状态信息,比如:
- DUALF:双地址标志,表示是否匹配了第二个地址
- GENCALL:广播呼叫标志,收到0x00地址时置位
- TRA:数据传输方向,1表示发送,0表示接收
- BUSY:总线忙标志(和SR1中的BUSY是同一个)
主机模式下,SR2用得不多。但如果你做从机,这个寄存器就很重要了。
4.3.5 数据寄存器DR
DR是收发数据的通道。写DR就是发送数据,读DR就是接收数据。注意:DR只有8位,一次只能处理一个字节。
操作DR时要注意:
- 发送时:先检查TxE位,确保DR为空,再写入
- 接收时:先检查RxNE位,确保DR有数据,再读取
- 连续收发:每字节后都要等待状态标志,不能一股脑全塞进去
4.4 寄存器配置实战流程
下面是一个典型的主机发送配置流程,我把它整理成了步骤列表:
- 使能GPIO时钟和I2C时钟:RCC_APB1PeriphClockCmd()
- 配置GPIO为开漏输出:SCL和SDA都要配成AF_OD模式
- 复位I2C外设:设置CR1的SWRST位,再清零
- 配置CR2的FREQ位:设置APB时钟频率
- 配置CCR寄存器:设置SCL时钟频率(标准/快速模式)
- 配置TRISE寄存器:设置SCL上升时间
- 使能I2C外设:设置CR1的PE位
- 产生起始条件:设置CR1的START位
- 等待SB位置1:确认起始条件已发送
- 写入从机地址:写DR寄存器
- 等待ADDR位置1:确认地址已发送并收到应答
- 发送数据:写DR,等待TxE,重复直到数据发完
- 产生停止条件:设置CR1的STOP位
这个流程我用了很多年,基本没出过问题。你写驱动时,可以照着这个顺序来。
4.5 常见寄存器配置错误
最后,我总结几个自己踩过的坑:
- FREQ位没配准:SCL频率会乱,从机不响应。一定要根据APB时钟算好
- PE位使能过早:还没配完参数就开外设,状态可能异常。建议最后再使能
- 没等标志位就操作DR:数据丢失或错位。每个字节都要等
- ACK位没置1:接收模式下,从机收不到应答信号,通信会中断
- SWRST用完后没清零:外设一直处于复位状态,无法工作