I2C通信 #

一、I2C协议基础 #

1.1 I2C概述 #

I2C(Inter-Integrated Circuit)是一种两线式串行总线协议,由Philips公司开发。

text
┌─────────────────────────────────────────────────────────┐
│                    I2C总线拓扑结构                       │
├─────────────────────────────────────────────────────────┤
│                                                         │
│     ┌─────────┐                                        │
│     │  主机   │                                        │
│     │(Master) │                                        │
│     └────┬────┘                                        │
│          │                                             │
│    ┌─────┴─────┐                                       │
│    │           │                                       │
│   SDA         SCL                                      │
│    │           │                                       │
│    ├─────┬─────┼─────┬─────┐                          │
│    │     │     │     │     │                          │
│  ┌─┴─┐ ┌─┴─┐ ┌─┴─┐ ┌─┴─┐                            │
│  │从机│ │从机│ │从机│ │从机│                            │
│  │0x48│ │0x49│ │0x4A│ │0x4B│                            │
│  └───┘ └───┘ └───┘ └───┘                            │
│                                                         │
└─────────────────────────────────────────────────────────┘

SDA: 串行数据线 (双向)
SCL: 串行时钟线 (主机控制)

1.2 I2C特性 #

特性 说明
线路数量 2线(SDA + SCL)
通信模式 主从模式
寻址方式 7位或10位地址
传输速率 标准模式100kbps,快速模式400kbps
多主机 支持多主机模式
应答机制 每字节传输需要应答

1.3 Raspberry Pi I2C引脚 #

text
    3.3V  ──[01] [02]──  5V
   GPIO2  ──[03] [04]──  5V      GPIO2 = SDA1 (I2C1数据线)
   GPIO3  ──[05] [06]──  GND     GPIO3 = SCL1 (I2C1时钟线)

二、启用I2C接口 #

2.1 系统配置 #

bash
# 启用I2C接口
sudo raspi-config
# 选择 Interface Options -> I2C -> Enable

# 或手动启用
sudo nano /boot/config.txt
# 添加或取消注释
dtparam=i2c_arm=on

# 安装I2C工具
sudo apt install i2c-tools -y

# 重启
sudo reboot

2.2 检测I2C设备 #

bash
# 扫描I2C总线上的设备
sudo i2cdetect -y 1

# 输出示例:
#      0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
# 00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
# 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
# 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
# 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
# 40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- -- 
# 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
# 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
# 70: -- -- -- -- -- -- -- --                         

# 0x48地址处有设备

三、Pi4J I2C编程 #

3.1 基本读写操作 #

java
package com.example.i2c;

import com.pi4j.Pi4J;
import com.pi4j.io.i2c.I2C;
import com.pi4j.io.i2c.I2CConfig;
import com.pi4j.io.i2c.I2CProvider;

public class I2CBasicDemo {

    private static final int BUS = 1;
    private static final int DEVICE_ADDRESS = 0x48;
    
    public static void main(String[] args) {
        var pi4j = Pi4J.newAutoContext();
        
        I2CConfig config = I2C.newConfigBuilder(pi4j)
                .id("i2c-device")
                .name("I2C Device")
                .bus(BUS)
                .device(DEVICE_ADDRESS)
                .build();
        
        I2C device = pi4j.create(config);
        
        System.out.println("I2C设备已连接: 0x" + Integer.toHexString(DEVICE_ADDRESS));
        
        byte register = 0x00;
        
        device.writeRegisterByte(register, (byte) 0x55);
        System.out.println("写入数据: 0x55");
        
        int value = device.readRegisterByte(register);
        System.out.println("读取数据: 0x" + Integer.toHexString(value & 0xFF));
        
        pi4j.shutdown();
    }
}

3.2 批量数据读写 #

java
package com.example.i2c;

import com.pi4j.Pi4J;
import com.pi4j.io.i2c.I2C;
import com.pi4j.io.i2c.I2CConfig;
import java.nio.ByteBuffer;

public class I2CBatchDemo {

    public static void main(String[] args) {
        var pi4j = Pi4J.newAutoContext();
        
        I2C device = pi4j.create(I2C.newConfigBuilder(pi4j)
                .id("eeprom")
                .name("EEPROM")
                .bus(1)
                .device(0x50)
                .build());
        
        byte[] writeData = {0x01, 0x02, 0x03, 0x04, 0x05};
        int startAddress = 0x0000;
        
        ByteBuffer buffer = ByteBuffer.allocate(writeData.length + 2);
        buffer.put((byte) ((startAddress >> 8) & 0xFF));
        buffer.put((byte) (startAddress & 0xFF));
        buffer.put(writeData);
        
        device.write(buffer.array());
        System.out.println("写入 " + writeData.length + " 字节");
        
        byte[] readBuffer = new byte[writeData.length];
        device.readRegisterBuffer(startAddress, readBuffer, 0, readBuffer.length);
        
        System.out.print("读取数据: ");
        for (byte b : readBuffer) {
            System.out.printf("0x%02X ", b);
        }
        System.out.println();
        
        pi4j.shutdown();
    }
}

四、实际设备驱动开发 #

4.1 TMP102温度传感器 #

java
package com.example.i2c.sensor;

import com.pi4j.Pi4J;
import com.pi4j.io.i2c.I2C;
import com.pi4j.io.i2c.I2CConfig;

public class TMP102Sensor {

    private static final int DEFAULT_ADDRESS = 0x48;
    
    private static final int REGISTER_TEMPERATURE = 0x00;
    private static final int REGISTER_CONFIG = 0x01;
    private static final int REGISTER_TLOW = 0x02;
    private static final int REGISTER_THIGH = 0x03;
    
    private final I2C device;
    
    public TMP102Sensor(var pi4j, int bus, int address) {
        this.device = pi4j.create(I2C.newConfigBuilder(pi4j)
                .id("tmp102")
                .name("TMP102 Temperature Sensor")
                .bus(bus)
                .device(address)
                .build());
    }
    
    public TMP102Sensor(var pi4j) {
        this(pi4j, 1, DEFAULT_ADDRESS);
    }
    
    public double readTemperature() {
        byte[] data = new byte[2];
        device.readRegisterBuffer(REGISTER_TEMPERATURE, data, 0, 2);
        
        int rawTemp = ((data[0] << 8) | (data[1] & 0xFF)) >> 4;
        
        if ((rawTemp & 0x800) != 0) {
            rawTemp |= 0xF000;
        }
        
        return rawTemp * 0.0625;
    }
    
    public void setConfig(int config) {
        device.writeRegisterByte(REGISTER_CONFIG, (byte) config);
    }
    
    public void setLowThreshold(double tempCelsius) {
        int value = (int) (tempCelsius / 0.0625);
        device.writeRegisterByte(REGISTER_TLOW, (byte) ((value >> 4) & 0xFF));
        device.writeRegisterByte(REGISTER_TLOW + 1, (byte) ((value << 4) & 0xF0));
    }
    
    public void setHighThreshold(double tempCelsius) {
        int value = (int) (tempCelsius / 0.0625);
        device.writeRegisterByte(REGISTER_THIGH, (byte) ((value >> 4) & 0xFF));
        device.writeRegisterByte(REGISTER_THIGH + 1, (byte) ((value << 4) & 0xF0));
    }
    
    public static void main(String[] args) throws InterruptedException {
        var pi4j = Pi4J.newAutoContext();
        
        TMP102Sensor sensor = new TMP102Sensor(pi4j);
        
        System.out.println("TMP102温度传感器测试");
        
        for (int i = 0; i < 10; i++) {
            double temp = sensor.readTemperature();
            System.out.printf("温度: %.2f°C%n", temp);
            Thread.sleep(1000);
        }
        
        pi4j.shutdown();
    }
}

4.2 BMP280气压温度传感器 #

java
package com.example.i2c.sensor;

import com.pi4j.Pi4J;
import com.pi4j.io.i2c.I2C;
import com.pi4j.io.i2c.I2CConfig;

public class BMP280Sensor {

    private static final int ADDRESS_LOW = 0x76;
    private static final int ADDRESS_HIGH = 0x77;
    
    private static final int REGISTER_DIG_T1 = 0x88;
    private static final int REGISTER_DIG_P1 = 0x8E;
    private static final int REGISTER_TEMP_MSB = 0xFA;
    private static final int REGISTER_PRESS_MSB = 0xF7;
    private static final int REGISTER_CONFIG = 0xF5;
    private static final int REGISTER_CTRL_MEAS = 0xF4;
    private static final int REGISTER_RESET = 0xE0;
    private static final int REGISTER_ID = 0xD0;
    
    private final I2C device;
    private int digT1, digT2, digT3;
    private int digP1, digP2, digP3, digP4, digP5, digP6, digP7, digP8, digP9;
    
    public BMP280Sensor(var pi4j, int bus, int address) {
        this.device = pi4j.create(I2C.newConfigBuilder(pi4j)
                .id("bmp280")
                .name("BMP280 Sensor")
                .bus(bus)
                .device(address)
                .build());
        
        readCalibrationData();
        configure();
    }
    
    private void readCalibrationData() {
        digT1 = readUInt16(REGISTER_DIG_T1);
        digT2 = readInt16(REGISTER_DIG_T1 + 2);
        digT3 = readInt16(REGISTER_DIG_T1 + 4);
        
        digP1 = readUInt16(REGISTER_DIG_P1);
        digP2 = readInt16(REGISTER_DIG_P1 + 2);
        digP3 = readInt16(REGISTER_DIG_P1 + 4);
        digP4 = readInt16(REGISTER_DIG_P1 + 6);
        digP5 = readInt16(REGISTER_DIG_P1 + 8);
        digP6 = readInt16(REGISTER_DIG_P1 + 10);
        digP7 = readInt16(REGISTER_DIG_P1 + 12);
        digP8 = readInt16(REGISTER_DIG_P1 + 14);
        digP9 = readInt16(REGISTER_DIG_P1 + 16);
    }
    
    private void configure() {
        device.writeRegisterByte(REGISTER_CTRL_MEAS, (byte) 0x27);
        device.writeRegisterByte(REGISTER_CONFIG, (byte) 0xA0);
    }
    
    public int readDeviceId() {
        return device.readRegisterByte(REGISTER_ID) & 0xFF;
    }
    
    public double readTemperature() {
        byte[] data = new byte[3];
        device.readRegisterBuffer(REGISTER_TEMP_MSB, data, 0, 3);
        
        int rawTemp = ((data[0] << 16) | (data[1] << 8) | data[2]) >> 4;
        
        int var1 = ((((rawTemp >> 3) - (digT1 << 1)) * digT2) >> 11);
        int var2 = (((((rawTemp >> 4) - digT1) * ((rawTemp >> 4) - digT1)) >> 12) * digT3) >> 14;
        
        return ((var1 + var2) * 5 + 128) / 25600.0;
    }
    
    public double readPressure() {
        double temperature = readTemperature();
        
        byte[] data = new byte[3];
        device.readRegisterBuffer(REGISTER_PRESS_MSB, data, 0, 3);
        
        int rawPress = ((data[0] << 16) | (data[1] << 8) | data[2]) >> 4;
        
        int var1 = (int) (temperature * 25600.0);
        int var2 = (int) (var1 / 2);
        
        var1 = (var1 - var2) / 2;
        var2 = (int) (digP4 * 2048);
        var2 = var2 + ((rawPress * digP3) / 2);
        var2 = var2 + ((rawPress * rawPress * digP2) / 65536);
        
        int var3 = (int) (digP6 * 16384);
        int var4 = (int) (digP7 * rawPress);
        int var5 = (int) (digP8 * rawPress * rawPress) / 65536;
        
        int pressure = (int) ((digP5 + var1 + var2 + var3 + var4 + var5) / 2);
        
        return pressure;
    }
    
    public double readAltitude(double seaLevelPressure) {
        double pressure = readPressure();
        return 44330.0 * (1.0 - Math.pow(pressure / seaLevelPressure, 0.1903));
    }
    
    private int readUInt16(int register) {
        int low = device.readRegisterByte(register) & 0xFF;
        int high = device.readRegisterByte(register + 1) & 0xFF;
        return (high << 8) | low;
    }
    
    private int readInt16(int register) {
        int value = readUInt16(register);
        if (value > 32767) {
            value -= 65536;
        }
        return value;
    }
    
    public static void main(String[] args) throws InterruptedException {
        var pi4j = Pi4J.newAutoContext();
        
        BMP280Sensor sensor = new BMP280Sensor(pi4j, 1, ADDRESS_LOW);
        
        System.out.println("BMP280传感器测试");
        System.out.println("设备ID: 0x" + Integer.toHexString(sensor.readDeviceId()));
        
        for (int i = 0; i < 10; i++) {
            double temp = sensor.readTemperature();
            double pressure = sensor.readPressure() / 100.0;
            double altitude = sensor.readAltitude(101325.0);
            
            System.out.printf("温度: %.2f°C, 气压: %.2f hPa, 海拔: %.2f m%n",
                temp, pressure, altitude);
            
            Thread.sleep(1000);
        }
        
        pi4j.shutdown();
    }
}

4.3 OLED显示屏驱动 #

java
package com.example.i2c.display;

import com.pi4j.Pi4J;
import com.pi4j.io.i2c.I2C;
import com.pi4j.io.i2c.I2CConfig;

public class SSD1306Display {

    private static final int DEFAULT_ADDRESS = 0x3C;
    
    private static final int WIDTH = 128;
    private static final int HEIGHT = 64;
    private static final int PAGES = HEIGHT / 8;
    
    private static final int COMMAND_MODE = 0x00;
    private static final int DATA_MODE = 0x40;
    
    private static final int SET_CONTRAST = 0x81;
    private static final int DISPLAY_ON = 0xAF;
    private static final int DISPLAY_OFF = 0xAE;
    private static final int SET_DISPLAY_OFFSET = 0xD3;
    private static final int SET_START_LINE = 0x40;
    private static final int SET_SEGMENT_REMAP = 0xA1;
    private static final int SET_COM_SCAN_DIR = 0xC8;
    private static final int SET_COM_PINS = 0xDA;
    private static final int SET_VCOM_DESELECT = 0xDB;
    private static final int SET_PRECHARGE = 0xD9;
    private static final int SET_CLK_DIV = 0xD5;
    private static final int SET_CHARGE_PUMP = 0x8D;
    private static final int SET_MEMORY_MODE = 0x20;
    private static final int SET_COLUMN_ADDR = 0x21;
    private static final int SET_PAGE_ADDR = 0x22;
    
    private final I2C device;
    private final byte[][] buffer = new byte[PAGES][WIDTH];
    
    public SSD1306Display(var pi4j, int bus, int address) {
        this.device = pi4j.create(I2C.newConfigBuilder(pi4j)
                .id("ssd1306")
                .name("SSD1306 OLED Display")
                .bus(bus)
                .device(address)
                .build());
        
        initialize();
    }
    
    private void initialize() {
        sendCommand(DISPLAY_OFF);
        sendCommand(SET_CLK_DIV, 0x80);
        sendCommand(SET_MEMORY_MODE, 0x00);
        sendCommand(SET_START_LINE);
        sendCommand(SET_SEGMENT_REMAP);
        sendCommand(SET_COM_SCAN_DIR);
        sendCommand(SET_COM_PINS, 0x12);
        sendCommand(SET_CONTRAST, 0xCF);
        sendCommand(SET_PRECHARGE, 0xF1);
        sendCommand(SET_VCOM_DESELECT, 0x40);
        sendCommand(SET_CHARGE_PUMP, 0x14);
        sendCommand(DISPLAY_ON);
        
        clear();
        display();
    }
    
    public void clear() {
        for (int page = 0; page < PAGES; page++) {
            for (int col = 0; col < WIDTH; col++) {
                buffer[page][col] = 0;
            }
        }
    }
    
    public void setPixel(int x, int y, boolean on) {
        if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) return;
        
        int page = y / 8;
        int bit = y % 8;
        
        if (on) {
            buffer[page][x] |= (1 << bit);
        } else {
            buffer[page][x] &= ~(1 << bit);
        }
    }
    
    public void drawChar(int x, int y, char c, byte[][] font) {
        int charIndex = c - 32;
        int charWidth = font[0].length;
        int charHeight = font.length;
        
        for (int col = 0; col < charWidth; col++) {
            for (int row = 0; row < charHeight; row++) {
                boolean pixel = (font[charIndex][col] & (1 << row)) != 0;
                setPixel(x + col, y + row, pixel);
            }
        }
    }
    
    public void drawString(int x, int y, String text, byte[][] font) {
        int charWidth = font[0].length;
        int cursorX = x;
        
        for (char c : text.toCharArray()) {
            drawChar(cursorX, y, c, font);
            cursorX += charWidth + 1;
        }
    }
    
    public void display() {
        sendCommand(SET_COLUMN_ADDR, 0, WIDTH - 1);
        sendCommand(SET_PAGE_ADDR, 0, PAGES - 1);
        
        for (int page = 0; page < PAGES; page++) {
            byte[] data = new byte[WIDTH + 1];
            data[0] = DATA_MODE;
            System.arraycopy(buffer[page], 0, data, 1, WIDTH);
            device.write(data);
        }
    }
    
    private void sendCommand(int... commands) {
        byte[] data = new byte[commands.length + 1];
        data[0] = COMMAND_MODE;
        for (int i = 0; i < commands.length; i++) {
            data[i + 1] = (byte) commands[i];
        }
        device.write(data);
    }
    
    public static void main(String[] args) throws InterruptedException {
        var pi4j = Pi4J.newAutoContext();
        
        SSD1306Display display = new SSD1306Display(pi4j, 1, DEFAULT_ADDRESS);
        
        display.clear();
        display.drawString(0, 0, "Hello OLED!", null);
        display.display();
        
        System.out.println("OLED显示测试完成");
        
        pi4j.shutdown();
    }
}

五、I2C工具类封装 #

java
package com.example.i2c;

import com.pi4j.Pi4J;
import com.pi4j.io.i2c.I2C;
import com.pi4j.io.i2c.I2CConfig;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class I2CUtils {

    public static class I2CDevice {
        private final I2C i2c;
        
        public I2CDevice(I2C i2c) {
            this.i2c = i2c;
        }
        
        public void writeByte(int value) {
            i2c.write((byte) value);
        }
        
        public int readByte() {
            return i2c.read() & 0xFF;
        }
        
        public void writeRegisterByte(int register, int value) {
            i2c.writeRegisterByte(register, (byte) value);
        }
        
        public int readRegisterByte(int register) {
            return i2c.readRegisterByte(register) & 0xFF;
        }
        
        public void writeRegisterWord(int register, int value, ByteOrder order) {
            ByteBuffer buffer = ByteBuffer.allocate(2).order(order);
            buffer.putShort((short) value);
            i2c.writeRegisterBuffer(register, buffer.array(), 0, 2);
        }
        
        public int readRegisterWord(int register, ByteOrder order) {
            byte[] data = new byte[2];
            i2c.readRegisterBuffer(register, data, 0, 2);
            return ByteBuffer.wrap(data).order(order).getShort() & 0xFFFF;
        }
        
        public void writeBytes(int register, byte[] data) {
            i2c.writeRegisterBuffer(register, data, 0, data.length);
        }
        
        public byte[] readBytes(int register, int length) {
            byte[] data = new byte[length];
            i2c.readRegisterBuffer(register, data, 0, length);
            return data;
        }
        
        public void writeBit(int register, int bit, boolean value) {
            int data = readRegisterByte(register);
            if (value) {
                data |= (1 << bit);
            } else {
                data &= ~(1 << bit);
            }
            writeRegisterByte(register, data);
        }
        
        public boolean readBit(int register, int bit) {
            int data = readRegisterByte(register);
            return (data & (1 << bit)) != 0;
        }
        
        public void writeBits(int register, int startBit, int numBits, int value) {
            int data = readRegisterByte(register);
            int mask = ((1 << numBits) - 1) << startBit;
            data = (data & ~mask) | ((value << startBit) & mask);
            writeRegisterByte(register, data);
        }
        
        public int readBits(int register, int startBit, int numBits) {
            int data = readRegisterByte(register);
            int mask = (1 << numBits) - 1;
            return (data >> startBit) & mask;
        }
    }
    
    public static I2CDevice createDevice(var pi4j, int bus, int address) {
        I2CConfig config = I2C.newConfigBuilder(pi4j)
                .id("i2c-" + address)
                .name("I2C Device 0x" + Integer.toHexString(address))
                .bus(bus)
                .device(address)
                .build();
        
        return new I2CDevice(pi4j.create(config));
    }
}

六、I2C调试技巧 #

6.1 常用调试命令 #

bash
# 扫描所有I2C总线
i2cdetect -l

# 扫描指定总线上的设备
i2cdetect -y 1

# 读取设备寄存器
i2cget -y 1 0x48 0x00

# 写入设备寄存器
i2cset -y 1 0x48 0x00 0x55

# 读取多个字节
i2cdump -y 1 0x48

6.2 常见问题排查 #

问题 可能原因 解决方案
设备未检测到 地址错误 检查设备地址,尝试扫描
通信失败 上拉电阻缺失 添加4.7K上拉电阻
数据错误 时序问题 降低通信速率
设备冲突 地址冲突 更改设备地址或使用多路复用器

七、总结 #

I2C通信要点:

  1. 总线配置:正确启用I2C接口,配置上拉电阻
  2. 地址识别:使用i2cdetect扫描设备地址
  3. 数据读写:掌握单字节、多字节、寄存器读写方法
  4. 设备驱动:理解设备寄存器映射,编写驱动程序
  5. 调试技巧:熟练使用i2c-tools进行调试

下一章我们将学习SPI通信协议,实现高速数据传输。

最后更新:2026-03-27