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

相关推荐
cookqq2 小时前
MongoDB源码分析慢日志:从配置到实现的完整解析
数据库·mongodb·nosql·慢日志
8K超高清2 小时前
汇世界迎全运 广州国际社区运动嘉年华举行,BOSMA博冠现场展示并分享与科技全运的故事
运维·服务器·网络·数据库·人工智能·科技
come112342 小时前
深入Spring Boot生态中最核心部分 数据库交互spring-boot-starter-data-jpa和Hibernate (指南五)
数据库·spring boot·hibernate
不剪发的Tony老师3 小时前
dbswitch:一款免费开源、功能强大的异构数据库迁移同步工具
数据库·etl·dbswitch
Crazy________4 小时前
13MySQL主从复制原理与搭建指南
数据库·mysql
June`5 小时前
Redis核心应用:从单机到分布式架构解析
数据库·redis·缓存
学习编程的Kitty5 小时前
MySQL——数据库基础与库的操作
数据库·mysql
krielwus5 小时前
Oracle 11g R2 物理冷备操作文档
数据库·oracle
大气层煮月亮5 小时前
Oracle EBS ERP之报表开发—嵌入Web中的报表预览、报表打印
前端·数据库·oracle