IIC通信
“线与” 并非传统数字电路中 “多个输出端直接连接” 的物理与逻辑(需特定门电路),而是 IIC 通过总线外部上拉电阻 + 设备开漏输出(Open-Drain) 实现的逻辑效果:解决 “多主竞争” 的总线仲裁当多个主设备同时尝试占用总线时(多主模式),IIC 通过 “总线仲裁” 避免数据冲突,而仲裁的核心就是线与特性:IIC(Inter-Integrated Circuit,集成电路间总线)是由飞
1.线与特性
“线与” 并非传统数字电路中 “多个输出端直接连接” 的物理与逻辑(需特定门电路),而是 IIC 通过总线外部上拉电阻 + 设备开漏输出(Open-Drain) 实现的逻辑效果:
- 当所有设备的 SDA(串行数据线)/SCL(串行时钟线)引脚均处于高电平(释放总线) 时,总线电平由上拉电阻拉至 VDD(高电平);
- 只要任意一个设备将 SDA/SCL 引脚拉低(输出低电平),总线电平就会被拉至低电平;
- 最终总线电平的逻辑规则:只有所有设备都不拉低总线时,总线才为高;只要有一个设备拉低,总线就为低—— 这与 “逻辑与(AND)” 的真值表完全一致,因此称为 “线与”。
解决 “多主竞争” 的总线仲裁
当多个主设备同时尝试占用总线时(多主模式),IIC 通过 “总线仲裁” 避免数据冲突,而仲裁的核心就是线与特性:
- 多个主设备同时向 SDA 发送电平(高或低);
- 若某主设备发送 “高电平”,但通过线与特性检测到总线实际为 “低电平”(说明其他主设备发送了低电平),则该主设备会立即停止发送,主动放弃总线控制权;
- 最终只有 “始终发送与总线电平一致信号” 的主设备能继续占用总线,实现无冲突的仲裁,且仲裁过程不破坏已发送的有效数据。
2.相关概念
IIC(Inter-Integrated Circuit,集成电路间总线)是由飞利浦(现恩智浦 / NXP)于 1982 年推出的短距离、同步串行通信总线,核心特点是 “双线制、多主多从、共享总线”,广泛用于芯片间的低速数据交互(如传感器、存储器、显示器与 MCU/CPU 的通信)。
3.IIC通信时序
1.start信号
在 IIC 通信时序中,Start(起始)信号和Stop(停止)信号是由主设备主动生成的 “总线控制信号”,核心作用是界定一次完整 IIC 通信的 “开始” 与 “结束”,同时向总线上所有从设备传递 “总线即将被占用” 或 “总线已释放” 的状态,是 IIC 时序逻辑的 “总开关”。
Start 信号的唯一判定条件:在 SCL 保持高电平期间,SDA 从高电平跳变到低电平
2.stop信号
Stop 信号的唯一判定条件:在 SCL 保持高电平期间,SDA 从低电平跳变到高电平
3.通信时序
1. 总线空闲状态
- 当 SCL(时钟线)和 SDA(数据线)均为高电平时(由上拉电阻维持),总线处于空闲状态,所有设备未进行通信。
2. 起始信号(Start)
- 触发条件:SCL 保持高电平时,SDA 从高电平跳变为低电平(高→低)。
- 作用:标志一次通信的开始,主设备通过此信号通知所有从设备 “总线即将被占用”,从设备进入监听状态。
3. 数据传输时序
- 位传输规则:每个数据位(0 或 1)在 SCL 高电平期间保持稳定,从设备在 SCL 高电平时采样 SDA 电平(读取数据);SDA 的电平变化只能在 SCL 低电平时进行(避免数据错乱)。
- 字节传输:每次传输 1 字节(8 位),按 “高位在前(MSB)” 的顺序发送。
4. 应答信号(ACK/NACK)
- 每传输 1 字节后,紧跟 1 个应答位:
- ACK(应答):接收方在应答位期间(SCL 高电平)拉低 SDA,表示 “已正确接收”。
- NACK(非应答):接收方释放 SDA(由上拉电阻维持高电平),表示 “未接收” 或 “结束传输”。
- 应答位由接收方(从设备或主设备,取决于传输方向)控制。
5. 重复起始信号(Repeated Start)
- 若主设备需要在一次通信中切换从设备或传输方向(如先写后读),无需发送 Stop 信号,直接再次发送 Start 信号(时序与首次 Start 相同)。
- 作用:避免总线被其他主设备抢占,实现连续通信。
6. 停止信号(Stop)
- 触发条件:SCL 保持高电平时,SDA 从低电平跳变为高电平(低→高)。
- 作用:标志一次通信的结束,主设备释放总线,从设备回到空闲状态。
4.相关配置
先配置IIC的初始化
注意设置波特率之前最好先将I2Cn_I2CR的IEN清0,设置好之后再置1
分频:由于时钟是irq_clk_root为66MHz,需要配置为100KHz,故分频系数为660,查阅手册知,与660最相近的为0x15
示例
void i2c1_init(void)
{
IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA, 1); //sion位置1表示检测引脚电平状态
IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL, 1);
//设置引脚电器特性
IOMUXC_SetPinConfig(IOMUXC_UART4_RX_DATA_I2C1_SDA, 0xF0B0);
IOMUXC_SetPinConfig(IOMUXC_UART4_TX_DATA_I2C1_SCL, 0xF0B0);
//失能I2C1(会进行复位)
I2C1->I2CR &= ~I2CR_IEN;
//修改分频值
I2C1->IFDR = 0x15;
//使能I2C1
I2C1->I2CR |= I2CR_IEN;
}
配置中断相关
-
IIF 位的状态定义:
- 当 IIF 位为 1 时:表示 IIC 模块已产生中断事件(如数据发送完成、数据接收完成、地址匹配、总线错误等,具体触发源取决于芯片设计)。
- 当 IIF 位为 0 时:表示 IIC 模块未产生中断,或中断已被清除。
-
触发场景:
IIF 位的置位(变为 1)通常与 IIC 模块的具体操作相关,常见触发条件包括:- 主设备发送完 1 字节数据;
- 从设备接收到 1 字节数据;
- 从设备检测到自身地址被主设备匹配(地址应答);
- 总线出现错误(如仲裁失败、无应答等)。
-
软件操作逻辑:
- 当 IIF 位为 1 时,会触发 CPU 的中断请求(需先使能 IIC 中断),程序会进入中断服务函数处理相应事件。
- 中断处理完成后,软件需通过特定指令清除 IIF 位(通常是向该位写 0,或读取状态寄存器后写特定值,具体依赖芯片手册),否则会持续触发中断。
I2CR 的 TXAK 位:控制 “本设备发送的应答信号”
- 全称:通常为 “Transmit Acknowledge”(发送应答)位,位于控制寄存器(I2CR)中,用于配置本设备在接收数据后,向发送方返回的应答类型。
- 功能:
- 当 TXAK=0 时:本设备在接收完 1 字节数据后,会主动拉低 SDA 线(在应答位周期内),向发送方返回ACK(应答信号),表示 “已正确接收数据”。
- 当 TXAK=1 时:本设备在接收完 1 字节数据后,会释放 SDA 线(由上拉电阻维持高电平),向发送方返回NACK(非应答信号),表示 “未接收数据” 或 “要求发送方停止传输”。
- 适用场景:
- 若本设备是从设备:接收主设备发送的数据后,通过 TXAK 配置是否返回 ACK(默认通常为 0,即返回 ACK)。
- 若本设备是主设备:接收从设备返回的数据后,通过 TXAK 配置是否返回 ACK(例如,主设备读取最后 1 字节数据时,可设置 TXAK=1 返回 NACK,告知从设备 “数据已读完”)。
- 本质:主动控制信号,由本设备软件配置,决定向对方发送 ACK 还是 NACK。
2. I2SR 的 RXAK 位:指示 “本设备收到的应答信号”
- 全称:通常为 “Receive Acknowledge”(接收应答)位,位于状态寄存器(I2SR)中,用于指示本设备作为发送方时,是否收到了接收方返回的应答信号。
- 功能:
- 当 RXAK=0 时:表示本设备发送完 1 字节数据后,接收到了接收方返回的ACK 信号(SDA 被拉低),即 “接收方已正确接收数据”。
- 当 RXAK=1 时:表示本设备发送完 1 字节数据后,接收到的是NACK 信号(SDA 为高电平),即 “接收方未接收数据” 或 “通信异常”(如从设备地址不存在、数据溢出等)。
- 适用场景:
- 若本设备是主设备:发送地址或数据后,通过 RXAK 判断从设备是否应答(例如,RXAK=1 可能意味着从设备未连接或地址错误)。
- 若本设备是从设备:发送数据给主设备后,通过 RXAK 判断主设备是否接收(例如,主设备返回 NACK 表示 “无需继续发送”)。
- 本质:被动状态指示,由硬件自动更新(根据总线上的应答信号),供软件查询通信是否正常。
每次发送数据后从机都会回应ACK还是NACK故需要写一个函数来判断
int i2c_wait_iif(I2C_Type *base)
{
while((base->I2SR & I2SR_IIF) == 0)
{
//超时机制
}
//清除中断(写0清除中断)
base->I2SR &= ~(I2SR_IIF);
if ((base->I2SR & I2SR_RXAK) != 0)
{
return -1; //NACK
}
return 0; //ACK
}
在此用的是RXAK,来读取从机回应的是什么
配置主机向从机写
示例
void i2c_write(I2C_Type *base, unsigned char dev_addr, unsigned char reg_addr, unsigned char *data, int len)
{
int statu;
int i = 0;
//清除仲裁丢失标志IAL,清除中断标志IIF, 切换收发模式到发送
base->I2SR &= ~(I2SR_IAL | I2SR_IIF);
base->I2CR |= (I2CR_MTX);
//产生start信号
base->I2CR |= (I2CR_MSTA);
//发送从机地址
base->I2DR = ((dev_addr << 1) | 0); //表示7个从机地址位和一个数据流向位
statu = i2c_wait_iif(base);
if (statu == -1) goto stop;
//发送从机要写的寄存器的地址
base->I2DR = reg_addr;
statu = i2c_wait_iif(base);
if (statu == -1) goto stop;
//循环发要传送的数据
for (; i < len; i++)
{
base->I2DR = data[i];
statu = i2c_wait_iif(base);
if (statu == -1) goto stop;
}
stop:
//结束发送stop信号
base->I2CR &= ~I2CR_MSTA;
//检测总线知道空闲
while ((base->I2SR & I2SR_IBB) != 0)
{
printf("i2c is busy\n");
}
delay_ms(5);
}
配置主机 从 从机中读取数据
注意 在读取到最后一个字节的时候,注意必须回复NACK,否则可能会导致从机通信异常
更多推荐
所有评论(0)