学习笔记——I2C(Inter-Intergrated Circuit)总线详解

I2C(Inter-Intergrated Circuit)总线详解

一、I2C总线基本概念

1.1 I2C简介

I2C(Inter-Integrated Circuit)是由Philips公司开发的一种串行、同步、半双工的通信总线。主要特点:

  • 两根线:SDA(数据线)和SCL(时钟线)

  • 多主多从结构

  • 7位/10位地址寻址

  • 标准模式 (100kbps)、快速模式 (400kbps)、高速模式(3.4Mbps)

1.2 物理层特性

复制代码
SDA ─────┬─────────┐
         │         │
       上拉      上拉
         │         │
SCL ─────┴─────────┘
       主机        从机

二、I2C通信时序

2.1 基本时序单元

复制代码
// 开始信号:SCL高电平时,SDA从高变低
void i2c_start(void) {
    SDA_HIGH();
    SCL_HIGH();
    DELAY();
    SDA_LOW();
    DELAY();
    SCL_LOW();
}

// 停止信号:SCL高电平时,SDA从低变高
void i2c_stop(void) {
    SDA_LOW();
    SCL_HIGH();
    DELAY();
    SDA_HIGH();
    DELAY();
}

2.2 数据传输格式

复制代码
开始信号 + 7位地址 + 1位R/W + ACK + 数据 + ACK + ... + 停止信号
  • 地址位:7位从机地址(0x00-0x7F)

  • R/W位:0-写,1-读

  • ACK位:每个字节后接收方应答

三、i.MX6UL的I2C控制器

3.1 寄存器概览

复制代码
// I2C相关寄存器定义(从原代码提取)
#define I2CR_IEN    (1 << 7)  // I2C使能位
#define I2CR_MSTA   (1 << 5)  // 主从模式选择位
#define I2CR_MTX    (1 << 4)  // 收发模式位
#define I2CR_TXAK   (1 << 3)  // 应答类型位
#define I2CR_RSTA   (1 << 2)  // 重复起始信号

#define I2SR_ICF    (1 << 7)  // 数据传输完成标志
#define I2SR_IBB    (1 << 5)  // 总线忙标志
#define I2SR_IAL    (1 << 4)  // 仲裁丢失标志
#define I2SR_IIF    (1 << 1)  // 中断标志
#define I2SR_RXAK   (1 << 0)  // 接收应答标志

3.2 初始化流程(从原代码分析)

复制代码
void i2c_init(I2C_Type *base)
{
    // 1. 引脚复用配置
    IOMUXC_SetPinMux(IOMUXC_UART4_RX_DATA_I2C1_SDA, 1);  // SDA
    IOMUXC_SetPinMux(IOMUXC_UART4_TX_DATA_I2C1_SCL, 1);  // SCL
    
    // 2. 电气特性配置(上拉、驱动能力等)
    IOMUXC_SetPinConfig(IOMUXC_UART4_RX_DATA_I2C1_SDA, 0x10B0);
    IOMUXC_SetPinConfig(IOMUXC_UART4_TX_DATA_I2C1_SCL, 0x10B0);

    // 3. I2C控制器初始化
    base->I2CR &= ~I2CR_IEN;     // 先失能I2C
    base->IFDR = 0x15;           // 设置时钟分频(384KHz)
    base->I2CR |= I2CR_IEN;      // 使能I2C
}

四、I2C写操作实现分析

4.1 完整的写数据流程

复制代码
// 原代码中的i2c_write函数分析
void i2c_write(I2C_Type *base, unsigned char dev_addr, 
               unsigned char reg_addr, unsigned char *data, 
               unsigned int len)
{
    int stat = 0;
    
    // 0. 清除状态标志
    base->I2SR &= ~(I2SR_IAL | I2SR_IIF);
    
    // 1. 设置为发送模式
    base->I2CR |= I2CR_MTX;
    
    // 2. 产生START信号(设置为主机模式)
    base->I2CR |= I2CR_MSTA;
    
    // 3. 发送从机地址(写模式)
    base->I2DR = ((dev_addr << 1) | 0);  // 地址左移1位,最低位0表示写
    stat = wait_i2c_iif(base);           // 等待传输完成
    
    // 4. 发送寄存器地址
    base->I2DR = reg_addr;
    stat = wait_i2c_iif(base);
    
    // 5. 循环发送数据
    for (int i = 0; i < len; i++)
    {
        base->I2DR = data[i];
        stat = wait_i2c_iif(base);
    }
    
    // 6. 产生STOP信号
stop:
    base->I2CR &= ~I2CR_MSTA;            // 清除主机模式,产生STOP
    while ((base->I2SR & I2SR_IBB) != 0) // 等待总线空闲
    {
        // 超时处理(原代码中待完善)
    }
    delay_ms(5);  // 延时确保STOP完成
}

4.2 等待中断标志函数

复制代码
// 等待传输完成的中断标志
int wait_i2c_iif(I2C_Type *base)
{
    // 等待IIF(中断标志)置位
    while ((base->I2SR & I2SR_IIF) == 0){
        // 建议添加超时机制
        // if(timeout) return -2;
    }
    
    // 清除中断标志
    base->I2SR &= ~I2SR_IIF;
    
    // 检查ACK(应答)
    if ((base->I2SR & I2SR_RXAK) != 0)
    {
        return -1;  // NACK,从机未应答
    }
    return 0;       // ACK,传输成功
}

五、常见问题与调试技巧

5.1 常见问题

  1. 无应答(NACK)

    • 检查从机地址是否正确

    • 确认从机设备是否上电

    • 检查上拉电阻(通常4.7KΩ)

  2. 仲裁丢失

    • 多个主机同时尝试控制总线

    • 总线被意外干扰

  3. 时钟速率问题

    • 确保IFDR寄存器配置正确

    • 考虑总线电容导致的信号完整性

5.2 调试建议

复制代码
// 添加调试信息的改进版本
void i2c_write_debug(I2C_Type *base, ...)
{
    printf("I2C Write Start - Device: 0x%02X, Reg: 0x%02X\n", 
           dev_addr, reg_addr);
    
    // ... 原有代码 ...
    
    if (stat == -1) {
        printf("ERROR: NACK received at byte %d\n", i);
        // 可以检查具体状态寄存器
        printf("I2SR: 0x%02X\n", base->I2SR);
        printf("I2CR: 0x%02X\n", base->I2CR);
    }
    
    printf("I2C Write Complete\n");
}

六、实际应用示例

6.1 EEPROM(AT24C02)操作

复制代码
// 向EEPROM写入数据
void eeprom_write_page(uint8_t dev_addr, uint8_t mem_addr, 
                      uint8_t *data, uint8_t len)
{
    // AT24C02的器件地址:0xA0(7位地址为0x50)
    uint8_t eeprom_addr = 0x50;  // 1010000b
    
    // 页写入(最多8字节)
    if (len > 8) len = 8;
    
    // 使用原代码的i2c_write函数
    i2c_write(I2C1, eeprom_addr, mem_addr, data, len);
    
    // EEPROM需要写入周期时间(5ms)
    delay_ms(5);
}

// 从EEPROM读取数据
void eeprom_read_byte(uint8_t dev_addr, uint8_t mem_addr, 
                     uint8_t *data, uint8_t len)
{
    // 需要先发送要读取的地址,然后重新开始读操作
    // 这里需要扩展原代码支持读操作
}

总结

I2C总线是一种简单高效的串行通信协议,特别适合板内低速外设通信。原代码实现了基本的写操作,但还有以下可以完善的地方:

  1. 缺少读操作:需要实现完整的读功能

  2. 错误处理不足:需要添加超时、重试机制

  3. 调试支持:添加状态输出便于调试

  4. 中断支持:可以考虑使用中断方式提高效率

理解I2C协议的关键是掌握其时序:START→地址+R/W→ACK→数据→ACK→...→STOP。在实际应用中,要特别注意时序的严格性和错误处理的完备性。

相关推荐
程序员_小兵2 小时前
GPIO分析
c语言·单片机·嵌入式硬件·mcu
科技林总2 小时前
【系统分析师】5.3 数据库控制
学习
深蓝海拓2 小时前
Qt(PySide/PyQt)的信号槽机制的比较深入的学习笔记
qt·学习·pyqt
后来后来啊2 小时前
20261.23 &1.24学习笔记
笔记·学习·算法
我的IT修行2 小时前
Visual EAM:以架构智慧,驱动企业数字化转型新征程
架构·业务架构·应用架构·企业架构管理
历程里程碑2 小时前
Linux 4 指令结尾&&通过shell明白指令实现的原理
linux·c语言·数据结构·笔记·算法·排序算法
星河天欲瞩2 小时前
【深度学习Day4】线性代数基础
人工智能·深度学习·学习·线性代数
猿小羽2 小时前
Java 架构演进史:从咖啡杯到云原生霸主
java·云原生·架构
lpfasd1232 小时前
《影响力》精读笔记
网络·笔记·成长