IIC通信解析
一、IIC总线核心认知
1. 总线结构与特性
IIC总线仅包含两根线:
- SDA(Serial Data):双向数据线,用于传输数据;
- SCL(Serial Clock):时钟线,由主机提供同步时钟。
其核心特性是多主多从:同一总线上可连接多个主机和从机,每个设备有唯一的7/10位地址,通过地址区分通信对象。
2. 线与特性:总线电平的核心规则
IIC总线采用线与逻辑(多个设备共享总线,实际电平由所有设备的输出共同决定),用户笔记中的表格清晰展示了线与逻辑:
| chipA输出 | chipB输出 | 实际电平 |
|---|---|---|
| 0 | 0 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
| 0 | 1 | 0 |
关键结论:只要有一个设备输出低电平,总线电平就是低;只有所有设备都输出高电平时,总线才是高。
3. 硬件驱动模式:开漏输出+上拉电阻
IIC设备的SDA/SCL引脚必须配置为开漏输出模式(不能用推挽模式),并外接上拉电阻(通常4.7k~10kΩ),原因如下:
- 开漏模式下,设备只能"拉低总线",无法"主动输出高电平";高电平由上拉电阻提供,天然契合线与逻辑;
- 推挽模式下,若两个设备同时输出高低电平,会导致总线短路(用户笔记中"不允许mos1和mos2同时导通"即为此理)。
用户笔记中MOS管控制的开漏模式表:
| mos1 | mos2 | pad电平 |
|---|---|---|
| 0 | 1 | 0 |
| 1 | 0 | 1(上拉) |
| 1 | 0 | 高阻/悬空态 |
| 1 | 1 | 不允许(短路) |
二、IIC通信核心时序
IIC的通信由起始信号(Start)、地址/数据传输、应答(ACK/NACK)、停止信号(Stop) 四个核心环节组成,用户笔记中的时序图清晰展示了这些环节。
1. 起始信号(Start)
- 触发条件:SCL为高电平时,SDA由高变低;
- 作用:告诉总线上的设备"通信即将开始",所有设备准备接收地址。
2. 数据传输规则
IIC以字节(8位) 为单位传输数据,传输时需遵循:
- SCL低电平时:发送方可以修改SDA电平(准备数据);
- SCL高电平时:SDA电平必须稳定,接收方采样数据(不能修改);
- 数据传输遵循MSB优先(高位先传)。
3. 应答机制(ACK/NACK)
每传输1字节数据后,接收方需要回复应答信号:
- ACK(应答):接收方拉低SDA(SCL高时),表示"数据接收成功";
- NACK(非应答):接收方不拉低SDA,或释放总线(高电平),表示"数据接收失败"或"通信结束"。
4. 停止信号(Stop)
- 触发条件:SCL为高电平时,SDA由低变高;
- 作用:告诉总线上的设备"本次通信结束",总线恢复空闲状态。
三、IIC写操作完整流程(结合用户流程图)
以"主机向从机指定寄存器写数据"为例,流程如下(对应用户的写操作流程图):
- 初始化准备:清除仲裁标志、中断标志,设置主机发送模式;
- 发Start信号:主机置位"主模式位",触发Start;
- 发从机地址(写方向):从机地址左移1位,最低位设0(表示写);
- 等从机应答:若收到ACK,继续;否则终止;
- 发寄存器地址:告诉从机"要写哪个寄存器";
- 等从机应答:确认从机收到寄存器地址;
- 发数据:依次发送要写入的数据,每发1字节等一次应答;
- 发Stop信号:清除"主模式位",触发Stop,等待总线空闲。
四、IMX6ULL IIC代码实战解析
用户提供了IMX6ULL的IIC初始化与写操作代码,以下是关键步骤的原理对应:
1. 头文件与寄存器宏定义
先定义IIC控制器的核心寄存器位(对应IMX6ULL的I2CR/I2SR寄存器):
c
// I2CR(控制寄存器)关键位
#define I2CR_IEN (1 << 7) // IIC使能位
#define I2CR_MSTA (1 << 5) // 主模式位(置1=主机,触发Start)
#define I2CR_MTX (1 << 4) // 发送模式位(置1=发送,0=接收)
#define I2CR_RSTA (1 << 2) // 重发起始位
// I2SR(状态寄存器)关键位
#define I2SR_IIF (1 << 1) // 中断标志(传输1字节后置1)
#define I2SR_RXAK (1 << 0) // 接收的应答类型(0=ACK,1=NACK)
2. IIC初始化函数:硬件层配置
初始化分为引脚配置 和IIC控制器配置两部分:
c
void i2c_init(I2C_Type *base)
{
// 1. 引脚复用:将UART4的引脚配置为IIC1的SDA/SCL
IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA, 1);
IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL, 1);
// 2. 电气特性:配置开漏、上拉等(0x10B0为IMX6ULL的IIC标准配置)
IOMUXC_SetPinConfig(IOMUXC_UART4_RX_DATA_I2C1_SDA, 0x10B0);
IOMUXC_SetPinConfig(IOMUXC_UART4_TX_DATA_I2C1_SCL, 0x10B0);
// 3. IIC控制器配置
base->I2CR &= ~I2CR_IEN; // 先失能IIC,复位控制器
base->IFDR = 0x15; // 设置时钟分频,对应IIC时钟频率(如100kHz)
base->I2CR |= I2CR_IEN; // 使能IIC控制器
}
- 引脚复用:IMX6ULL的引脚是多功能的,需通过IOMUXC将引脚切换为IIC功能;
- 时钟分频 :
IFDR寄存器决定IIC的通信速率(如0x15对应100kHz,需匹配从机支持的速率)。
3. 应答等待函数:确保数据传输成功
传输1字节后,需等待"中断标志(IIF)"置1,并检查应答类型:
c
int wait_i2c_iif(I2C_Type *base)
{
while ((base->I2SR & I2SR_IIF) == 0); // 等待传输完成(IIF置1)
base->I2SR &= ~I2SR_IIF; // 清除中断标志
if ((base->I2SR & I2SR_RXAK) != 0) // 检查应答:非0则NACK
{
return -1;
}
return 0; // 收到ACK,传输成功
}
4. IIC写操作函数:对应通信流程
代码完全遵循"写操作流程",每一步对应时序环节:
c
void i2c_write(I2C_Type *base, unsigned char dev_addr, unsigned char reg_addr, unsigned char *data, unsigned int len)
{
int stat = 0;
// 初始化:清除仲裁、中断标志,设为发送模式
base->I2SR &= ~(I2SR_IAL | I2SR_IIF);
base->I2CR |= I2CR_MTX;
// 1. 发Start信号:置位主模式位(MSTA)
base->I2CR |= I2CR_MSTA;
// 2. 发从机地址(写方向:最低位0)
base->I2DR = ((dev_addr << 1) | 0);
stat = wait_i2c_iif(base); // 等应答
if (stat != 0) goto stop;
// 3. 发寄存器地址
base->I2DR = reg_addr;
stat = wait_i2c_iif(base);
if (stat != 0) goto stop;
// 4. 发数据(循环发送len字节)
int i = 0;
for (; i < len; i++)
{
base->I2DR = data[i];
stat = wait_i2c_iif(base);
if (stat == -1) goto stop;
}
stop:
// 5. 发Stop信号:清除主模式位
base->I2CR &= ~I2CR_MSTA;
while ((base->I2SR & I2SR_IBB) != 0); // 等待总线空闲
delay_ms(5);
}
base->I2CR |= I2CR_MSTA:置位主模式位,触发Start信号;((dev_addr << 1) | 0):从机地址左移1位,最低位0表示"写操作";goto stop:若某一步收到NACK,直接跳转到Stop环节终止通信。
五、总结
IIC通信的核心是"线与逻辑+时序规则":
- 硬件层依赖开漏输出+上拉电阻实现线与,避免总线短路;
- 通信层通过Start/Stop信号 界定通信范围,SCL高低电平 同步数据,ACK/NACK确认传输;
- 代码层需将寄存器操作与时序流程一一对应,确保每一步符合IIC协议。