跳转至

I2C通信

概述

I2C(Inter-Integrated Circuit)是一种两线式串行通信协议,由Philips公司开发,广泛应用于嵌入式系统中芯片间的短距离通信。

I2C特点

  1. 两线制:SDA(数据线)+ SCL(时钟线)
  2. 多主多从:支持多个主设备和从设备
  3. 寻址机制:7位或10位地址
  4. 速率:标准模式100kbps,快速模式400kbps
  5. 简单灵活:硬件连接简单,软件控制灵活

I2C信号时序

Text Only
1
2
3
4
5
6
     START      ADDRESS+R/W    ACK    DATA    ACK      STOP
        |___________|_____|_______|_____|_______|_____|
SCL: _|           |_____|       |_____|       |_____|   |_

SDA:   |___________|_____|_______|_____|_______|_____|   |
       START       A6-A0  R/W   ACK   D7-D0  ACK   STOP

基本信号

  • START:SCL为高时,SDA由高变低
  • STOP:SCL为高时,SDA由低变高
  • ACK:第9个时钟周期,SDA为低表示应答
  • NACK:第9个时钟周期,SDA为高表示非应答

Linux I2C编程

用户态编程(i2c-dev)

C
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <fcntl.h>

int fd;
int adapter_nr = 1;
char filename[20];

snprintf(filename, 19, "/dev/i2c-%d", adapter_nr);
fd = open(filename, O_RDWR);

// 设置从设备地址
int addr = 0x50;
ioctl(fd, I2C_SLAVE, addr);

// 写数据
__u8 buf[2] = {0x00, 0x55};
write(fd, buf, 2);

// 读数据
__u8 reg = 0x00;
write(fd, &reg, 1);
read(fd, buf, 1);

SMBus方式

C
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>

// 写字节
i2c_smbus_write_byte(fd, 0x55);

// 写字(2字节)
i2c_smbus_write_word_data(fd, 0x00, 0x55AA);

// 读字节
__s32 value = i2c_smbus_read_byte(fd);

// 读数据块
__u8 buf[32];
i2c_smbus_read_i2c_block_data(fd, 0x00, 32, buf);

I2C工具命令

Bash
# 检测I2C总线上的设备
i2cdetect -y 1

# 读取寄存器
i2cget -y 1 0x50 0x00          # 读字节
i2cget -y 1 0x50 0x00 w        # 读字

# 写入寄存器
i2cset -y 1 0x50 0x00 0x55     # 写字节
i2cset -y 1 0x50 0x00 0x55AA w # 写字

# dump寄存器
i2cdump -y 1 0x50

STM32 I2C编程

HAL库主机模式

C
#include "stm32f4xx_hal.h"

I2C_HandleTypeDef hi2c1;

void I2C1_Init(void)
{
    hi2c1.Instance = I2C1;
    hi2c1.Init.ClockSpeed = 100000;
    hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
    hi2c1.Init.OwnAddress1 = 0;
    hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
    hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
    hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
    HAL_I2C_Init(&hi2c1);
}

// 写数据
uint8_t data[2] = {0x00, 0x55};
HAL_I2C_Master_Transmit(&hi2c1, 0x50 << 1, data, 2, 100);

// 读数据
HAL_I2C_Master_Receive(&hi2c1, 0x50 << 1, data, 2, 100);

HAL库从机模式

C
1
2
3
4
5
6
7
8
// 作为从设备接收数据
HAL_I2C_Slave_Receive(&hi2c1, data, 10, 100);

// 作为从设备发送数据
HAL_I2C_Slave_Transmit(&hi2c1, data, 10, 100);

// 设置从机地址
hi2c1.Init.OwnAddress1 = 0x50;

DMA方式

C
1
2
3
4
5
6
7
8
HAL_I2C_Master_Transmit_DMA(&hi2c1, 0x50 << 1, data, 10);
HAL_I2C_Master_Receive_DMA(&hi2c1, 0x50 << 1, data, 10);

// DMA传输完成回调
void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
    // 发送完成
}

ESP32 I2C编程

C
#include "driver/i2c.h"

#define I2C_MASTER_NUM     I2C_NUM_0
#define I2C_MASTER_SDA_IO  21
#define I2C_MASTER_SCL_IO  22
#define I2C_MASTER_FREQ_HZ 100000

void i2c_master_init(void)
{
    i2c_config_t conf = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = I2C_MASTER_SDA_IO,
        .scl_io_num = I2C_MASTER_SCL_IO,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = I2C_MASTER_FREQ_HZ,
    };
    i2c_param_config(I2C_MASTER_NUM, &conf);
    i2c_driver_install(I2C_MASTER_NUM, conf.mode, 0, 0, 0);
}

// 写命令
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (0x50 << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, 0x00, true);  // 寄存器地址
i2c_master_write_byte(cmd, 0x55, true);  // 数据
i2c_master_stop(cmd);
i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);

// 读数据
uint8_t data;
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (0x50 << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, 0x00, true);  // 寄存器地址
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (0x50 << 1) | I2C_MASTER_READ, true);
i2c_master_read_byte(cmd, &data, I2C_MASTER_NACK);
i2c_master_stop(cmd);
i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 1000 / portTICK_RATE_MS);
i2c_cmd_link_delete(cmd);

树莓派I2C编程

smbus2(Python)

Python
from smbus2 import SMBus

with SMBus(1) as bus:
    # 写字节
    bus.write_byte_data(0x50, 0x00, 0x55)
    
    # 读字节
    data = bus.read_byte_data(0x50, 0x00)
    
    # 写数据块
    bus.write_i2c_block_data(0x50, 0x00, [0x11, 0x22, 0x33])
    
    # 读数据块
    data = bus.read_i2c_block_data(0x50, 0x00, 3)

设备树I2C配置

Devicetree
&i2c1 {
    status = "okay";
    
    eeprom@50 {
        compatible = "atmel,24c08";
        reg = <0x50>;
        pagesize = <16>;
    };
    
    sensor@68 {
        compatible = "invensense,mpu6050";
        reg = <0x68>;
        interrupt-parent = <&gpio>;
        interrupts = <17 IRQ_TYPE_EDGE_RISING>;
    };
};

常用I2C设备

EEPROM(24C系列)

C
// AT24C02地址:0x50-0x57

// 写数据(页写)
void eeprom_write(uint8_t addr, uint8_t reg, uint8_t data)
{
    uint8_t buf[2] = {reg, data};
    HAL_I2C_Master_Transmit(&hi2c, addr << 1, buf, 2, 100);
}

// 读数据
uint8_t eeprom_read(uint8_t addr, uint8_t reg)
{
    uint8_t data;
    HAL_I2C_Master_Transmit(&hi2c, addr << 1, &reg, 1, 100);
    HAL_I2C_Master_Receive(&hi2c, addr << 1, &data, 1, 100);
    return data;
}

温度传感器(LM75)

C
1
2
3
4
5
6
7
8
9
// LM75地址:0x48-0x4F

float read_temperature(void)
{
    uint8_t buf[2];
    HAL_I2C_Master_Receive(&hi2c, 0x48 << 1, buf, 2, 100);
    int16_t temp = (buf[0] << 8) | buf[1];
    return temp / 256.0;
}

RTC(DS3231)

C
// DS3231地址:0x68

typedef struct {
    uint8_t sec;
    uint8_t min;
    uint8_t hour;
    uint8_t day;
    uint8_t date;
    uint8_t month;
    uint8_t year;
} rtc_time_t;

void rtc_read_time(rtc_time_t *time)
{
    uint8_t buf[7];
    uint8_t reg = 0x00;
    HAL_I2C_Master_Transmit(&hi2c, 0x68 << 1, &reg, 1, 100);
    HAL_I2C_Master_Receive(&hi2c, 0x68 << 1, buf, 7, 100);
    
    time->sec = (buf[0] >> 4) * 10 + (buf[0] & 0x0F);
    time->min = (buf[1] >> 4) * 10 + (buf[1] & 0x0F);
    // ...
}

调试技巧

波形分析

使用逻辑分析仪或示波器观察I2C波形: - 检查START/STOP信号 - 检查ACK/NACK响应 - 检查数据传输正确性 - 检查时序是否满足要求

常见问题

  1. 无ACK响应
  2. 检查设备地址是否正确
  3. 检查设备是否已上电
  4. 检查I2C线路连接

  5. 数据错误

  6. 检查上拉电阻(通常4.7kΩ)
  7. 降低通信速率
  8. 检查是否有总线冲突

  9. 通信失败

  10. 检查总线是否被占用
  11. 尝试发送STOP信号复位总线
  12. 检查电压电平匹配

参考资料