基于IMX6ULL芯片--I2C总线简单应用
I2C(Inter-Integrated Circuit,集成电路间总线)
是由飞利浦(现恩智浦 / NXP)开发的一种同步串行通信总线(有时钟线,数据线只有一根),核心特点是 “两根线实现多设备互联”,广泛应用于芯片间近距离数据传输场景,如嵌入式系统中单片机与传感器(温湿度、加速度)、存储芯片(EEPROM)、显示驱动芯片等的通信。
相较于 UART(需 TX/RX 交叉连接,多设备通信需多组线),I2C 的核心优势是 **“总线共享”**—— 仅通过 SDA(Serial Data,串行数据线)和 SCL(Serial Clock,串行时钟线)两根线,即可实现 “1 个主设备(如单片机)控制多个从设备(如多个传感器)”,极大简化了硬件布线。
一、I2C 的核心组成:2 根线 + 主从架构
1. 硬件线路(仅 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 线的后续数据。(总线仲裁)
2. 2. 从设备地址 + 读写位(7 位地址 + 1 位 R/W):选择目标设备与传输方向
- 共 8 位数据,传输顺序为 “高位在前(MSB first)”:
- 前 7 位:从设备的唯一地址(如温湿度传感器 SHT30 的默认地址为 0x44);
- 第 8 位:读写控制位(R/W bit):
- 0 = 写操作(主设备→从设备,如主设备给从设备发配置指令);
- 1 = 读操作(从设备→主设备,如主设备读取从设备的测量数据)。
- 从设备响应:所有从设备会将接收到的 7 位地址与自身地址对比,仅地址匹配的从设备会在第 8 位传输完成后,主动将 SDA 拉低(生成 ACK 信号),告知主设备 “地址已匹配,可继续传输数据”。
3. 3. ACK 应答信号(Acknowledge):确保数据被接收
I2C 的每 8 位数据(地址或数据)传输完成后,都需要 “应答” 来确认数据是否被正确接收,分为两种情况:
- ACK(确认应答):接收方(地址匹配的从设备,或读操作时的主设备)在第 8 位数据传输完成后,将 SDA 拉低,并在 SCL 高电平时保持低电平;
- NACK(非确认应答):接收方不拉低 SDA(SDA 保持高电平),通常表示 “数据接收完成” 或 “接收错误”(如读操作时主设备已获取所有需要的数据,会发送 NACK 告知从设备 “停止发送”)。
- 注意:应答位的 SCL 时钟仍由 主设备 生成,接收方仅需控制 SDA 电平。
4. 4. 数据传输(Data):实际数据的收发
- 时序要求:仅在 SCL 为低电平时,SDA 允许改变电平(准备数据);在 SCL 为高电平时,SDA 必须保持稳定(接收方此时采样数据,确保数据准确);
- 传输单位:每次传输 8 位数据(1 字节),高位在前,每传输 1 字节后,都需要一次 ACK/NACK 应答;
- 方向灵活:根据 “读写位” 决定方向,写操作时主设备发数据、从设备应答;读操作时从设备发数据、主设备应答。
5. 5. 停止信号(Stop Condition):通信结束的标志
- 时序要求:在SCL 保持高电平时,SDA 从低电平 “快速拉高”(上升沿);
- 作用:告知所有从设备 “本次通信结束”,总线恢复到默认的高电平状态,等待下一次启动信号。
用外部 I2C 从设备(如温湿度传感器 SHT30、EEPROM 芯片 AT24C02 等)内部的功能寄存器地址。例如: SHT30 传感器内部有 “测量命令寄存器”“配置寄存器”,其地址由传感器厂商定义(如 0x2C 表示某种测量模式的寄存器地址)。 AT24C02(EEPROM)的reg_addr是存储单元的地址(0x00~0xFF),用于指定要读写的存储位置。
三、两种不同的方式
2. 常见问题与排查方向
问题 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总线配置:
#include "i2c.h"
#include "/home/linux/IMAX6ULL/I2C/imx6ull/fsl_iomuxc.h"
#include "stdio.h"
#include"gpt.h"
//I2CR
#define I2CR_IEN (1 << 7) //I2C使能位(0:使能且复位, 1:使能)
#define I2CR_MSTA (1 << 5) //主从模式选择位(0->1:主 产生start, 1->0:从 产生stop)
#define I2CR_MTX (1 << 4) //发送/接收模式选择位(0:接收 1:发送)
#define I2CR_TXAK (1 << 3) //应答类型 (0:应答ACK, 1: 应答NACK)
#define I2CR_RSTA (1 << 2) //重发start信号(0->1:Repeat start.)
//I2SR
#define I2SR_ICF (1 << 7) //数据传输位(0:正在传输 1:传输已完成)
#define I2SR_IBB (1 << 5) //总线忙碌状态(0:空闲 1:忙碌)
#define I2SR_IAL (1 << 4) //仲裁丢失标志(0:正常 1:仲裁丢失)
#define I2SR_IIF (1 << 1) //中断标志(0:未产生 1:产生 )写0清0
#define I2SR_RXAK (1 << 0) //接收到的应答类型(0:ACK 1:NACK)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);//失能并复位I2Cbase->I2CR &= ~I2CR_IEN;//配置分频系数base->IFDR = 0x15;//使能I2Cbase->I2CR |= I2CR_IEN;
}int i2c_wait_iif(I2C_Type *base)
{while ((base->I2SR & I2SR_IIF) == 0){//可以设计超时}base->I2SR &= ~I2SR_IIF;if ((base->I2SR & I2SR_RXAK) != 0){printf("NACK\n");return -1;}printf("ACK\n");return 0;
}void i2c_write(I2C_Type *base, unsigned char dev_addr, unsigned char reg_addr, 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.发送寄存器地址base->I2DR = reg_addr;status = i2c_wait_iif(base);if (status != 0) goto stop;int i = 0;for(; i < len; i++){base->I2DR = data[i];status = i2c_wait_iif(base);if (status != 0) goto stop;}
stop://产生stop信号base->I2CR &= ~I2CR_MSTA;//总线空闲状态检测 while((base->I2SR & I2SR_IBB) != 0){//可以设计超时}
}
四、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 通信的核心,也是排查实际开发中通信问题的关键基础。