一、IIC基础概念
-
起源与定义
- 由飞利浦(Philips)1982年开发,全称Inter-Integrated Circuit,即集成电路间通信协议。
- 目标:实现低速、短距离、多设备间的简单通信,广泛用于嵌入式系统。
-
物理层特性
- 双线制:仅需两根信号线——时钟线(SCL)和数据线(SDA)。
- 开漏输出(OD):需外接上拉电阻(通常4.7kΩ,高速场景可降至1kΩ),默认高电平,支持“线与”逻辑。
- 电压兼容性:支持1.8V、3.3V、5V等多电压域,通过电平转换电路可跨电压通信。
- 总线负载:单总线最多挂载约10-128个设备(取决于总线电容,通常≤400pF)。
- 多主多从:允许多个主设备同时控制总线,通过仲裁机制避免冲突。
二、协议层核心机制
-
信号定义
- 起始信号(Start):SCL高电平时,SDA由高变低,标志通信开始。
- 停止信号(Stop):SCL高电平时,SDA由低变高,标志通信结束。
- 重复起始(Repeated Start):在不发送Stop的情况下,重新发送Start信号,用于切换主从角色或改变传输方向(如主→从写切换为从→主读)。
-
数据传输规则
- 数据有效性:SCL高电平时,SDA必须保持稳定;SCL低电平时,SDA可改变电平。
- 字节传输:每次传输8位数据,高位在前,低位在后,后跟1位应答信号(ACK/NACK)。
- ACK(应答):接收方在第9个时钟周期拉低SDA,表示数据接收成功。
- NACK(非应答):接收方保持SDA高电平,可能因地址错误、数据溢出或主动终止传输。
-
寻址方式
- 7位地址(主流):
- 地址范围:
0x00
~0x7F
(128个地址),其中保留地址:0x00
:通用呼叫地址(广播模式)。0x01
~0x03
:保留给不同用途(如SMBus警报响应)。0x7E
~0x7F
:保留,用户不可用。
- 实际可用地址:112个(排除16个保留地址)。
- 地址范围:
- 10位地址:
- 第一字节(高 7 位 + R/W 位):
- 固定前缀:
11110
(二进制,占 5 位),用于标识 10 位寻址模式。 - 地址高位:
XX
(占 2 位,对应 10 位地址的最高两位,范围 00~11)。 - 读写位(R/W):0 表示写操作,1 表示读操作。
- 示例:若 10 位地址为
0x123
(二进制0010010011
),则第一字节为0b1111000
(0xF0),R/W 位为 0。
- 固定前缀:
- 第二字节(地址低位):
- 完整 8 位地址(对应 10 位地址的低 8 位)。
- 示例:上述地址的第二字节为
0x23
(二进制00100011
)。
- 第一字节(高 7 位 + R/W 位):
-
数据帧结构
- 写操作:
Start → 从地址(7位)+ W(0) → ACK → 数据字节 → ACK → ... → Stop
- 读操作:
Start → 从地址 + W → ACK → 寄存器地址 → ACK → Repeated Start → 从地址 + R(1) → ACK → 数据字节 → NACK(主设备终止) → Stop
- 多字节传输:从设备收到ACK后继续发送/接收数据,直到主设备发送NACK或Stop。
- 写操作:
-
仲裁与时钟同步
- 仲裁机制:多主设备冲突时,SDA线电平以“线与”为准,发送低电平的主设备获胜,发送高电平的设备检测到冲突后退出。
- 时钟同步:通过“线与”使所有主设备的SCL保持低电平至最长的低电平时间,高电平时间由最短的决定,确保多主设备时钟一致。
三、时序与速度模式
-
时序参数
- 建立时间(tSU:STA):Start信号前,SDA高电平保持时间(标准模式≥4.7μs)。
- 保持时间(tHD:STA):Start信号后,SDA低电平保持时间(标准模式≥4μs)。
- 时钟频率:
- 标准模式(SM):100kHz,适用于低速设备(如EEPROM)。
- 快速模式(FM):400kHz,通用场景(如传感器)。
- 高速模式(FM+):3.4MHz,需器件支持(如高速ADC)。
- 超高速模式(HS):5MHz(仅主机发送,从机不响应,用于快速数据下载)。
-
上拉电阻计算
-
公式:
-
目标:确保SDA上升时间符合时序要求,同时避免过大电流(通常取2.2kΩ~10kΩ)。
-
四、设备角色与分类
-
设备类型
- 主设备:生成SCL时钟,发起通信(如MCU)。
- 从设备:响应主设备寻址,接收或发送数据(如EEPROM、传感器)。
- 复合设备:可在主/从角色间切换(如某些FPGA)。
-
从设备地址
- 固定部分:由器件厂商分配(如EEPROM的前4位固定为
1010
)。 - 可编程部分:通过硬件引脚(A0、A1)或软件配置,避免地址冲突。
- 固定部分:由器件厂商分配(如EEPROM的前4位固定为
五、典型应用场景
- 存储设备:EEPROM(如24C02)、FRAM。
- 传感器:温度传感器(如DS1621)、加速度计(如MMA8452Q)、陀螺仪。
- 外设控制:LCD驱动(如SSD1306)、ADC/DAC(如PCF8591)、键盘扫描芯片(如TCA8418)。
- 系统管理:SMBus(电源管理,如电池监控)、PMBus(功率器件配置)。
六、优缺点与注意事项
-
优点
- 接线简单,节省IO口。
- 支持多主多从,地址空间灵活(7/10位)。
- 跨电压域通信方便(通过上拉电阻)。
-
缺点
- 速度受限(最高3.4MHz,HS模式5MHz但功能有限),低于SPI(通常几十MHz)。
- 总线负载敏感,长距离或多设备时需考虑电容匹配。
- 仲裁机制复杂,软件实现需处理冲突。
-
常见问题
- 地址冲突:确保每个从设备地址唯一(通过硬件引脚或软件配置)。
- ACK缺失:检查从设备是否上电、地址正确,或总线是否被拉低(短路)。
- 时序错误:高速模式下需严格匹配SCL/SDA时序,避免使用软件模拟(推荐硬件I²C外设)。
- 总线锁定:若SDA/SCL持续低电平,可通过复位主设备、断开电源或强制释放总线(上拉电阻作用)。
七、扩展与变种
-
SMBus(系统管理总线)
- IIC的子集,用于低速系统管理(如主板温度监控),定义了更严格的时序和协议(如超时机制)。
-
PMBus(电源管理总线)
- 基于IIC,专门用于电源设备通信(如电源模块配置、状态读取)。
-
软件IIC vs 硬件IIC
- 软件模拟:通过GPIO模拟时序,灵活适配非标准时序,但速度慢(受MCU主频限制),适合低速设备。
- 硬件外设:利用芯片内置I²C控制器,自动处理时序和ACK/NACK,效率高但需配置寄存器,兼容性依赖芯片实现。
八、实战建议
-
硬件设计
- 短距离走线(≤10cm),避免高速模式下的信号反射。
- 上拉电阻靠近从设备,或在总线末端并联电容(≤400pF)。
-
软件实现
- 主设备需处理ACK/NACK,超时重试(通常3次)。
- 多字节读写时,注意从设备是否支持自动地址递增(如EEPROM页写入)。
-
调试工具
- 逻辑分析仪(如Saleae)抓取SCL/SDA波形,分析时序错误。
- 万用表测量总线电压,确认上拉电阻是否正常工作。
代码示例
这是一个基于STM32的IIC通信示例代码,这里以STM32Cube HAL库为例,实现STM32作为主设备向从设备发送数据并读取数据的功能。
#include "stm32f4xx_hal.h"
// 定义I2C句柄
I2C_HandleTypeDef hi2c1;
// 从设备地址
#define SLAVE_ADDRESS 0x48
// 系统时钟配置
void SystemClock_Config(void);
// 外设初始化
static void MX_GPIO_Init(void);
static void MX_I2C1_Init(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
uint8_t tx_data = 0x10; // 要发送的数据
uint8_t rx_data; // 接收的数据
while (1)
{
// 向从设备发送数据
if (HAL_I2C_Master_Transmit(&hi2c1, SLAVE_ADDRESS << 1, &tx_data, 1, 100) != HAL_OK)
{
// 处理发送错误
Error_Handler();
}
// 从从设备读取数据
if (HAL_I2C_Master_Receive(&hi2c1, SLAVE_ADDRESS << 1, &rx_data, 1, 100) != HAL_OK)
{
// 处理接收错误
Error_Handler();
}
// 可以在这里添加对接收数据的处理逻辑
HAL_Delay(1000);
}
}
// 系统时钟配置
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** 初始化RCC振荡器
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** 初始化RCC时钟
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
// I2C1初始化
static void MX_I2C1_Init(void)
{
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000;
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c1) != HAL_OK)
{
Error_Handler();
}
}
// GPIO初始化
static void MX_GPIO_Init(void)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
}
// 错误处理函数
void Error_Handler(void)
{
while(1)
{
}
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
// 可以自行在这里添加错误处理逻辑
while(1)
{
}
}
#endif
代码说明:
- 系统初始化:在
main
函数里,首先对HAL库、系统时钟、GPIO和I²C进行初始化操作。 - I²C通信:借助
HAL_I2C_Master_Transmit
函数向从设备发送数据,利用HAL_I2C_Master_Receive
函数从从设备读取数据。 - 错误处理:在数据发送和接收过程中,若出现错误就会调用
Error_Handler
函数进行处理。
注意事项:
- 要依据实际情况对
SLAVE_ADDRESS
进行修改。 - 该示例代码假设使用的是STM32F4系列芯片,若使用其他系列芯片,可能需要对部分配置进行调整。
总结
IIC以其简洁性和灵活性成为嵌入式系统的主流通信协议,核心在于理解物理层的开漏特性、协议层的寻址与应答机制,以及多主环境下的仲裁逻辑。掌握时序要求、设备寻址和典型应用场景,即可应对95%以上的开发需求。实际项目中,需结合器件手册调试,注意总线负载和时序匹配,确保通信稳定。