1. I2C模块概述
STM32F103C8T6芯片内置2个独立的I2C接口(I2C1和I2C2),支持标准I2C协议(最高400 kHz)和SMBus协议(系统管理总线)。其核心功能包括:
- 多主机/从机模式:支持多设备总线竞争及仲裁。
- 双向通信:通过串行数据线(SDA)和时钟线(SCL)实现半双工通信。
- 硬件从地址识别:可编程7位或10位从机地址,支持广播呼叫。
- 时钟控制:主模式下可配置SCL时钟频率,支持时钟拉伸(Clock Stretching)。
- 中断/DMA支持:通过事件中断或DMA提升数据传输效率。
2. I2C架构与核心组件
-
时钟生成器
- 由APB1总线时钟分频产生SCL时钟频率,公式为:
fSCL=(2×CCR)fAPB1
其中CCR为I2C时钟控制寄存器的分频值。 - 支持标准模式(100 kHz)和快速模式(400 kHz)。
- 由APB1总线时钟分频产生SCL时钟频率,公式为:
-
数据寄存器(DR)
- 8位寄存器,用于发送或接收数据字节。
- 写操作自动触发数据发送,读操作自动接收数据。
-
控制逻辑
- 通过状态寄存器(SR1/SR2)监控通信状态(如起始条件、地址匹配、数据收发完成等)。
- 支持自动生成起始条件(START)、重复起始条件(Repeated START)和停止条件(STOP)。
3. 工作模式
主模式(Master Mode)
-
主发送模式
主机发起通信,向从机发送数据。流程:- 发送起始条件(START)。
- 发送从机地址(含读/写位)。
- 发送数据字节。
- 发送停止条件(STOP)或重复起始条件。
-
主接收模式
主机发起通信,从从机接收数据。流程:- 发送起始条件(START)。
- 发送从机地址(读标志位)。
- 接收数据字节。
- 发送停止条件(STOP)。
从模式(Slave Mode)
-
从发送模式
从机响应主机请求并发送数据。需预先配置自身地址。 -
从接收模式
从机接收主机发送的数据。支持地址匹配和广播呼叫(Broadcast Call)检测。
4. 中断与事件管理
-
中断类型
- 通信事件:起始条件检测、地址匹配、数据字节收发完成。
- 错误事件:总线错误(Bus Error)、仲裁丢失(Arbitration Loss)、ACK故障(ACK Failure)。
- 缓冲区事件:DMA请求、数据寄存器空/满。
-
中断配置
- 通过I2C_CR2寄存器使能中断源(如ITEVTEN、ITERREN)。
- 中断服务函数需手动清除标志位(通过读SR1/SR2寄存器或写DR寄存器)。
5. 关键特性与配置参数
-
时钟配置
- 标准模式:tHIGH/tLOW 时间符合I2C规范(最小4.7 μs)。
- 快速模式:支持时钟占空比调节(可通过I2C_CCR寄存器配置)。
-
从地址配置
- 7位地址模式:支持112个独立地址。
- 10位地址模式:扩展地址范围,需两次地址发送。
-
总线超时检测
- 可选总线空闲超时(Timeout)功能,防止总线死锁。
6. 错误处理机制
-
仲裁丢失(ARLO)
多主机竞争时,若检测到自身发送电平与总线实际电平不一致,自动切换为从机模式。 -
总线错误(BERR)
检测到非法的起始/停止条件时触发,需软件复位总线。 -
ACK故障(AF)
从机未应答地址或数据时触发,主机可重试或终止传输。
7. DMA支持
-
数据传输优化
- 通过DMA通道实现数据批量传输,减少CPU占用。
- 发送和接收方向可独立配置DMA请求。
-
配置步骤
- 使能I2C的DMA请求(I2C_DLEN设置数据长度)。
- 配置DMA通道,指定内存与外设地址(I2C_DR)。
- 启动DMA传输。
8.上完整程序模版,复制可用,已详细注释
/* 包含STM32标准外设库头文件 */
#include "stm32f10x.h"
/* 硬件配置宏(示例:I2C1,SCL-PB6,SDA-PB7,目标从机地址0xA0) */
#define I2Cx I2C1
#define I2C_SCL_PIN GPIO_Pin_6
#define I2C_SDA_PIN GPIO_Pin_7
#define I2C_GPIO_PORT GPIOB
#define I2C_CLOCK RCC_APB1Periph_I2C1
#define SLAVE_ADDRESS 0xA0 // 7位地址左移1位后的值(0xA0 = 0x50 << 1)
#define I2C_SPEED 400000 // 通信速率(单位:Hz,范围:0-400k)
/**
* @brief I2C初始化配置函数
* @param 无
* @retval 无
*/
void I2C_Config(void) {
GPIO_InitTypeDef GPIO_InitStruct;
I2C_InitTypeDef I2C_InitStruct;
/* 1. 使能时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // GPIOB时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); // I2C1时钟
/* 2. 配置GPIO为复用开漏模式 */
GPIO_InitStruct.GPIO_Pin = I2C_SCL_PIN | I2C_SDA_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD; // 复用开漏模式
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 高速模式
GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStruct);
/* 3. I2C参数配置 */
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; // 标准I2C模式
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; // 时钟占空比(2:1,仅快速模式有效)
I2C_InitStruct.I2C_OwnAddress1 = 0x00; // 主机自身地址(从机模式时有效)
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; // 使能ACK响应
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // 7位地址模式
I2C_InitStruct.I2C_ClockSpeed = I2C_SPEED; // 通信速率(最大400kHz)
I2C_Init(I2Cx, &I2C_InitStruct);
/* 4. 使能I2C模块 */
I2C_Cmd(I2Cx, ENABLE);
}
/**
* @brief I2C发送数据函数
* @param devAddr: 从机地址(7位地址左移1位后的值)
* @param regAddr: 寄存器地址
* @param pData: 待发送数据指针
* @param len: 数据长度
* @retval 状态:0-成功,1-失败
*/
uint8_t I2C_Write(uint8_t devAddr, uint8_t regAddr, uint8_t *pData, uint16_t len) {
uint32_t timeout = 10000; // 超时计数器
/* 1. 发送START条件 */
I2C_GenerateSTART(I2Cx, ENABLE);
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT)) { // 等待EV5事件
if (--timeout == 0) return 1;
}
/* 2. 发送从机地址(写模式) */
I2C_Send7bitAddress(I2Cx, devAddr, I2C_Direction_Transmitter);
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { // 等待EV6事件
if (--timeout == 0) return 1;
}
/* 3. 发送寄存器地址 */
I2C_SendData(I2Cx, regAddr);
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { // 等待EV8_2事件
if (--timeout == 0) return 1;
}
/* 4. 发送数据字节 */
for (uint16_t i = 0; i < len; i++) {
I2C_SendData(I2Cx, pData[i]);
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {
if (--timeout == 0) return 1;
}
}
/* 5. 发送STOP条件 */
I2C_GenerateSTOP(I2Cx, ENABLE);
return 0;
}
/**
* @brief I2C接收数据函数
* @param devAddr: 从机地址(7位地址左移1位后的值)
* @param regAddr: 寄存器地址
* @param pData: 接收数据缓冲区指针
* @param len: 数据长度
* @retval 状态:0-成功,1-失败
*/
uint8_t I2C_Read(uint8_t devAddr, uint8_t regAddr, uint8_t *pData, uint16_t len) {
uint32_t timeout = 10000;
/* 1. 发送寄存器地址(写模式) */
I2C_GenerateSTART(I2Cx, ENABLE);
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT)) {
if (--timeout == 0) return 1;
}
I2C_Send7bitAddress(I2Cx, devAddr, I2C_Direction_Transmitter);
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) {
if (--timeout == 0) return 1;
}
I2C_SendData(I2Cx, regAddr);
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {
if (--timeout == 0) return 1;
}
/* 2. 重新发送START条件(切换为读模式) */
I2C_GenerateSTART(I2Cx, ENABLE);
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT)) {
if (--timeout == 0) return 1;
}
/* 3. 发送从机地址(读模式) */
I2C_Send7bitAddress(I2Cx, devAddr, I2C_Direction_Receiver);
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) { // 等待EV6事件
if (--timeout == 0) return 1;
}
/* 4. 接收数据 */
for (uint16_t i = 0; i < len; i++) {
if (i == len - 1) {
I2C_AcknowledgeConfig(I2Cx, DISABLE); // 最后1字节发送NACK
}
while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)) { // 等待EV7事件
if (--timeout == 0) return 1;
}
pData[i] = I2C_ReceiveData(I2Cx);
}
/* 5. 发送STOP条件 */
I2C_GenerateSTOP(I2Cx, ENABLE);
I2C_AcknowledgeConfig(I2Cx, ENABLE); // 恢复ACK使能
return 0;
}
/********************* 模板使用示例 *********************
int main(void) {
uint8_t data[2] = {0xAA, 0x55};
uint8_t recv[2];
SystemInit();
I2C_Config();
// 向地址0xA0的寄存器0x01写入2字节数据
I2C_Write(SLAVE_ADDRESS, 0x01, data, 2);
// 从地址0xA0的寄存器0x01读取2字节数据
I2C_Read(SLAVE_ADDRESS, 0x01, recv, 2);
while(1);
}
********************************************************/
9.模板程序功能说明
-
硬件抽象封装
宏定义硬件连接参数(I2C端口、引脚、地址),便于移植修改。 -
完整I2C流程实现
I2C_Config()
:完成GPIO复用配置、时钟设置、I2C参数初始化。I2C_Write()
:实现带寄存器地址的数据写入(常见于EEPROM、传感器)。I2C_Read()
:实现带寄存器地址的数据读取(支持多字节连续读)。
-
错误处理
使用超时机制防止总线死锁,返回操作状态码。
10.关键参数详解
-
I2C模式参数
I2C_Mode_I2C // 标准I2C模式 I2C_DutyCycle_2 // 快速模式占空比2:1(t_low=2*t_high) I2C_Ack_Enable // 使能ACK自动响应
-
事件标志(用于状态检测)
I2C_EVENT_MASTER_MODE_SELECT // START条件已发送(EV5) I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED // 从机地址已应答(EV6) I2C_EVENT_MASTER_BYTE_TRANSMITTED // 数据字节发送完成(EV8_2) I2C_EVENT_MASTER_BYTE_RECEIVED // 数据字节接收完成(EV7)
-
地址格式
- 7位从机地址需左移1位后传入(例如:设备地址0x50 →
0x50 << 1 = 0xA0
)。 - 读/写模式通过
I2C_Direction_Transmitter
或I2C_Direction_Receiver
指定。
- 7位从机地址需左移1位后传入(例如:设备地址0x50 →
11.使用注意事项
-
硬件连接
- SCL/SDA必须外接4.7kΩ上拉电阻至3.3V。
- 长距离通信需考虑总线电容(建议加缓冲器)。
-
时序匹配
- 确保从机支持主机的时钟速率(例如:EEPROM通常最高支持400kHz)。
-
错误恢复
- 检测超时后建议执行总线复位操作:
I2C_SoftwareResetCmd(I2Cx, ENABLE); I2C_SoftwareResetCmd(I2Cx, DISABLE);
- 检测超时后建议执行总线复位操作:
-
多设备管理
- 同一总线上多个从机需分配唯一地址,避免冲突。
12. 应用场景
- 传感器通信:连接温度传感器(如LM75)、加速度计(MPU6050)。
- 存储设备读写:访问EEPROM(AT24Cxx系列)。
- 多设备管理:控制LCD屏、RTC时钟(DS3231)等外设。
- 系统监控:通过SMBus管理电源或电池状态。
13. 使用注意事项
-
引脚配置
- SDA和SCL需配置为复用开漏模式(GPIO_Mode_AF_OD),并外接4.7kΩ上拉电阻。
- 避免长距离布线,防止信号衰减或干扰。
-
时序兼容性
- 从机设备需支持主机的时钟频率,否则需启用时钟拉伸功能。
-
总线冲突处理
- 多主机系统中,需设计重试机制(如随机延迟后重新发起传输)。
-
低功耗模式
- I2C模块在睡眠模式下可保持活动,但需配置相应唤醒源(如地址匹配中断)。
14. 性能限制
- 最大速率
- 快速模式下理论速率为400 kbps,实际速率受APB1时钟和布线质量影响。
- 从机地址容量
- 单个I2C总线最多支持112个7位地址设备,实际数量受总线电容限制。
- 中断延迟
- 高优先级任务可能影响实时性,需结合DMA或优化中断服务函数。