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通信要点:
- 总线配置:正确启用I2C接口,配置上拉电阻
- 地址识别:使用i2cdetect扫描设备地址
- 数据读写:掌握单字节、多字节、寄存器读写方法
- 设备驱动:理解设备寄存器映射,编写驱动程序
- 调试技巧:熟练使用i2c-tools进行调试
下一章我们将学习SPI通信协议,实现高速数据传输。
最后更新:2026-03-27