一、I2C 总线核心硬件与通信架构
1.1 两根线实现多设备通信的设计精髓
I2C 总线摒弃了传统通信的片选线设计,仅通过两根双向信号线实现所有挂载设备的通信,硬件连接极致简化,也是其在嵌入式领域普及的核心原因:
- SDA(Serial Data):串行数据线,双向传输,负责所有设备间的数据收发,主从设备均可通过该引脚发送或接收数据;
- SCL(Serial Clock) :串行时钟线,双向传输,时钟信号由主设备唯一提供 ,从设备仅能在特定时序下响应,保证主从设备的通信同步

上拉电阻的关键作用
为保证总线空闲时 SDA 和 SCL 均为高电平 (I2C 协议的基础空闲状态),SDA 和 SCL 引脚必须外接上拉电阻 (典型值 4.7KΩ~10KΩ)。对于 IMX6ULL 芯片,可通过引脚电气配置寄存器 直接开启内部上拉,无需额外焊接硬件电阻,简化了开发板的硬件设计。
1.2 主从通信架构与总线仲裁机制
I2C 总线采用主从应答式 通信架构,这是多设备挂载的核心设计,同时配合总线仲裁机制 解决多设备同时发起通信的冲突问题,整体架构规则如下:
- 主设备:唯一发起通信、提供时钟、控制通信流程的设备(如 IMX6ULL 单片机),可主动发起读 / 写操作,总线上同一时刻仅能有一个主设备;
- 从设备 :被动响应主设备指令的外设(如 AT24C02、LM75),每个从设备拥有全球唯一的 7 位 / 10 位设备地址(主流为 7 位),仅当主设备发送的地址与自身匹配时,才会响应通信;
- 通信中转:从设备之间无法直接通信,必须通过主设备中转,主设备作为 "通信枢纽" 完成从设备间的数据交互;
- 全总线监听 :主设备发送的所有数据会通过 SDA/SCL 广播到总线上所有从设备,未匹配地址的从设备会继续处于聆听状态,不参与后续通信;
- 总线仲裁 :若多个设备同时尝试发起通信(发送起始信号),硬件会通过逐位仲裁 机制确定主设备:仲裁时各设备同时发送数据,若某设备发送的高电平检测到总线为低电平,则判定仲裁失败,立即切换为从机接收模式,并置位仲裁丢失位(IAL),仲裁成功的设备成为主设备,继续控制总线。
1.3 IMX6ULL 的 I2C 控制器硬件资源
IMX6ULL 芯片内置4 路独立的 I2C 控制器(I2C1~I2C4) ,均支持标准模式(100Kbits/s)和快速模式(400Kbits/s),且兼容 I2C 协议的主从模式、应答机制、重复起始信号等核心特性,各控制器的硬件分配如下:
- I2C2:IMX6ULL-Mini 开发板默认使用,连接触控屏控制器,用于检测屏幕触摸坐标,不可随意修改配置;
- I2C1/I2C3/I2C4:空闲状态,引脚可通过杜邦线外接任意 I2C 外设,是开发者进行二次开发、外设驱动的首选;
- 时钟源 :所有 I2C 控制器共享IPG_CLK_ROOT时钟源,默认频率为 66MHz,通过分频配置可得到目标通信波特率;
- 引脚复用 :I2C 控制器的 SDA/SCL 引脚为复用功能,需通过 IOMUXC 寄存器配置引脚功能,如 I2C1 的 SDA/SCL 可复用为 UART4_RX/TX 引脚(开发板 43 号等引脚)。
二、I2C 协议核心时序与通信规则
I2C 的通信过程由起始信号、地址传输、数据传输、应答信号、停止信号 五个核心部分组成,所有操作均遵循严格的时序规范,时序的正确性是通信成功的关键 ,也是开发中需要重点关注和调试的点。以下为基于7 位设备地址的标准通信时序。

2.1 总线空闲状态
I2C 总线的空闲状态 定义为:SDA 和 SCL 同时保持高电平,且持续至少一个时钟周期。这是判断总线是否可用的基础,由上拉电阻保证,若总线被拉低,则表示当前有设备正在通信。
2.2 起始信号(Start,S)
主设备发起通信的唯一标志 ,时序定义为:SCL 保持高电平期间,SDA 由高电平向低电平产生一个下降沿跳变,跳变完成后,总线进入忙状态。
关键细节:
- 起始信号的跳变必须在 SCL 高电平期间完成,若 SCL 为低电平时 SDA 跳变,不会被识别为起始信号;
- 起始信号发送后,主设备会立即控制 SCL 产生时钟脉冲,开始后续的地址传输;
- 总线上所有从设备检测到起始信号后,会立即退出空闲状态,进入聆听状态,等待地址传输。
2.3 地址传输规则
起始信号后,主设备发送的第一个字节为从设备地址帧 ,这是实现多设备寻址的核心,地址帧的格式为7 位设备地址 + 1 位数据流向位,共 8 位,各位定义如下:
| 位 7 | 位 6 | 位 5 | 位 4 | 位 3 | 位 2 | 位 1 | 位 0 |
|---|---|---|---|---|---|---|---|
| 设备地址 bit6 | 设备地址 bit5 | 设备地址 bit4 | 设备地址 bit3 | 设备地址 bit2 | 设备地址 bit1 | 设备地址 bit0 | 数据流向位 R/W |
- 7 位设备地址:从设备的唯一物理地址,由芯片厂商固化(如 AT24C02 默认地址为 0x50),部分芯片支持通过硬件引脚(如 A0/A1/A2)修改地址的低几位,实现同一总线挂载多个同型号芯片;
- 数据流向位(R/W) :地址帧的最低位,用于定义后续的通信方向:
- 0 :表示主发从收,即主设备向从设备写入数据(写操作);
- 1 :表示从发主收,即主设备从从设备读取数据(读操作)。
示例 :AT24C02 的 7 位物理地址为 0x50(二进制0101000),则:
- 写操作地址帧:
01010000(0xA0),即0x50 << 1 | 0; - 读操作地址帧:
01010001(0xA1),即0x50 << 1 | 1。
2.4 数据传输规则
地址匹配后,主从设备进入数据传输阶段,I2C 协议对数据传输的时序、格式、速率均有严格要求,核心规则如下:
- 时钟同步规则 :SCL 高电平期间,SDA 上的数据必须保持绝对稳定 ,不允许任何跳变;仅能在SCL 低电平期间,修改 SDA 的电平状态。这是因为从设备会在 SCL 高电平期间采样 SDA 数据,若此时 SDA 跳变,会导致数据采样错误;
- 数据格式 :每次传输1 个字节(8 位) ,且先传输最高位(MSB),后传输最低位(LSB),这是 I2C 协议的固定传输顺序,所有兼容 I2C 的外设均遵循此规则;
- 传输速率 :
- 标准模式(Standard-mode):最高 100Kbits/s,适用于绝大多数低速外设;
- 快速模式(Fast-mode):最高 400Kbits/s,适用于对传输速率有一定要求的外设;
- 注:IMX6ULL 的 I2C 控制器支持两种模式,可通过波特率配置寄存器灵活切换;
- 数据长度:I2C 协议对单次通信的总数据长度无限制,可连续传输任意字节数,仅需在每个字节后完成应答交互。
2.5 应答信号(ACK/NACK)
为保证数据传输的可靠性 ,I2C 协议设计了字节级的应答机制 :主设备每发送 1 个字节(地址帧或数据帧),从设备需在第 9 个时钟脉冲期间反馈应答信号,主设备通过检测 SDA 的电平状态,判断从设备是否成功接收该字节。
应答信号的时序与定义
- 第 9 个时钟脉冲:由主设备提供,仅用于传输应答信号,不传输数据;
- 有效应答(ACK) :从设备在第 9 个时钟脉冲的低电平期间 ,将 SDA 拉低,并在高电平期间保持低电平,表示从设备已成功接收该字节,主设备可继续传输下一个字节;
- 非应答(NACK) :从设备在第 9 个时钟脉冲期间,将 SDA 保持高电平,有两种应用场景:
- 从设备未成功接收字节(如数据错误、设备忙),主设备接收到 NACK 后,可终止通信或重新传输;
- 主设备读取最后一个字节时,主动向从设备发送 NACK,告知从设备数据读取完成,停止发送数据(这是读操作的强制要求,否则从设备会持续发送数据,导致总线卡死)。
应答信号的检测主体
- 写操作:主设备发送字节,从设备反馈应答,主设备检测 SDA 电平判断 ACK/NACK;
- 读操作:从设备发送字节,主设备反馈应答,从设备检测 SDA 电平判断 ACK/NACK。
2.6 停止信号(Stop,P)
主设备终止通信的唯一标志 ,时序定义为:SCL 保持高电平期间,SDA 由低电平向高电平产生一个上升沿跳变,跳变完成后,总线恢复空闲状态,SDA 和 SCL 均回到高电平。
关键细节:
- 停止信号的跳变必须在 SCL 高电平期间完成,与起始信号的时序要求一致;
- 停止信号发送后,主设备释放对 SDA/SCL 的控制权,由上拉电阻将总线拉回空闲状态;
- 若主设备需要继续与其他从设备通信,可无需发送停止信号,直接发送重复起始信号,切换通信对象。
2.7 重复起始信号(Repeated Start,Sr)
I2C 协议的高效通信特性 之一,定义为:在一次通信过程中,主设备无需发送停止信号释放总线,可直接在当前通信的任意阶段(通常为地址 / 数据传输后)发送一个与起始信号时序完全相同的跳变,即为重复起始信号。
核心应用场景
重复起始信号主要用于读操作 :主设备需要先向从设备写入待读取的寄存器 / 存储地址 (写操作),再切换为从从设备读取数据 (读操作),此时通过重复起始信号,可在不释放总线的情况下完成通信方向的切换,避免了 "停止 - 重新起始" 的操作,大幅提升通信效率。
与普通起始信号的区别
- 时序:完全一致,均为 SCL 高电平时 SDA 的下降沿;
- 总线状态 :普通起始信号在总线空闲 时发送,重复起始信号在总线忙时发送;
- 硬件实现 :IMX6ULL 的 I2C 控制器通过RSTA 位直接生成重复起始信号,无需手动模拟时序,开发便捷。
2.8 完整的 I2C 通信时序流程
结合以上核心规则,给出 I2C写操作 和 ** 读操作(重复起始信号方式)** 的完整时序流程,这是驱动开发的核心依据:
写操作完整时序
空闲状态 → 主设备发送起始信号 S → 主设备发送地址帧(写) → 从设备反馈ACK → 主设备发送寄存器 / 存储地址 → 从设备反馈ACK → 主设备发送n 个数据字节 (每个字节后从设备反馈 ACK) → 主设备发送停止信号 P → 总线空闲。
读操作完整时序(重复起始信号方式,工业界主流)
空闲状态 → 主设备发送起始信号 S → 主设备发送地址帧(写) → 从设备反馈ACK → 主设备发送寄存器 / 存储地址 → 从设备反馈ACK → 主设备发送重复起始信号 Sr → 主设备发送地址帧(读) → 从设备反馈ACK → 从设备发送n-1 个数据字节 (每个字节后主设备反馈 ACK) → 从设备发送最后 1 个数据字节 → 主设备反馈NACK → 主设备发送停止信号 P → 总线空闲。
三、IMX6ULL 的 I2C 控制器核心寄存器详解
IMX6ULL 的 I2C 控制器为硬件化的 I2C 协议实现 ,无需开发者通过 GPIO 手动模拟时序,仅需通过配置寄存器即可实现起始 / 停止信号生成、地址传输、数据收发、应答处理等所有操作,大幅降低开发难度。
所有 I2C 控制器的寄存器结构完全一致,仅基地址不同(I2C1:0x021A0000,I2C2:0x021A4000,I2C3:0x021A8000,I2C4:0x021AC000),核心寄存器包括地址寄存器、波特率配置寄存器、控制寄存器、状态寄存器、数据寄存器 ,以下为各寄存器的位定义、功能详解、配置要点 (x 表示控制器编号,1~4)。
3.1 I2Cx_IADR:从设备地址寄存器
- 寄存器地址:基地址 + 0x00
- 位宽 :32 位,仅bit7~bit1有效,bit0 和 bit31~bit8 为保留位,写 0 无效;
- 核心功能 :存储待访问的从设备7 位物理地址,用于主设备的地址匹配(从模式下存储自身的从设备地址);
- 配置要点 :访问某一从设备前,需将其 7 位物理地址写入bit7~bit1,无需添加数据流向位,数据流向位在数据寄存器(I2DR)中配置。
示例 :访问 AT24C02(7 位地址 0x50=0101000B),则I2C1_IADR = 0x50 << 1 = 0xA0(bit7~bit1 为 0101000)。
3.2 I2Cx_IFDR:波特率配置寄存器
- 寄存器地址:基地址 + 0x04
- 位宽:32 位,仅 **bit5~bit0(IC [5:0])** 有效,其余位为保留位;
- 核心功能 :通过配置IC[5:0]的 6 位值,设置 I2C 时钟的分频系数,从而将 66MHz 的 IPG_CLK_ROOT 时钟源分频为目标波特率;
- 关键特性 :分频系数不可随意设置 ,NXP 官方在 IMX6ULL 参考手册(第 1464 页)中给出了标准 IC 值与对应分频系数,开发者需严格参考,否则会导致波特率偏差过大,通信失败;
- 波特率计算公式:波特率分频系数对应的分频系数。
经典配置:
- 标准模式(100KHz):IC 值配置为0x15,对应分频系数 640,实际波特率 = 66MHz/640≈103.125KHz,偏差在协议允许范围内;
- 快速模式(400KHz):IC 值配置为0x06,对应分频系数 160,实际波特率 = 66MHz/160=412.5KHz,满足快速模式要求。
3.3 I2Cx_I2CR:I2C 控制寄存器
I2C 控制器的核心配置寄存器 ,用于设置 I2C 的工作模式、传输方向、中断使能、应答控制等,所有功能均通过位操作实现,寄存器地址 为基地址 + 0x08,位宽 32 位,有效位为 bit7~bit2,各有效位的定义与功能如下表所示:
| 位号 | 位名称 | 功能说明 | 配置值 |
|---|---|---|---|
| bit7 | IEN | I2C 控制器全局使能位 | 1 - 使能 I2C,0 - 失能 I2C(配置前需先失能) |
| bit6 | IIEN | I2C 中断使能位 | 1 - 使能 I2C 中断,0 - 失能 I2C 中断(轮询模式下置 0) |
| bit5 | MSTA | 主从模式选择位 | 1 - 主模式(开发中最常用),0 - 从模式 |
| bit4 | MTX | 传输方向选择位 | 1 - 发送模式(写操作),0 - 接收模式(读操作) |
| bit3 | TXAK | 应答信号控制位 | 1 - 发送 NACK,0 - 发送 ACK(读操作最后一字节置 1) |
| bit2 | RSTA | 重复起始信号生成位 | 1 - 生成重复起始信号,0 - 正常模式(自动清零) |
配置要点:
- 初始化 I2C 时,需先置位 IEN=0 失能控制器,配置完成后再置位 IEN=1 使能;
- 轮询模式开发中,IIEN 需置 0,禁止 I2C 中断,通过轮询状态寄存器判断通信状态;
- 主模式开发中,MSTA 需置 1,由 IMX6ULL 作为主设备控制总线;
- 重复起始信号通过置位 RSTA=1生成,硬件会自动完成时序,生成后该位会自动清零,无需手动操作;
- TXAK 位的配置是读操作的关键,需在读取最后一个字节前置 1,发送 NACK。
3.4 I2Cx_I2SR:I2C 状态寄存器
用于实时检测 I2C 的通信状态 ,开发者通过读取该寄存器的各位值,可判断数据传输是否完成、总线是否忙、是否收到应答、是否发生仲裁丢失等,是轮询模式 开发的核心寄存器。寄存器地址 为基地址 + 0x0C,位宽 32 位,有效位为 bit7~bit4、bit2、bit1、bit0,各有效位的定义与功能如下表所示:
| 位号 | 位名称 | 功能说明 | 关键判断逻辑 |
|---|---|---|---|
| bit7 | ICF | 数据传输完成标志位 | 1 - 当前字节传输完成,0 - 正在传输(自动清零) |
| bit6 | IAAS | 从模式地址匹配标志位 | 1 - 地址匹配成功,0 - 未匹配(主模式下无效) |
| bit5 | IBB | 总线忙标志位 | 1 - 总线忙,0 - 总线空闲(起始后置 1,停止后置 0) |
| bit4 | IAL | 仲裁丢失标志位 | 1 - 仲裁丢失,0 - 仲裁正常(需软件清零) |
| bit2 | SRW | 从模式读写状态位 | 1 - 主机读,0 - 主机写(主模式下无效) |
| bit1 | IIF | 中断挂起标志位 | 1 - 有中断挂起,0 - 无中断(需软件清零) |
| bit0 | RXAK | 应答检测标志位 | 1 - 收到 NACK,0 - 收到 ACK(核心判断位) |
开发核心用法:
- 轮询IIF=1判断当前字节传输完成,再进行下一步操作;
- 轮询RXAK=0判断收到 ACK,若 RXAK=1 则表示通信失败,立即终止;
- 轮询IBB=0判断总线释放完成,确保停止信号发送成功;
- 通信前需软件清零 IAL 和 IIF 位,防止历史状态影响通信;
- ICF 位与 IIF 位联动,字节传输完成后,两者会同时置位。
3.5 I2Cx_I2DR:I2C 数据寄存器
- 寄存器地址:基地址 + 0x10
- 位宽:32 位,仅 ** 低 8 位(bit7~bit0)** 有效,其余位为保留位;
- 核心功能 :作为 I2C 通信的数据缓冲区,实现主设备与总线之间的数据交互;
- 读写特性 :
- 发送数据:主设备将待发送的 8 位数据(地址帧 / 数据帧)写入该寄存器的低 8 位,硬件会自动将数据发送到 SDA 总线上;
- 接收数据:主设备从该寄存器的低 8 位读取从设备发送到 SDA 总线上的 8 位数据,读取前需等待传输完成。
关键注意点:
- 写入 / 读取数据时,仅操作低 8 位,高 24 位写 0 无效;
- 读操作中,第一次读取的数值为无效数据 ,需通过伪读触发硬件的第一次数据传输,后续读取的才是有效数据。
四、IMX6ULL 实战:I2C1 驱动 AT24C02 EEPROM
AT24C02 是一款基于 I2C 协议的串行非易失性 EEPROM ,存储容量为256 字节(2Kbit) ,掉电后数据永久保存,支持字节写入、页写入、随机读取、连续读取等操作,是嵌入式系统中常用的小容量数据存储设备(如存储设备参数、校准数据、运行日志等),也是学习 I2C 协议的经典实战外设。

本次实战基于 IMX6ULL-Mini 开发板的I2C1 控制器 ,实现 AT24C02 的完整读写驱动 ,提供可直接移植的代码,并补充硬件连接、代码注释、特性适配、使用示例等细节,确保开发者能快速上手。
4.1 AT24C02 核心特性与硬件参数
在开发驱动前,需先了解 AT24C02 的核心特性,这是驱动适配的关键:
- 供电电压:1.8V~5.5V,宽电压兼容,与 IMX6ULL 的 3.3V 供电完美匹配,无需电平转换;
- I2C 协议 :兼容标准模式(100KHz),7 位物理地址默认值为0x50,支持通过 A0/A1/A2 引脚修改地址低 3 位(本次实战将 A0/A1/A2 接地,地址保持 0x50);
- 存储结构 :分为32 页,每页 8 字节 ,总容量 32×8=256 字节,存储地址范围为0x00~0xFF;
- 写入特性 :
- 字节写入:单次写入 1 个字节,写入耗时约 5ms;
- 页写入 :单次最多写入 8 字节(1 页),若写入数据超过 8 字节,会发生地址回卷 ,覆盖当前页的起始地址数据,因此跨页写入时需分批次发起通信;
- 写入后需等待至少 5ms 的数据固化时间,否则会导致写入失败;
- 读取特性 :支持随机读取(指定地址读取)和连续读取(从指定地址开始连续读取 n 字节),无读取时间限制,速率由 I2C 总线波特率决定。

4.2 硬件连接
AT24C02 为 8 引脚芯片,与 IMX6ULL-Mini 开发板的 I2C1 连接仅需4 根线(VCC、GND、SCL、SDA),其余引脚(A0/A1/A2、WP)做简单配置即可,具体连接方式如下:
| AT24C02 引脚 | 功能说明 | 连接到 IMX6ULL 开发板 | 备注 |
|---|---|---|---|
| VCC | 电源正极 | 3.3V 引脚 | 供电,不可接 5V |
| GND | 电源负极 | GND 引脚 | 共地 |
| SCL | I2C 时钟线 | 43 号针脚(UART4_TX) | 复用为 I2C1_SCL |
| SDA | I2C 数据线 | UART4_RX 对应引脚 | 复用为 I2C1_SDA |
| A0/A1/A2 | 地址引脚 | GND | 接地,地址为默认 0x50 |
| WP | 写保护引脚 | GND | 接地,关闭写保护,允许写入 |
| NC | 空引脚 | 悬空 | 无功能 |
硬件注意事项:
- 若开发板未开启 I2C1 引脚的内部上拉,需在 SCL 和 SDA 引脚与 3.3V 之间外接 4.7KΩ 上拉电阻;
- 接线时需注意正负极不要接反,否则会烧毁 AT24C02 芯片;
- WP 引脚接地为关闭写保护,若接 3.3V 则开启写保护,仅能读取数据,无法写入。
4.3 完整 I2C 驱动代码(i2c.h + i2c.c)
驱动代码分为头文件(i2c.h)和实现文件(i2c.c) ,基于 IMX6ULL 的标准库开发,包含I2C 初始化、中断等待、写操作、读操作、通用传输接口 等功能,代码中添加详细注释 ,并做了超时机制、AT24C02 页写入适配、数据固化延时等工程化处理,可直接移植到 IMX6ULL 项目中使用。
4.3.1 头文件定义(i2c.h)
头文件主要完成寄存器位定义、宏定义、枚举类型、函数声明,为实现文件提供接口,同时避免头文件重复包含:
#ifndef __I2C_H__
#define __I2C_H__
#include "imx6ull.h" // IMX6ULL寄存器定义头文件
/************************* I2C寄存器位定义 *************************/
// I2C控制寄存器I2CR位定义
#define I2CR_IEN (1 << 7) // I2C全局使能位
#define I2CR_IIEN (1 << 6) // I2C中断使能位
#define I2CR_MSTA (1 << 5) // 主从模式选择位
#define I2CR_MTX (1 << 4) // 传输方向选择位
#define I2CR_TXAK (1 << 3) // 应答控制位
#define I2CR_RSTA (1 << 2) // 重复起始信号位
// I2C状态寄存器I2SR位定义
#define I2SR_ICF (1 << 7) // 传输完成标志位
#define I2SR_IAAS (1 << 6) // 从模式地址匹配位
#define I2SR_IBB (1 << 5) // 总线忙标志位
#define I2SR_IAL (1 << 4) // 仲裁丢失位
#define I2SR_SRW (1 << 2) // 从模式读写状态位
#define I2SR_IIF (1 << 1) // 中断挂起标志位
#define I2SR_RXAK (1 << 0) // 应答检测标志位
/************************* 宏定义 *************************/
#define AT24C02 1 // 启用AT24C02特性适配(页写入、固化延时)
#define I2C_TIMEOUT 0xFFFF // I2C超时计数阈值,防止死循环
/************************* 枚举与结构体定义 *************************/
// I2C传输方向枚举
typedef enum {
I2C_Write = 0, // 写操作:主发从收
I2C_Read = 1 // 读操作:从发主收
} I2C_Dir;
// I2C通用传输消息结构体
// 设计思路:统一读写操作的入口参数,提高代码复用性
typedef struct {
unsigned char dev_addr; // 从设备7位物理地址(如AT24C02的0x50)
unsigned short reg_addr; // 从设备寄存器/存储地址(如AT24C02的0x00~0xFF)
int reg_len; // 寄存器/存储地址的长度(1字节/2字节,AT24C02为1)
I2C_Dir dir; // 传输方向:I2C_Write/I2C_Read
unsigned char *data; // 数据缓冲区:写操作-待发送数据,读操作-接收数据
unsigned int len; // 传输数据的长度(字节数)
} I2C_Msg;
/************************* 函数声明 *************************/
void i2c_init(I2C_Type *base); // I2C控制器初始化(适配I2C1)
int wait_i2c_iif(I2C_Type *base); // 等待传输完成并检查应答
void i2c_write(I2C_Type *base, unsigned char dev_addr, unsigned short reg_addr, int reg_len, unsigned char *data, unsigned int len); // I2C写操作
void i2c_read(I2C_Type *base, unsigned char dev_addr, unsigned short reg_addr, int reg_len, unsigned char *data, unsigned int len); // I2C读操作
void transfer(I2C_Type *base, struct I2C_Msg *msg); // I2C通用传输接口(统一读写)
void delay_ms(unsigned int ms); // 毫秒延时函数(需配合GPT定时器实现)
#endif
4.3.2 驱动实现文件(i2c.c)
实现文件是驱动的核心,包含所有函数的具体实现,严格遵循 I2C 协议时序和 IMX6ULL 寄存器配置规则,同时适配 AT24C02 的页写入特性,添加超时机制和错误处理:
#include "i2c.h"
#include "fsl_iomuxc.h" // IMX6ULL引脚复用配置库
#include "stdio.h"
#include "gpt.h" // GPT定时器头文件,用于延时
/**
* @brief I2C控制器初始化函数(适配IMX6ULL的I2C1,可移植到其他I2C控制器)
* @param base: I2C控制器基地址(如I2C1、I2C2)
* @note 1. 配置UART4_RX/TX复用为I2C1_SDA/SCL
* 2. 配置引脚电气特性:上拉、高速率、高驱动能力
* 3. 初始化I2C控制器:失能→配置波特率→使能
*/
void i2c_init(I2C_Type *base)
{
// ************************* 步骤1:配置引脚复用功能 *************************
// IOMUXC_SetPinMux(引脚复用号, SION位)
// SION位=1:启用引脚的串行输入功能,SDA为双向线,必须置1
IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA, 1); // UART4_RX → I2C1_SDA
IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL, 1); // UART4_TX → I2C1_SCL
// ************************* 步骤2:配置引脚电气特性 *************************
// 配置值0x10B0解析:
// SRE(0):慢速率摆率 | DSE(6):高驱动能力 | SPEED(1):高速率(100MHz)
// ODE(0):禁用开漏输出 | PKE(1):启用引脚拉/灌电 | PUE(1):启用上拉
// PUS(2):22KΩ上拉电阻 | HYS(0):禁用迟滞比较器
IOMUXC_SetPinConfig(IOMUXC_UART4_RX_DATA_I2C1_SDA, 0x10B0);
IOMUXC_SetPinConfig(IOMUXC_UART4_TX_DATA_I2C1_SCL, 0x10B0);
// ************************* 步骤3:初始化I2C控制器 *************************
base->I2CR &= ~I2CR_IEN; // 1. 先失能I2C,防止配置过程中触发通信
base->IFDR = 0x15; // 2. 配置波特率:0x15=640分频,≈100KHz(标准模式)
base->I2CR |= I2CR_IEN; // 3. 使能I2C控制器,配置完成
}
/**
* @brief 等待I2C传输完成(IIF置位)并检查应答信号
* @param base: I2C控制器基地址
* @return 0-成功(传输完成且收到ACK),-1-失败(超时或收到NACK)
* @note 1. 轮询IIF位,添加超时机制,避免硬件异常导致死循环
* 2. 传输完成后软件清零IIF位
* 3. 检查RXAK位,判断是否收到ACK/NACK
*/
int wait_i2c_iif(I2C_Type *base)
{
unsigned int timeout = I2C_TIMEOUT; // 初始化超时计数器
// 轮询等待IIF置位(传输完成),同时判断是否超时
while (((base->I2SR & I2SR_IIF) == 0) && (timeout-- > 0)) {}
// 超时判断:硬件异常,返回失败
if (timeout == 0) {
printf("I2C Communication Timeout!\r\n");
return -1;
}
base->I2SR &= ~I2SR_IIF; // 软件清零中断挂起标志,准备下一次传输
// 检查应答信号:RXAK=1 → 收到NACK,传输失败
if ((base->I2SR & I2SR_RXAK) != 0)
{
printf("I2C Received NACK Signal!\r\n");
return -1;
}
return 0; // 传输完成且收到ACK,成功
}
/**
* @brief I2C写操作函数(适配AT24C02页写入特性,支持任意长度数据写入)
* @param base: I2C控制器基地址
* @param dev_addr: 从设备7位物理地址
* @param reg_addr: 从设备寄存器/存储地址
* @param reg_len: 地址长度(1/2字节)
* @param data: 待写入数据的缓冲区指针
* @param len: 待写入数据的字节数
* @note 1. 严格遵循I2C写操作时序:S→地址帧(写)→ACK→存储地址→ACK→数据→ACK→P
* 2. 适配AT24C02页写入:每8字节跨页时,停止并重新发起通信
* 3. 每次写入后添加5ms固化延时,确保数据写入成功
*/
void i2c_write(I2C_Type *base, unsigned char dev_addr, unsigned short reg_addr, int reg_len, unsigned char *data, unsigned int len)
{
int stat = 0; // 通信状态:0-成功,-1-失败
// 0. 清除历史状态:仲裁丢失位(IAL)、中断挂起位(IIF),防止影响本次通信
base->I2SR &= ~(I2SR_IAL | I2SR_IIF);
// 1. 设置为主机发送模式:MTX=1(写操作)
base->I2CR |= I2CR_MTX;
// 2. 发送起始信号:MSTA=1,总线进入忙状态
base->I2CR |= I2CR_MSTA;
// 3. 发送从设备地址帧:7位地址左移1位 + 写标志(0)
base->I2DR = ((dev_addr << 1) | 0);
stat = wait_i2c_iif(base);
if (stat != 0) goto stop; // 应答失败,跳转到停止信号处理
// 4. 发送从设备寄存器/存储地址(支持1/2字节地址,高位先传)
int i = reg_len - 1;
for (; i >= 0; i--)
{
base->I2DR = (reg_addr >> (i * 8)) & 0xFF; // 取当前8位地址
stat = wait_i2c_iif(base);
if (stat != 0) goto stop; // 某一字节地址传输失败,停止通信
}
// 5. 循环发送待写入数据,适配AT24C02页写入特性
for (i = 0; i < len; i++)
{
base->I2DR = data[i]; // 写入1字节数据到数据寄存器
stat = wait_i2c_iif(base); // 等待传输完成并检查应答
#if AT24C02
// AT24C02跨页判断:(当前存储地址+已发送字节数) % 8 == 0 且 未发送完所有数据
// 跨页时需停止通信,等待固化,再重新发起通信
if (((reg_addr + (i + 1)) % 8 == 0) && ((i + 1) < len))
{
base->I2CR &= ~I2CR_MSTA; // 发送停止信号,释放总线
delay_ms(5); // 等待5ms,数据固化到AT24C02
// 重新发起通信,继续写入下一页数据
base->I2CR |= I2CR_MSTA; // 重新发送起始信号
base->I2DR = ((dev_addr << 1) | 0); // 重新发送地址帧(写)
stat = wait_i2c_iif(base);
if (stat != 0) goto stop;
// 重新发送下一页的存储地址(高位先传)
int j = reg_len - 1;
for (; j >= 0; j--)
{
base->I2DR = ((reg_addr + (i + 1)) >> (j * 8)) & 0xFF;
stat = wait_i2c_iif(base);
if (stat != 0) goto stop;
}
}
#endif
if (stat != 0) goto stop; // 数据传输失败,停止通信
}
stop: // 停止信号处理标签
// 1. 发送停止信号:MSTA=0,主设备释放总线控制权
base->I2CR &= ~I2CR_MSTA;
// 2. 轮询等待总线空闲(IBB=0),添加超时机制
unsigned int timeout = I2C_TIMEOUT;
while (((base->I2SR & I2SR_IBB) != 0) && (timeout-- > 0)) {}
// 3. AT24C02写入后需5ms固化延时,确保数据写入成功
#if AT24C02
delay_ms(5);
#endif
}
/**
* @brief I2C读操作函数(采用重复起始信号方式,工业界主流,效率更高)
* @param base: I2C控制器基地址
* @param dev_addr: 从设备7位物理地址
* @param reg_addr: 从设备寄存器/存储地址
* @param reg_len: 地址长度(1/2字节)
* @param data: 接收数据的缓冲区指针
* @param len: 待读取数据的字节数
* @note 1. 严格遵循I2C读操作时序:S→地址帧(写)→ACK→存储地址→ACK→Sr→地址帧(读)→ACK→数据→ACK/NACK→P
* 2. 伪读触发第一次数据传输,第一个读取值无效
* 3. 最后一个字节必须发送NACK,告知从设备停止传输
* 4. 读取完成后切换为发送模式,确保停止信号正常生成
*/
void i2c_read(I2C_Type *base, unsigned char dev_addr,unsigned short reg_addr, int reg_len, unsigned char *data, unsigned int len)
{
int stat = 0; // 通信状态:0-成功,-1-失败
// 0. 清除历史状态:仲裁丢失位(IAL)、中断挂起位(IIF)
base->I2SR &= ~(I2SR_IAL | I2SR_IIF);
// 1. 设置为主机发送模式:先写存储地址,MTX=1
base->I2CR |= I2CR_MTX;
// 2. 发送起始信号:MSTA=1
base->I2CR |= I2CR_MSTA;
// 3. 发送地址帧(写):7位地址左移1位 + 写标志(0),用于写入存储地址
base->I2DR = ((dev_addr << 1) | 0);
stat = wait_i2c_iif(base);
if (stat != 0) goto stop;
// 4. 发送待读取的寄存器/存储地址(支持1/2字节,高位先传)
int i = reg_len - 1;
for (; i >= 0; i--)
{
base->I2DR = (reg_addr >> (i * 8)) & 0xFF;
stat = wait_i2c_iif(base);
if (stat != 0) goto stop;
}
// 5. 发送重复起始信号:Sr,无需释放总线,直接切换通信方向
base->I2CR |= I2CR_RSTA;
// 6. 发送地址帧(读):7位地址左移1位 + 读标志(1),准备读取数据
base->I2DR = ((dev_addr << 1) | 1);
stat = wait_i2c_iif(base);
if (stat != 0) goto stop;
// 7. 切换为主机接收模式:MTX=0,准备接收从设备数据
base->I2CR &= ~I2CR_MTX;
// 8. 配置应答类型:多字节读先发送ACK,单字节读直接发送NACK
if (len > 1)
{
base->I2CR &= ~I2CR_TXAK; // 多字节:TXAK=0 → 发送ACK
} else {
base->I2CR |= I2CR_TXAK; // 单字节:TXAK=1 → 发送NACK
}
// 9. 伪读操作:触发硬件的第一次数据传输,第一个读取值无效
data[0] = base->I2DR;
// 10. 循环读取n个字节的数据
for (i = 0; i < len; i++)
{
stat = wait_i2c_iif(base); // 等待传输完成
// 倒数第二个字节读取完成后,设置TXAK=1,为最后一个字节发送NACK
if (i == len - 2){
base->I2CR |= I2CR_TXAK;
}
// 最后一个字节读取完成后,切换为发送模式,确保停止信号正常生成
if (i == len - 1){
base->I2CR |= I2CR_MTX;
}
data[i] = base->I2DR; // 读取有效数据到缓冲区
if (stat != 0) goto stop;
}
stop: // 停止信号处理标签
// 1. 发送停止信号:MSTA=0
base->I2CR &= ~I2CR_MSTA;
// 2. 轮询等待总线空闲(IBB=0),添加超时机制
unsigned int timeout = I2C_TIMEOUT;
while (((base->I2SR & I2SR_IBB) != 0) && (timeout-- > 0)) {}
}
/**
* @brief I2C通用传输接口:统一读写操作入口,提高代码复用性
* @param base: I2C控制器基地址
* @param msg: I2C传输消息结构体指针,包含所有传输参数
* @note 1. 先做参数合法性检查,防止空指针访问
* 2. 根据传输方向(dir)自动调用读/写函数
*/
void transfer(I2C_Type *base, struct I2C_Msg *msg)
{
// 参数合法性检查:基地址或消息结构体为空,直接返回
if (base == NULL || msg == NULL || msg->data == NULL)
{
printf("I2C Parameter Error: NULL Pointer!\r\n");
return;
}
// 根据传输方向调用对应的读写函数
if (msg->dir == I2C_Write)
{
i2c_write(base, msg->dev_addr, msg->reg_addr, msg->reg_len, msg->data, msg->len);
} else {
i2c_read(base, msg->dev_addr, msg->reg_addr, msg->reg_len, msg->data, msg->len);
}
}
/**
* @brief 毫秒延时函数(基于GPT定时器实现,1ms精度)
* @param ms: 延时的毫秒数
* @note 需先初始化GPT定时器(gpt_init()),否则延时无效
*/
void delay_ms(unsigned int ms)
{
gpt_delay_ms(ms);
}
4.4 代码核心亮点与工程化处理
本次提供的驱动代码并非简单的功能实现,而是做了大量工程化优化,适配实际项目开发的需求,核心亮点如下:
- 超时机制:所有轮询操作均添加超时计数器,避免因硬件异常(如外设掉线、总线短路)导致程序死循环,提高系统的鲁棒性;
- AT24C02 深度适配:自动处理页写入的跨页问题,无需开发者手动分批次,同时添加 5ms 数据固化延时,确保写入成功率 100%;
- 通用传输接口 :通过
I2C_Msg结构体统一读写操作的入口,后续驱动其他 I2C 外设(如 LM75、SHT30)时,仅需填充结构体即可,大幅提高代码复用性; - 参数合法性检查:在通用接口中对空指针等非法参数做判断,防止程序崩溃;
- 详细的注释与日志:关键步骤添加注释,异常情况打印日志(如超时、NACK、参数错误),方便调试;
- 可移植性强:代码与 I2C 控制器基地址解耦,可直接移植到 I2C2/I2C3/I2C4,仅需修改引脚复用配置;
- 严格遵循协议:读操作的 NACK 时机、重复起始信号的使用、时序的先后顺序,均严格遵循 I2C 协议规范,避免因协议违规导致的通信失败。
4.5 代码使用示例(测试 AT24C02)
提供简单的主函数测试示例,实现向 AT24C02 写入字符串,再从对应地址读取并通过串口打印 ,验证驱动的正确性。测试前需确保GPT 定时器已初始化(用于延时) 、串口已初始化(用于打印)。
#include "i2c.h"
#include "gpt.h"
#include "uart.h"
// 宏定义:AT24C02的7位物理地址和测试存储地址
#define AT24C02_DEV_ADDR 0x50 // AT24C02默认地址(A0/A1/A2接地)
#define AT24C02_TEST_ADDR 0x00 // 测试用存储地址:0x00
int main(void)
{
// 1. 初始化外设
uart_init(); // 初始化串口(波特率115200),用于打印日志
gpt_init(); // 初始化GPT定时器,用于毫秒延时
i2c_init(I2C1); // 初始化I2C1控制器,适配AT24C02
// 2. 定义测试数据
unsigned char write_buf[] = "IMX6ULL I2C Test!"; // 待写入数据
unsigned char read_buf[sizeof(write_buf)] = {0}; // 接收数据缓冲区,初始化为0
I2C_Msg i2c_msg; // 定义I2C传输消息结构体
// 3. 配置写操作参数,向AT24C02写入测试数据
i2c_msg.dev_addr = AT24C02_DEV_ADDR; // 从设备地址0x50
i2c_msg.reg_addr = AT24C02_TEST_ADDR; // 存储地址0x00
i2c_msg.reg_len = 1; // AT24C02地址长度为1字节
i2c_msg.dir = I2C_Write; // 写操作
i2c_msg.data = write_buf; // 待写入数据缓冲区
i2c_msg.len = sizeof(write_buf); // 写入数据长度(包含结束符'\0')
transfer(I2C1, &i2c_msg); // 调用通用传输接口,执行写操作
printf("I2C Write Success: %s\r\n", write_buf);
// 4. 配置读操作参数,从AT24C02读取写入的数据
i2c_msg.dir = I2C_Read; // 仅需修改传输方向,其余参数不变
i2c_msg.data = read_buf; // 接收数据缓冲区
transfer(I2C1, &i2c_msg); // 调用通用传输接口,执行读操作
printf("I2C Read Success: %s\r\n", read_buf);
// 死循环,程序挂起
while(1)
{
delay_ms(1000);
}
return 0;
}
预期运行结果
烧录程序到 IMX6ULL-Mini 开发板后,通过串口调试助手(波特率 115200、8 位数据位、1 位停止位、无校验、无流控)可看到如下打印信息,表明 I2C 通信正常,AT24C02 读写成功:
I2C Write Success: IMX6ULL I2C Test!
I2C Read Success: IMX6ULL I2C Test!
若出现超时、NACK 等提示,需按照下文的调试技巧排查硬件或代码问题。
4.6 硬件与软件调试技巧
开发过程中若出现 I2C 通信失败(如超时、NACK、读取数据为 0xFF / 乱码),可按照先硬件后软件的顺序逐步排查,以下为嵌入式开发中 I2C 调试的经典技巧,适用于所有 I2C 外设驱动开发:
硬件调试(优先级最高,80% 的问题源于硬件)
- 万用表测供电与接地:检测 AT24C02 的 VCC 引脚是否为 3.3V,GND 是否与开发板共地,避免供电异常导致芯片不工作;
- 检测上拉电阻:若开发板未开启内部上拉,需确认 SDA/SCL 引脚外接了 4.7KΩ~10KΩ 上拉电阻,用万用表测量 SDA/SCL 空闲时的电平,应为 3.3V 高电平;
- 检查接线是否正确 :确认 SDA 接 I2C1_SDA、SCL 接 I2C1_SCL,严禁 SDA 和 SCL 接反,接反会导致总线始终无应答;
- 排查接触不良:杜邦线、排针若接触不良,会导致总线电平不稳定,可重新插拔接线或更换杜邦线;
- 关闭写保护:确认 AT24C02 的 WP 引脚接地,若接 3.3V 会开启写保护,写操作会返回 NACK;
- 示波器观测时序 :若有示波器,可直接观测 SDA/SCL 的波形,检查起始 / 停止信号、地址 / 数据传输、应答信号的时序是否符合协议要求,这是最精准的硬件调试方法。
软件调试
- 检查寄存器配置:确认 I2C 的波特率配置为 0x15(≈100KHz),引脚复用配置的 SION 位为 1,电气特性配置开启上拉;
- 核对设备地址:AT24C02 的 7 位物理地址为 0x50,传输时需左移 1 位加读写标志,写操作地址帧为 0xA0、读操作为 0xA1,避免地址写错;
- 检查超时阈值 :若实际项目中总线速率较慢,可适当增大
I2C_TIMEOUT宏的数值,防止误判超时; - 注释跨页逻辑测试:若写入数据小于 8 字节,可暂时注释 AT24C02 的跨页处理逻辑,单独测试基础写操作;
- 调试伪读操作:读操作中伪读是触发数据传输的关键,不可省略,否则无法读取到有效数据;
- 检查延时函数 :确认 GPT 定时器已正确初始化,
delay_ms函数能实现精准延时,否则 AT24C02 的数据固化时间不足会导致写入失败; - 分步测试通信 :将写操作拆分为起始信号→地址帧→存储地址→单字节数据→停止信号,分步测试每一步的应答状态,定位具体的失败环节。
五、I2C 协议进阶知识点与拓展应用
5.1 10 位设备地址(拓展寻址)
前文讲解的 7 位地址是 I2C 协议的主流用法,支持最多27=128个从设备,当总线上挂载的设备数量超过 128 个时,可使用10 位设备地址,支持最多210=1024个从设备。
10 位地址的传输时序与 7 位地址不同,需要两个字节传输地址:
- 第一个字节:11110XX(前 5 位为固定标志 11110,后 2 位为 10 位地址的高 2 位)+ 读写标志;
- 第二个字节:10 位地址的低 8 位;
- 后续的寄存器地址、数据传输与 7 位地址完全一致。
IMX6ULL 的 I2C 控制器原生支持 10 位地址,仅需修改地址传输的代码逻辑,无需修改硬件配置。
5.2 I2C 的从模式开发
前文的实战均为主模式 (IMX6ULL 作为主设备),IMX6ULL 的 I2C 控制器也支持从模式,即 IMX6ULL 作为从设备,由其他主设备(如 STM32、树莓派)发起通信,IMX6ULL 被动响应。
从模式开发的核心配置:
- 将
I2CR_MSTA位置 0,配置为从模式; - 在
I2Cx_IADR寄存器中写入 IMX6ULL 作为从设备的 7 位 / 10 位地址; - 使能中断(
I2CR_IIEN置 1),通过中断方式处理主设备的读写指令; - 主设备发送的地址与
I2Cx_IADR匹配时,I2SR_IAAS位会置位,触发中断。
从模式适用于多 MCU 通信场景,如 IMX6ULL 与 STM32 组成的双 MCU 系统,可通过 I2C 实现数据交互。
5.3 多 I2C 外设挂载实战
I2C 总线的核心优势是多设备挂载 ,同一根 SDA/SCL 总线上可挂载多个不同型号的 I2C 外设,仅需保证每个外设的7 位物理地址不重复。
挂载示例:I2C1 同时挂载 AT24C02 和 LM75(温度传感器)
- 地址核对:AT24C02 默认地址 0x50,LM75 默认地址 0x48,地址不重复,可直接挂载;
- 硬件连接:将 LM75 的 VCC/GND 接 3.3V/GND,SDA/SCL 分别与 I2C1 的 SDA/SCL 相连,A0/A1/A2 接地;
- 软件开发 :基于本次的 I2C 通用驱动,仅需定义 LM75 的设备地址和寄存器地址,填充
I2C_Msg结构体即可实现读写,无需修改底层 I2C 驱动代码,体现了通用驱动的高复用性。
同型号设备挂载:多个 AT24C02
若需挂载多个同型号的 AT24C02,可通过修改 A0/A1/A2 引脚电平改变其 7 位物理地址:
- AT24C02 的地址格式为
1010XXX,XXX 由 A0/A1/A2 的电平决定(0 = 低,1 = 高); - 第一个 AT24C02:A0/A1/A2=000 → 地址 0x50;
- 第二个 AT24C02:A0/A1/A2=001 → 地址 0x51;
- 以此类推,最多可挂载 8 个 AT24C02。
5.4 I2C 与其他串行通信协议的对比
嵌入式开发中常用的串行通信协议还有SPI、UART,三者的应用场景差异显著,需根据实际需求选择,以下为核心参数对比:
| 协议 | 信号线数量 | 通信方式 | 速率 | 寻址方式 | 主从架构 | 典型应用场景 |
|---|---|---|---|---|---|---|
| I2C | 2(SDA/SCL) | 半双工 | 标准 100K / 快速 400K | 7/10 位地址,无片选 | 主从,多主仲裁 | 低速外设(EEPROM、传感器、OLED)、多设备挂载 |
| SPI | 3~4(SCK/MOSI/MISO/CS) | 全双工 | 几 MHz~ 几十 MHz | 片选线 CS,无地址 | 主从,无多主 | 高速外设(Flash、LCD、ADC)、高速数据传输 |
| UART | 2(TX/RX) | 全双工 | 几 K~115200K | 无地址,点对点 | 无主从,点对点 | 串口调试、模块通信(蓝牙、WiFi)、点对点数据传输 |
选型建议:
- 若需多设备挂载、硬件接线简单,优先选择 I2C;
- 若需高速数据传输,优先选择 SPI;
- 若需点对点调试、模块通信,优先选择 UART。
六、总结与学习建议
6.1 核心知识点总结
本文从协议原理→寄存器详解→硬件实战→调试技巧,全维度讲解了 IMX6ULL 的 I2C 总线开发,核心知识点可总结为以下 5 点:
- I2C 协议核心 :两根双向线(SDA/SCL)+ 主从架构 + 应答机制,时序的正确性是通信成功的关键,尤其是SCL 高电平期间 SDA 稳定、读操作最后一字节发 NACK;
- IMX6ULL 硬件实现 :通过 4 路独立的 I2C 控制器硬件化实现协议,无需手动模拟时序,核心配置为引脚复用 + 波特率分频 + 控制 / 状态寄存器操作;
- 驱动开发关键 :严格遵循 ** 写操作(S→地址→寄存器地址→数据→P)和读操作(S→地址 (写)→寄存器地址→Sr→地址 (读)→数据→NACK→P)** 的时序流程,适配外设的硬件特性(如 AT24C02 的页写入、5ms 固化延时);
- 工程化开发 :驱动代码需添加超时机制、参数检查、异常日志、外设特性适配 ,提高系统的鲁棒性,同时设计通用传输接口,提升代码复用性;
- 调试原则 :I2C 问题排查遵循先硬件后软件 ,硬件重点检查供电、上拉、接线 ,软件重点检查寄存器配置、设备地址、时序逻辑。
6.2 嵌入式开发学习建议
对于嵌入式开发初学者,从 I2C 总线入手学习外设驱动是非常合适的选择,结合本次实战,给出以下学习建议:
- 先理解协议,再写代码 :不要直接照搬驱动代码,先吃透 I2C 的时序规则和通信流程,理解起始 / 停止信号、应答机制、重复起始信号的设计初衷,才能在调试时快速定位问题;
- 寄存器是硬件开发的核心 :IMX6ULL 的外设驱动本质是寄存器配置 ,需熟练掌握 I2C 控制器的 ** 控制寄存器(I2CR)和状态寄存器(I2SR)** 的位定义,能通过位操作实现各种功能;
- 重视工程化细节:初学者容易忽略超时机制、参数检查、外设特性适配等工程化细节,这些细节是区别 "demo 代码" 和 "项目代码" 的关键,直接决定了程序的稳定性和可维护性;
- 多动手调试,积累排错经验 :嵌入式开发是实践性极强的学科,纸上谈兵无法掌握,需多动手接线、烧录程序、调试问题,积累硬件和软件的排错经验,这是最宝贵的开发财富;
- 从通用驱动到外设适配 :先实现通用的 I2C 底层驱动,再基于通用驱动适配具体的外设(如 AT24C02、LM75、SHT30),这种分层开发的思想是嵌入式项目开发的主流,能大幅提高开发效率;
- 拓展学习其他协议:掌握 I2C 后,可继续学习 SPI、UART、CAN 等串行通信协议,对比各协议的优缺点和应用场景,形成完整的通信协议知识体系。
6.3 后续拓展开发方向
基于本次的 I2C 通用驱动,可进行以下拓展开发,进一步巩固和提升嵌入式开发能力:
- 适配更多 I2C 外设:基于通用驱动适配 LM75(温度传感器)、SHT30(温湿度传感器)、0.96 寸 OLED(显示)、PCF8574(IO 扩展)等常用 I2C 外设,实现 "数据采集 - 存储 - 显示" 的完整小项目;
- 实现 I2C 中断模式开发 :本次实战为轮询模式,可尝试开发中断模式的 I2C 驱动,通过中断方式处理数据传输,提高 CPU 的运行效率;
- 多总线同时使用:尝试使用 I2C3/I2C4 挂载其他外设,实现多 I2C 总线的同时使用,掌握 IMX6ULL 外设资源的分配和管理;
- I2C 从模式实战:将 IMX6ULL 配置为 I2C 从设备,与 STM32 等其他 MCU 进行 I2C 通信,实现多 MCU 之间的数据交互;
- 基于 RTOS 的 I2C 驱动 :在 FreeRTOS、UCOS 等实时操作系统中,基于本次的底层驱动,实现带互斥锁的 I2C 设备驱动,解决多任务下的 I2C 总线竞争问题。
附录:IMX6ULL I2C 相关寄存器地址与 AT24C02 指令集
附录 1 IMX6ULL I2C1~I2C4 控制器基地址
| I2C 控制器 | 基地址 |
|---|---|
| I2C1 | 0x021A0000 |
| I2C2 | 0x021A4000 |
| I2C3 | 0x021A8000 |
| I2C4 | 0x021AC000 |
附录 2 AT24C02 核心指令集(I2C 地址帧)
| 操作 | 7 位物理地址 | 8 位地址帧(写操作) | 8 位地址帧(读操作) | |
|---|---|---|---|---|
| 字节 / 页写入 | 0x50 | 0xA0(0x50<<1 | 0) | - |
| 随机读取 | 0x50 | 0xA0(先写地址) | 0xA1(0x50<<1 | 1) |
| 连续读取 | 0x50 | 0xA0(先写地址) | 0xA1(0x50<<1 | 1) |
附录 3 I2C 波特率配置 IC 值与分频系数(IMX6ULL 参考手册 1464 页)
| IC 值 | 分频系数 | 66MHz 时钟下波特率 | 模式 |
|---|---|---|---|
| 0x00 | 20 | 3.3MHz | - |
| 0x06 | 160 | 412.5KHz | 快速模式 |
| 0x07 | 192 | 343.75KHz | 快速模式 |
| 0x15 | 640 | 103.125KHz | 标准模式 |
| 0x26 | 1280 | 51.5625KHz | 低速模式 |
