基于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 总线中主设备与多个从设备的数据传输(仅地址匹配的从设备拉低总线响应,其他设备截止)

注意:

  1. I2C 总线的 SDA/SCL 线:必须接 4.7kΩ~10kΩ 上拉电阻,确保总线默认处于高电平,只有设备需要通信时才拉低 SDA/SCL 生成启动信号;
  2. 上拉电阻过小时,驱动不足
  3. 上拉电阻过大时,会发生开漏

2. 主从架构(1 主多从,从设备靠地址区分)

I2C 采用严格的 "主从通信架构",规则如下:

  • 主设备(Master) :唯一的 "控制方",负责:
    1. 发起通信(拉低 SDA 启动信号);
    2. 生成 SCL 时钟信号;
    3. 发送从设备地址(选择要通信的从设备);
    4. 决定数据传输方向(主→从,或从→主);
    5. 终止通信(释放 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 线的后续数据。(总线仲裁)
    1. 从设备地址 + 读写位(7 位地址 + 1 位 R/W):选择目标设备与传输方向
  • 共 8 位数据,传输顺序为 "高位在前(MSB first)":
    • 前 7 位:从设备的唯一地址(如温湿度传感器 SHT30 的默认地址为 0x44);
    • 第 8 位:读写控制位(R/W bit):
      • 0 = 写操作(主设备→从设备,如主设备给从设备发配置指令);
      • 1 = 读操作(从设备→主设备,如主设备读取从设备的测量数据)。
  • 从设备响应:所有从设备会将接收到的 7 位地址与自身地址对比,仅地址匹配的从设备会在第 8 位传输完成后,主动将 SDA 拉低(生成 ACK 信号),告知主设备 "地址已匹配,可继续传输数据"。
    1. ACK 应答信号(Acknowledge):确保数据被接收

I2C 的每 8 位数据(地址或数据)传输完成后,都需要 "应答" 来确认数据是否被正确接收,分为两种情况:

  • ACK(确认应答):接收方(地址匹配的从设备,或读操作时的主设备)在第 8 位数据传输完成后,将 SDA 拉低,并在 SCL 高电平时保持低电平;
  • NACK(非确认应答):接收方不拉低 SDA(SDA 保持高电平),通常表示 "数据接收完成" 或 "接收错误"(如读操作时主设备已获取所有需要的数据,会发送 NACK 告知从设备 "停止发送")。
  • 注意 :应答位的 SCL 时钟仍由 主设备 生成,接收方仅需控制 SDA 电平。
    1. 数据传输(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. 常见问题与排查方向
  • **问题 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 模块从 "未就绪" 变为 "可通信" 状态,需完成 "引脚配置" 和 "模块参数配置" 两步,严格匹配硬件手册要求:

  1. 引脚功能复用与电气配置

    • IMX6ULL 的引脚是 "多功能复用" 设计(如 UART4 的 RX/TX 引脚可复用为 I2C1 的 SDA/SCL),因此先通过IOMUXC_SetPinMux将指定引脚 "切换" 为 I2C 功能(第二个参数1表示启用复用功能);
    • 再通过IOMUXC_SetPinConfig配置引脚的电气特性(0x10B0是 IMX6ULL I2C 引脚的标准配置值,包含上拉电阻使能、驱动能力设置等,确保 SDA/SCL 线符合 I2C"开漏输出 + 上拉" 的硬件要求)。
  2. I2C 模块复位与参数配置

    • 初始化前先 "失能 I2C"(base->I2CR &= ~I2CR_IEN),避免配置过程中模块误动作;
    • 配置波特率分频系数(base->IFDR = 0x15):根据 I2C 协议要求(如 100Kbps 标准速率),结合 IMX6ULL 的 I2C 时钟源(66MHz),查手册确定分频值0x15对应 640 分频,最终波特率≈100Kbps;
    • 最后 "使能 I2C"(base->I2CR |= I2CR_IEN),模块进入就绪状态。

二、辅助工具函数:解决 "通信同步与应答判断" 共性问题

i2c_wait_iif函数的逻辑目标是封装 "等待传输完成" 和 "检查应答" 这两个 I2C 通信的共性操作,避免在读写函数中重复代码,同时确保通信可靠性:

  1. 等待传输完成(中断标志同步)

    • I2C 模块每完成 1 字节传输(地址 / 数据),会置位I2SR_IIF(中断标志位),因此通过while循环等待该位为1,确保 "上一步传输完成后再进行下一步",避免数据错位;
    • (注:代码预留了 "超时设计" 入口,实际项目中需补充超时判断,防止硬件异常导致无限循环)。
  2. 清除中断标志与应答判断

    • I2SR_IIF是 "写 0 清除" 的标志位,必须手动清除(base->I2SR &= ~I2SR_IIF),否则后续传输无法触发新的标志;
    • 读取I2SR_RXAK(应答标志位)判断接收方状态:若为1表示接收方返回 NACK(通信失败,如从设备未响应),返回-1;若为0表示 ACK(成功),返回0,给上层函数提供 "是否继续通信" 的判断依据。

三、核心读写函数:严格遵循 I2C 协议时序,适配特殊设备

i2c_writei2c_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、发送从地址和新的寄存器地址,继续写入下一页;
  • 结束阶段 :发送 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 字节时,切换回发送模式(避免硬件异常);
  • 结束阶段 :与i2c_write一致,发送 Stop 信号并等待总线空闲。

四、上层封装函数:提供 "简洁统一" 的调用接口

transfer函数的逻辑目标是对上层应用隐藏 "读 / 写" 的细节差异,提供一个 "参数化" 的统一接口,降低上层使用难度:

  1. 参数合法性检查 :先判断base(I2C 模块基地址)和pdata(通信参数结构体)是否为NULL,避免空指针访问;
  2. 根据方向调用对应函数 :通过pdata->dirI2C_read/I2C_write)判断通信方向,自动调用i2c_readi2c_write
  3. 参数封装struct I2C_t结构体(未显式定义,但可推断)包含dev_addr(从设备地址)、reg_addr(寄存器地址)、reg_len(寄存器地址长度)、data(数据缓冲区)、len(数据长度)、dir(通信方向),将所有通信参数打包,上层只需填充结构体即可调用,无需关注底层寄存器操作。

总结:整体编写逻辑的核心原则

  1. 协议优先:所有操作严格遵循 I2C 协议时序(Start/ReStart/Stop、地址 + 读写位、应答机制),确保与任何符合 I2C 标准的从设备兼容;
  2. 硬件匹配:针对 IMX6ULL 的硬件特性(引脚复用、寄存器位定义、时钟分频)编写代码,同时适配特殊从设备(AT24C02 的页写入限制);
  3. 模块化与复用:将共性操作(等待应答)封装为辅助函数,读写函数复用相同的初始化 / 结束逻辑,上层接口统一,降低维护成本;
  4. 可靠性保障:包含应答检查、总线空闲等待、超时预留,避免通信异常导致的程序卡死或数据错误。

简言之,这段代码是 "协议规则→硬件操作→上层接口" 的完整映射,是嵌入式驱动开发中 "硬件抽象" 思想的典型体现。

四、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 通信的核心,也是排查实际开发中通信问题的关键基础。

相关推荐
火星MARK几秒前
RAID详解
数据库·oracle
JAVA学习通2 分钟前
Spring AI与DeepSeek实战:打造企业级智能体
数据库
安审若无1 小时前
Oracle 打补丁指南
数据库·oracle
樱花的浪漫1 小时前
Cuda reduce算子实现与优化
数据库·人工智能·深度学习·神经网络·机器学习·自然语言处理
啊森要自信1 小时前
【MySQL 数据库】MySQL用户管理
android·c语言·开发语言·数据库·mysql
kkkkk0211061 小时前
Redis八股
数据库·redis·缓存
Liu1bo2 小时前
【MySQL】表的约束
linux·数据库·mysql
胖胖的战士2 小时前
Mysql 数据库迁移
数据库·mysql
czhc11400756633 小时前
LINUX1012 mysql GLIBC安装
数据库·mysql
DemonAvenger3 小时前
深入 Redis Hash:从原理到实战,10 年经验的后端工程师带你玩转哈希结构
数据库·redis·性能优化