I2C(Inter-Integrated Circuit,集成电路间总线)


是由飞利浦(现恩智浦 / NXP)开发的一种同步串行通信总线(有时钟线,数据线只有一根),核心特点是 "两根线实现多设备互联",广泛应用于芯片间近距离数据传输场景,如嵌入式系统中单片机与传感器(温湿度、加速度)、存储芯片(EEPROM)、显示驱动芯片等的通信。
相较于UART (需 TX/RX 交叉连接,多设备通信需多组线),I2C 的核心优势是 **"总线共享"**------ 仅通过 SDA(Serial Data,串行数据线)和 SCL(Serial Clock,串行时钟线)两根线,即可实现 "1 个主设备(如单片机)控制多个从设备(如多个传感器)",极大简化了硬件布线。
一、I2C 的核心组成:2 根线 + 主从架构
- 硬件线路(仅 2 根核心线,均需上拉电阻)
I2C 的两根线均为双向信号线,且必须通过上拉电阻(通常 4.7kΩ~10kΩ)连接到电源(如 3.3V 或 5V,与设备供电一致),默认处于高电平状态,具体作用如下:
- SDA(串行数据线):用于传输实际数据(双向,主设备和从设备均可发送 / 接收);
- SCL(串行时钟线) :用于同步数据传输节奏(由主设备独占生成,从设备被动跟随时钟信号采样数据,确保收发时序一致)。

为什么需要上拉电阻
上拉电阻的核心作用,是解决开漏 / 开集输出'只能拉低、不能主动输出高电平'的问题:当设备不需要输出低电平时,上拉电阻将信号线拉到电源电压(稳定高电平,确保总线默认状态不悬空);当设备需要输出低电平时,直接把信号线拉到 GND 即可,无需担心冲突

上拉电阻核心作用总结表(基于开漏 / 开集输出与线与逻辑)
应用场景分类 | 核心作用 | 具体原理与说明 | 典型示例 |
---|---|---|---|
针对开漏 / 开集输出 | 1. 提供默认高电平,消除悬空态 | 开漏 / 开集输出仅能拉低电平(逻辑 0),晶体管截止时信号线悬空(电平不确定);上拉电阻将信号线接电源,截止时提供电流回路,把信号线拉至电源电压(稳定逻辑 1),实现 0/1 逻辑全覆盖,避免误触发。 | 单片机 GPIO 开漏输出的按键检测(按键未按时,GPIO 通过上拉电阻保持高电平) |
2. 限流保护,防止芯片烧毁 | 开漏 / 开集输出导通时,信号线直接接 GND;上拉电阻串联在电源与信号线之间,将回路电流限制在安全范围(如 3.3V/4.7kΩ≈0.7mA),避免短路大电流烧毁晶体管或芯片引脚。 | I2C 总线 SDA/SCL 线的上拉电阻(防止设备导通时过流) | |
针对线与逻辑 | 1. 统一总线高电平基准,确保逻辑一致 | 总线上只要有一个设备将信号线拉低(输出逻辑 0),整个总线的电平就为 0;只有当所有设备都不拉低信号线(即均处于高电平状态),总线电平才为 1 ,简单来说就是 "有 0 出 0,全 1 出 1"。 | I2C 总线多从设备通信(所有设备共享 SDA/SCL,高电平状态统一) |
2. 实现多设备无冲突协作 | 开漏输出 "仅能拉低"+ 上拉电阻 "统一供高电平",避免 "设备 A 输出 1、设备 B 输出 0" 的短路冲突;设备需发 0 时拉低总线,无需发时截止,不影响总线状态,实现多设备共享总线且无硬件冲突。 | I2C 总线中主设备与多个从设备的数据传输(仅地址匹配的从设备拉低总线响应,其他设备截止) |
注意:
- I2C 总线的 SDA/SCL 线:必须接 4.7kΩ~10kΩ 上拉电阻,确保总线默认处于高电平,只有设备需要通信时才拉低 SDA/SCL 生成启动信号;
- 上拉电阻过小时,驱动不足
- 上拉电阻过大时,会发生开漏
2. 主从架构(1 主多从,从设备靠地址区分)

I2C 采用严格的 "主从通信架构",规则如下:
- 主设备(Master) :唯一的 "控制方",负责:
- 发起通信(拉低 SDA 启动信号);
- 生成 SCL 时钟信号;
- 发送从设备地址(选择要通信的从设备);
- 决定数据传输方向(主→从,或从→主);
- 终止通信(释放 SDA 生成停止信号)。
- 从设备(Slave) :被动响应方,每个从设备都有一个唯一的 7 位或 10 位地址(出厂时固化或可通过引脚配置),仅当主设备发送的地址与自身地址匹配时,才会响应通信,避免多设备冲突。
示例:一个 STM32 单片机(主设备)通过 I2C 总线连接了 2 个传感器(A 和 B),主设备要给传感器 A 发指令时,会先在 SDA 上发送 "A 的地址",只有 A 会响应,B 则忽略该地址,不参与通信。
二、I2C 的通信时序:从 "启动" 到 "停止" 的完整流程
空闲时:SDA和SCL都要保持高电平
I2C 的数据传输以 "帧" 为单位,每帧包含 **"启动信号→从设备地址 + 读写位→ACK 应答→数据→停止信号",**时序严格且固定,是确保通信正确的核心,具体步骤如下:

1. 1. 启动信号(Start Condition):通信开始的标志
- 时序要求:在SCL 保持高电平时,SDA 从高电平 "快速拉低"(下降沿);
- 作用:告知总线上所有从设备 "主设备要开始通信了,请准备接收地址",此时所有从设备会开始监听 SDA 线的后续数据。(总线仲裁)

- 从设备地址 + 读写位(7 位地址 + 1 位 R/W):选择目标设备与传输方向
- 共 8 位数据,传输顺序为 "高位在前(MSB first)":
- 前 7 位:从设备的唯一地址(如温湿度传感器 SHT30 的默认地址为 0x44);
- 第 8 位:读写控制位(R/W bit):
- 0 = 写操作(主设备→从设备,如主设备给从设备发配置指令);
- 1 = 读操作(从设备→主设备,如主设备读取从设备的测量数据)。
- 从设备响应:所有从设备会将接收到的 7 位地址与自身地址对比,仅地址匹配的从设备会在第 8 位传输完成后,主动将 SDA 拉低(生成 ACK 信号),告知主设备 "地址已匹配,可继续传输数据"。
- ACK 应答信号(Acknowledge):确保数据被接收
I2C 的每 8 位数据(地址或数据)传输完成后,都需要 "应答" 来确认数据是否被正确接收,分为两种情况:
- ACK(确认应答):接收方(地址匹配的从设备,或读操作时的主设备)在第 8 位数据传输完成后,将 SDA 拉低,并在 SCL 高电平时保持低电平;
- NACK(非确认应答):接收方不拉低 SDA(SDA 保持高电平),通常表示 "数据接收完成" 或 "接收错误"(如读操作时主设备已获取所有需要的数据,会发送 NACK 告知从设备 "停止发送")。
- 注意 :应答位的 SCL 时钟仍由 主设备 生成,接收方仅需控制 SDA 电平。

- 数据传输(Data):实际数据的收发

- 时序要求:仅在 SCL 为低电平时,SDA 允许改变电平(准备数据);在 SCL 为高电平时,SDA 必须保持稳定(接收方此时采样数据,确保数据准确);
- 传输单位:每次传输 8 位数据(1 字节),高位在前,每传输 1 字节后,都需要一次 ACK/NACK 应答;
- 方向灵活:根据 "读写位" 决定方向,写操作时主设备发数据、从设备应答;读操作时从设备发数据、主设备应答。
数据传送时,先传送最高位(MSB), 后传送最低位(LSB)。
5. 5. 停止信号(Stop Condition):通信结束的标志

- 时序要求:在SCL 保持高电平时,SDA 从低电平 "快速拉高"(上升沿);
- 作用:告知所有从设备 "本次通信结束",总线恢复到默认的高电平状态,等待下一次启动信号。
用外部 I2C 从设备(如温湿度传感器 SHT30、EEPROM 芯片 AT24C02 等 )内部的功能寄存器地址。例如: SHT30 传感器内部有 "测量命令寄存器""配置寄存器",其地址由传感器厂商定义(如 0x2C 表示某种测量模式的寄存器地址)。 AT24C02(EEPROM)的reg_addr是存储单元的地址(0x00~0xFF),用于指定要读写的存储位置。
三、两种不同的方式

- 常见问题与排查方向
-
**问题 1:通信无响应(从设备不发 ACK)**排查:① 上拉电阻是否焊接(或阻值是否合适);② 主设备发送的从设备地址是否正确(注意 7 位地址 vs 8 位地址,部分芯片需左移 1 位);③ SDA/SCL 线路是否接反或接触不良。
-
**问题 2:数据错误(读取值异常)**排查:① SCL 时钟速率是否超过从设备支持的最大速率(如低速传感器不支持快速模式 400kbps);② 时序是否严格(尤其是 SDA 在 SCL 高电平时是否稳定);③ 电源是否稳定(电压波动可能导致信号干扰)。
-
问题 3:多从设备冲突排查:确保所有从设备的 I2C 地址唯一,若地址重复,需通过芯片引脚(如 A0/A1)重新配置地址,或更换不同地址的芯片型号。
以下是 I2C 相关核心寄存器的总结,按功能分类梳理其关键作用和位定义:

一、地址与波特率配置寄存器
寄存器名称 | 核心功能 | 关键位说明 |
---|---|---|
I2Cx_IADR | 从设备地址存储 | - ADR(bit7:1):7 位有效,用于保存本设备作为 I2C 从机时的地址。访问从设备时,需将目标设备地址写入此位(仅从模式下有效)。 |
I2Cx_IFDR | 波特率设置 | - IC(bit5:0):6 位分频系数控制位,通过配置不同值设定 I2C 通信波特率(需参考手册固定分频表)。例:时钟源 66MHz 时,IC=0X15(640 分频),波特率≈100KHz(66MHz/640≈103.125KHz)。 |
二、控制寄存器(I2Cx_I2CR):配置通信模式与行为
位编号 | 位名称 | 功能说明 |
---|---|---|
bit7 | IEN | I2C 使能位:1 = 使能 I2C 模块,0 = 关闭 I2C 模块。 |
bit6 | IIEN | 中断使能位:1 = 允许 I2C 中断(如传输完成、仲裁丢失等),0 = 禁止中断。 |
bit5 | MSTA | 主从模式选择:1 = 主设备模式,0 = 从设备模式。 |
bit4 | MTX | 传输方向控制:1 = 发送数据(写操作),0 = 接收数据(读操作)。 |
bit3 | TXAK | 应答信号控制:0 = 发送 ACK(确认接收),1 = 发送 NACK(不确认接收)。 |
bit2 | RSTA | 重复起始位:1 = 生成重复起始信号(用于连续通信时切换读写方向,无需先发送停止信号)。 |
三、状态寄存器(I2Cx_I2SR):反馈通信状态与结果
位编号 | 位名称 | 功能说明 |
---|---|---|
bit7 | ICF | 数据传输完成标志:1 = 当前字节传输完成,0 = 传输中(需等待此位为 1 再进行下一步操作)。 |
bit6 | IAAS | 地址匹配标志:1 = 检测到总线上的地址与 I2Cx_IADR 中存储的从地址匹配(从模式下有效)。 |
bit5 | IBB | 总线忙标志:1=I2C 总线正在被使用(有数据传输),0 = 总线空闲(可发起通信)。 |
bit4 | IAL | 仲裁丢失标志:1 = 多主设备竞争总线时本设备仲裁失败(自动切换为从接收模式,需软件清零)。 |
bit2 | SRW | 从机读写状态:从模式下有效,0 = 主机要向从机写数据,1 = 主机要从从机读数据。 |
bit1 | IIF | 中断挂起标志:1 = 存在未处理的 I2C 中断(如传输完成、地址匹配等),需软件清零。 |
bit0 | RXAK | 应答检测标志:0 = 接收到 ACK 信号,1 = 接收到 NACK 信号(判断数据是否被对方正确接收)。 |
四、数据寄存器(I2Cx_I2DR):收发数据的缓冲区
- 功能 :用于存储待发送或刚接收的数据,仅低 8 位有效。
- 发送时:将待传输的 8 位数据写入此寄存器(高位在前,LSB 根据协议要求设置);
- 接收时:从该寄存器读取接收到的 8 位数据。
总结
I2C 寄存器可分为配置类 (地址、波特率、模式控制)、状态类 (传输进度、总线状态、应答反馈)和数据类(数据缓冲区),通过配置这些寄存器可实现 I2C 主 / 从模式切换、数据收发控制及通信状态监控,是 I2C 硬件驱动开发的核心操作对象。
基于IMAX6ULL的I2C总线配置:
cs
#include "i2c.h"
#include "/home/linux/IMAX6ULL/I2C/imx6ull/fsl_iomuxc.h"
#include "stdio.h"
#include "gpt.h"
void i2c_init(I2C_Type *base)
{
IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA, 1);
IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL, 1);
IOMUXC_SetPinConfig(IOMUXC_UART4_RX_DATA_I2C1_SDA, 0x10B0);
IOMUXC_SetPinConfig(IOMUXC_UART4_TX_DATA_I2C1_SCL, 0x10B0);
//失能并复位I2C
base->I2CR &= ~I2CR_IEN;
//配置分频系数
base->IFDR = 0x15;
//使能I2C
base->I2CR |= I2CR_IEN;
}
static int i2c_wait_iif(I2C_Type *base)
{
//等待中断标志置位
while ((base->I2SR & I2SR_IIF) == 0){
//可以设计超时
}
//写0清除IIF中断标志
base->I2SR &= ~I2SR_IIF;
//判断总线上的应答类型 -1:NACK 0:ACK
if ((base->I2SR & I2SR_RXAK) != 0){
return -1;
}
return 0;
}
static void i2c_write(I2C_Type *base, unsigned char dev_addr, unsigned short reg_addr, int reg_len, unsigned char *data, int len)
{
int status;
//0.清除IIF IAL,设置发送模式
base->I2SR &= ~((I2SR_IIF | I2SR_IAL));
base->I2CR |= I2CR_MTX;
//1.产生start信号
base->I2CR |= I2CR_MSTA;
//2.发送从机地址及数据流向位
base->I2DR = ((dev_addr << 1) | 0);
status = i2c_wait_iif(base);
if (status != 0) goto stop;
//3.发送寄存器地址
int i = reg_len - 1;
for(; i >= 0; i--){
base->I2DR = ((reg_addr >> (8 * i)) & 0xFF);
status = i2c_wait_iif(base);
if (status != 0) goto stop;
}
for(i = 0; i < len; i++){
base->I2DR = data[i];
status = i2c_wait_iif(base);
#if AT24C02 //适配 AT24C0
if (((reg_addr + i + 1) % 8) == 0 && (i < len - 1))
{
//产生stop信号
base->I2CR &= ~I2CR_MSTA;
delay_ms(5);
//1.产生start信号
base->I2CR |= I2CR_MSTA;
//2.发送从机地址及数据流向位
base->I2DR = ((dev_addr << 1) | 0);
status = i2c_wait_iif(base);
if (status != 0) goto stop;
//3.发送寄存器地址
int j = reg_len - 1;
for(; j >= 0; j--){
base->I2DR = (((reg_addr + i + 1) >> (8 * j)) & 0xFF);
status = i2c_wait_iif(base);
if (status != 0) goto stop;
}
}
#endif
if (status != 0) goto stop;
}
stop:
//产生stop信号
base->I2CR &= ~I2CR_MSTA;
//总线空闲状态检测
while((base->I2SR & I2SR_IBB) != 0){
//可以设计超时
}
#if AT24C02 //适配 AT24C0
delay_ms(5);
#endif
}
static void i2c_read(I2C_Type *base, unsigned char dev_addr, unsigned short reg_addr, int reg_len, unsigned char *data, int len)
{
int status;
//0.清除IIF IAL,设置发送模式
base->I2SR &= ~((I2SR_IIF | I2SR_IAL));
base->I2CR |= I2CR_MTX;
//1.产生start信号
base->I2CR |= I2CR_MSTA;
//2.发送从机地址及数据流向位
base->I2DR = ((dev_addr << 1) | 0);
status = i2c_wait_iif(base);
if (status != 0) goto stop;
//3.发送寄存器地址
int i = reg_len - 1;
for(; i >= 0; i--){
base->I2DR = ((reg_addr >> (8 * i)) & 0xFF);
status = i2c_wait_iif(base);
if (status != 0) goto stop;
}
//4.重发start信号
base->I2CR |= I2CR_RSTA;
//5.发送从机地址及数据流向位
base->I2DR = ((dev_addr << 1) | 1);
status = i2c_wait_iif(base);
if (status != 0) goto stop;
//6.设置接收模式
base->I2CR &= ~I2CR_MTX;
//7.设置应答类型
if (len > 1){
base->I2CR &= ~I2CR_TXAK; //ACK
}else{
base->I2CR |= I2CR_TXAK; //NACK
}
//8.伪读
data[0] = base->I2DR;
//9.数据读取
for( i = 0; i < len; i++){
status = i2c_wait_iif(base);
if (i == len - 2){//倒数第二个字节读取前要切换应答类型
base->I2CR |= I2CR_TXAK; //NACK
}
if (i == len - 1)
base->I2CR |= I2CR_MTX; //设置发送模式
data[i] = base->I2DR;
if (status != 0) goto stop;
}
stop:
//产生stop信号
base->I2CR &= ~I2CR_MSTA;
//总线空闲状态检测
while((base->I2SR & I2SR_IBB) != 0){
//可以设计超时
}
}
void transfer(I2C_Type *base, struct I2C_t *pdata)
{
if (NULL == base || NULL == pdata){
return;
}
if (pdata->dir == I2C_read){
i2c_read(base, pdata->dev_addr, pdata->reg_addr, pdata->reg_len, pdata->data, pdata->len);
}else{
i2c_write(base, pdata->dev_addr, pdata->reg_addr, pdata->reg_len, pdata->data, pdata->len);
}
}
这段 I2C 驱动代码的编写逻辑,完全围绕 **"遵循 I2C 协议时序" 和 "适配硬件特性"** 展开,核心是将抽象的 I2C 通信规则(如 Start/Stop 信号、应答机制、读写时序)转化为对 IMX6ULL 单片机 I2C 寄存器的具体操作,同时针对特殊从设备(AT24C02)做兼容性处理,最终提供简洁的上层调用接口。整体逻辑可拆解为 "初始化→辅助工具→核心读写→统一封装" 四个层次,每个层次都有明确的设计目标:
一、底层初始化:为 I2C 通信 "搭好硬件基础"
i2c_init
函数的逻辑目标是让 IMX6ULL 的 I2C 模块从 "未就绪" 变为 "可通信" 状态,需完成 "引脚配置" 和 "模块参数配置" 两步,严格匹配硬件手册要求:
-
引脚功能复用与电气配置
- IMX6ULL 的引脚是 "多功能复用" 设计(如 UART4 的 RX/TX 引脚可复用为 I2C1 的 SDA/SCL),因此先通过
IOMUXC_SetPinMux
将指定引脚 "切换" 为 I2C 功能(第二个参数1
表示启用复用功能); - 再通过
IOMUXC_SetPinConfig
配置引脚的电气特性(0x10B0
是 IMX6ULL I2C 引脚的标准配置值,包含上拉电阻使能、驱动能力设置等,确保 SDA/SCL 线符合 I2C"开漏输出 + 上拉" 的硬件要求)。
- IMX6ULL 的引脚是 "多功能复用" 设计(如 UART4 的 RX/TX 引脚可复用为 I2C1 的 SDA/SCL),因此先通过
-
I2C 模块复位与参数配置
- 初始化前先 "失能 I2C"(
base->I2CR &= ~I2CR_IEN
),避免配置过程中模块误动作; - 配置波特率分频系数(
base->IFDR = 0x15
):根据 I2C 协议要求(如 100Kbps 标准速率),结合 IMX6ULL 的 I2C 时钟源(66MHz),查手册确定分频值0x15
对应 640 分频,最终波特率≈100Kbps; - 最后 "使能 I2C"(
base->I2CR |= I2CR_IEN
),模块进入就绪状态。
- 初始化前先 "失能 I2C"(
二、辅助工具函数:解决 "通信同步与应答判断" 共性问题
i2c_wait_iif
函数的逻辑目标是封装 "等待传输完成" 和 "检查应答" 这两个 I2C 通信的共性操作,避免在读写函数中重复代码,同时确保通信可靠性:
-
等待传输完成(中断标志同步)
- I2C 模块每完成 1 字节传输(地址 / 数据),会置位
I2SR_IIF
(中断标志位),因此通过while
循环等待该位为1
,确保 "上一步传输完成后再进行下一步",避免数据错位; - (注:代码预留了 "超时设计" 入口,实际项目中需补充超时判断,防止硬件异常导致无限循环)。
- I2C 模块每完成 1 字节传输(地址 / 数据),会置位
-
清除中断标志与应答判断
I2SR_IIF
是 "写 0 清除" 的标志位,必须手动清除(base->I2SR &= ~I2SR_IIF
),否则后续传输无法触发新的标志;- 读取
I2SR_RXAK
(应答标志位)判断接收方状态:若为1
表示接收方返回 NACK(通信失败,如从设备未响应),返回-1
;若为0
表示 ACK(成功),返回0
,给上层函数提供 "是否继续通信" 的判断依据。
三、核心读写函数:严格遵循 I2C 协议时序,适配特殊设备
i2c_write
和i2c_read
是代码的核心,逻辑目标是将 I2C 协议的 "写时序" 和 "读时序" 转化为寄存器操作,同时针对 AT24C02 的硬件限制做兼容性处理:
1. i2c_write
:实现 I2C 写时序(主→从)
遵循 I2C 写操作的标准流程:Start→从设备地址(写)→ACK→寄存器地址→ACK→数据→ACK→Stop
,同时适配 AT24C02 的 "页写入限制":
- 初始化阶段 :清除中断标志(
I2SR_IIF
)和仲裁丢失标志(I2SR_IAL
,避免上一次通信异常影响),设置为 "发送模式"(base->I2CR |= I2CR_MTX
); - Start 信号与从地址发送 :置位
I2CR_MSTA
(主模式)生成 Start 信号,发送 "从设备地址 <<1 | 0"(0
表示写操作),调用i2c_wait_iif
等待完成并检查 ACK; - 寄存器地址发送 :支持多字节寄存器地址(如 16 位地址),通过
for
循环从 "高位到低位" 分字节发送(如 16 位地址0x1234
,先发送0x12
,再发送0x34
),每字节后检查 ACK; - 数据发送与 AT24C02 适配 :循环发送数据,每字节后检查 ACK;
- AT24C02 是 "页写入" EEPROM(每 8 字节为 1 页,超过 1 页需重新发起 Start),因此通过
((reg_addr + i + 1) % 8) == 0
判断是否到达页边界:若到达,先发送 Stop 信号,延迟 5ms(EEPROM 写操作需要时间),再重新发起 Start、发送从地址和新的寄存器地址,继续写入下一页;
- AT24C02 是 "页写入" EEPROM(每 8 字节为 1 页,超过 1 页需重新发起 Start),因此通过
- 结束阶段 :发送 Stop 信号(
base->I2CR &= ~I2CR_MSTA
),等待总线空闲(I2SR_IBB == 0
),AT24C02 需额外延迟 5ms 确保写操作完成。
2. i2c_read
:实现 I2C 读时序(从→主)
I2C 读操作需先 "写寄存器地址"(告知从设备 "读哪个寄存器"),再 "切换为读模式",流程为Start→从地址(写)→ACK→寄存器地址→ACK→ReStart→从地址(读)→ACK→数据→NACK→Stop
:
- 前序写操作 :与
i2c_write
的前 3 步一致(Start→从地址写→寄存器地址),目的是 "定位要读取的寄存器"; - 切换读模式(ReStart + 读地址) :置位
I2CR_RSTA
生成 "重复 Start 信号"(避免总线释放,防止其他设备抢占),发送 "从设备地址 <<1 | 1"(1
表示读操作),检查 ACK; - 接收模式配置与应答控制 :
- 清除
I2CR_MTX
切换为 "接收模式"; - I2C 读操作中,主设备需控制应答:多字节读时,除最后 1 字节外均返回 ACK(告知从设备 "继续发"),最后 1 字节返回 NACK(告知从设备 "停止发"),因此先根据读取长度
len
设置初始应答(len>1
时 ACK,len=1
时 NACK);
- 清除
- 数据读取(含伪读) :
- IMX6ULL 的 I2C 接收需先 "伪读"(
data[0] = base->I2DR
):硬件要求接收前先触发一次读操作,否则后续数据无法正确获取; - 循环读取数据:每读取 1 字节前等待
IIF
标志,读取到 "倒数第二个字节" 时,切换为 NACK(确保最后 1 字节后从设备停止);读取到最后 1 字节时,切换回发送模式(避免硬件异常);
- IMX6ULL 的 I2C 接收需先 "伪读"(
- 结束阶段 :与
i2c_write
一致,发送 Stop 信号并等待总线空闲。
四、上层封装函数:提供 "简洁统一" 的调用接口
transfer
函数的逻辑目标是对上层应用隐藏 "读 / 写" 的细节差异,提供一个 "参数化" 的统一接口,降低上层使用难度:
- 参数合法性检查 :先判断
base
(I2C 模块基地址)和pdata
(通信参数结构体)是否为NULL
,避免空指针访问; - 根据方向调用对应函数 :通过
pdata->dir
(I2C_read
/I2C_write
)判断通信方向,自动调用i2c_read
或i2c_write
; - 参数封装 :
struct I2C_t
结构体(未显式定义,但可推断)包含dev_addr
(从设备地址)、reg_addr
(寄存器地址)、reg_len
(寄存器地址长度)、data
(数据缓冲区)、len
(数据长度)、dir
(通信方向),将所有通信参数打包,上层只需填充结构体即可调用,无需关注底层寄存器操作。
总结:整体编写逻辑的核心原则
- 协议优先:所有操作严格遵循 I2C 协议时序(Start/ReStart/Stop、地址 + 读写位、应答机制),确保与任何符合 I2C 标准的从设备兼容;
- 硬件匹配:针对 IMX6ULL 的硬件特性(引脚复用、寄存器位定义、时钟分频)编写代码,同时适配特殊从设备(AT24C02 的页写入限制);
- 模块化与复用:将共性操作(等待应答)封装为辅助函数,读写函数复用相同的初始化 / 结束逻辑,上层接口统一,降低维护成本;
- 可靠性保障:包含应答检查、总线空闲等待、超时预留,避免通信异常导致的程序卡死或数据错误。
简言之,这段代码是 "协议规则→硬件操作→上层接口" 的完整映射,是嵌入式驱动开发中 "硬件抽象" 思想的典型体现。
四、I2C 与 UART 的核心区别(选择参考)
在嵌入式开发中,I2C 和 UART 是最常用的两种串行通信协议,需根据场景选择,核心区别如下:
对比维度 | I2C | UART |
---|---|---|
线路数量 | 2 根(SDA+SCL,需上拉) | 2 根(TX+RX,无需上拉) |
主从架构 | 必须(1 主多从) | 无(对等通信,需交叉连接 TX/RX) |
设备区分方式 | 从设备地址 | 硬件线路(1 对 TX/RX 对应 1 个设备) |
速率 | 最高 3.4Mbps(高速模式) | 最高通常 1Mbps(受电平干扰限制) |
传输距离 | 短(<1 米) | 较短(<10 米,TTL 电平) |
典型应用 | 板载芯片间通信(传感器、EEPROM) | 设备间近距离通信(模块、电脑) |
总结
I2C 是一种 "高效、简洁" 的芯片间通信总线,凭借 "两根线 + 多从设备" 的优势,成为嵌入式系统中传感器、存储芯片等板载设备的首选通信方案。理解其主从架构、时序规则和上拉电阻的作用,是掌握 I2C 通信的核心,也是排查实际开发中通信问题的关键基础。