I2C 使用说明#
概述#
K230 SoC 集成了 5 个 Synopsys DesignWare I2C 控制器,具体信息如下:
I2C 控制器 |
基地址 |
默认时钟频率 |
默认状态 |
|---|---|---|---|
I2C0 |
0x91405000 |
100kHz |
disabled |
I2C1 |
0x91406000 |
400kHz |
disabled |
I2C2 |
0x91407000 |
400kHz |
disabled |
I2C3 |
0x91408000 |
400kHz |
disabled |
I2C4 |
0x91409000 |
400kHz |
disabled |
注意:所有 I2C 控制器默认状态均为
disabled,需在设备树中启用后方可使用。
设备树配置#
设备树别名配置#
在设备树的 aliases 节点中定义 I2C 总线别名,便于内核和其他设备树文件引用。
CanMV-K230 开发板示例:
aliases {
serial3 = &uart3;
i2c0 = &i2c4; // 对应 /dev/i2c-0
i2c1 = &i2c3; // 对应 /dev/i2c-1
mmc0 = &mmc_sd0;
mmc1 = &mmc_sd1;
};
说明:
别名名称(
i2c0、i2c1)由内核或板级配置定义别名指向具体的 I2C 控制器节点(
&i2c4、&i2c3)内核会根据别名创建对应编号的 I2C 总线,设备节点可通过
&i2c0引用此方式提高了设备树的可移植性,板级配置更改时无需修改所有引用
启用 I2C 控制器#
&i2c2 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c2_pins>;
clock-frequency = <400000>; // 400kHz 快速模式
};
配置引脚复用#
&iomux {
i2c2_pins: i2c2_pins {
pins = K230_IO11, K230_IO12;
function = "alt3";
bias-pull-up;
};
};
添加 I2C 设备节点#
在启用的 I2C 总线下添加设备节点:
&i2c2 {
status = "okay";
// 示例:添加 24C02 EEPROM
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
};
// 示例:添加温度传感器 TMP102
tmp102@48 {
compatible = "ti,tmp102";
reg = <0x48>;
};
};
CanMV-K230 完整配置示例#
参考文件:arch/riscv/boot/dts/canaan/k230-canmv-01studio.dts
// 初始化别名
aliases {
serial3 = &uart3;
i2c0 = &i2c4;
i2c1 = &i2c3;
mmc0 = &mmc_sd0;
mmc1 = &mmc_sd1;
};
// 引脚配置
&iomux {
i2c2_pins: i2c2_pins {
pins = K230_IO11, K230_IO12;
function = "alt3";
};
};
// 启用 I2C3(用于 LT9611 HDMI 桥接)
&i2c3 {
status = "okay";
lt9611: hdmi-bridge@3b {
compatible = "lontium,lt9611";
reg = <0x3b>;
reset-gpios = <&gpio0_ports 22 GPIO_ACTIVE_HIGH>;
interrupt-parent = <&gpio0_ports>;
interrupts = <23 IRQ_TYPE_EDGE_FALLING>;
};
};
// 启用 I2C4
&i2c4 {
status = "okay";
};
用户空间操作#
列出 I2C 总线#
i2cdetect -l
输出示例:
[root@canaan ~]# i2cdetect -l
i2c-1 i2c Synopsys DesignWare I2C adapter I2C adapter
i2c-2 i2c Synopsys DesignWare I2C adapter I2C adapter
i2c-0 i2c Synopsys DesignWare I2C adapter I2C adapter
扫描 I2C 设备#
i2cdetect -y -r -a 2
输出示例:
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
读取寄存器#
# 读取单个字节:从 I2C 总线 2 上地址为 0x50 的设备,读取偏移量为 0x00 的数据
i2cget -y 2 0x50 0x00
# 读取指定寄存器的字节数据(b = byte)
i2cget -y 2 0x50 0x01 b
# 读取字数据(w = word,16 位)
i2cget -y 2 0x50 0x00 w
写入寄存器#
# 写入单个字节
i2cset -y 2 0x50 0x00 0x55
# 写入字数据(w = word)
i2cset -y 2 0x50 0x00 0x1234 w
设备文件接口#
I2C 设备在 /dev/ 中以以下形式出现:
ls -l /dev/i2c-*
输出示例:
crw-rw---- 1 root i2c 89, 0 Jun 10 10:00 /dev/i2c-0
crw-rw---- 1 root i2c 89, 1 Jun 10 10:00 /dev/i2c-1
编程示例#
C 语言示例#
#include <linux/i2c-dev.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
void print_usage(const char *prog)
{
printf("Usage: %s [I2C_BUS]\n", prog);
printf(" -h, --help Show this help message\n");
printf(" I2C_BUS I2C bus device (default: /dev/i2c-0)\n");
}
int main(int argc, char *argv[])
{
// 解析 -h 参数
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
print_usage(argv[0]);
return 0;
}
}
int file;
char *filename = "/dev/i2c-0";
if (argc > 1) {
filename = argv[1];
}
// 打开 I2C 设备
file = open(filename, O_RDWR);
if (file < 0) {
perror("Failed to open i2c device");
return -1;
}
printf("Scanning I2C bus: %s\n", filename);
printf("Address Device\n");
printf("------- ------\n");
printf("\n7-bit addresses (0x00-0xff):\n");
// 扫描所有 0x00 到 0xFF 的地址
for (int addr = 0x00; addr <= 0xFF; addr++) {
// 设置从设备地址
if (ioctl(file, I2C_SLAVE, addr) < 0) {
continue;
}
// 尝试读取 1 个字节
// 此操作是安全的,不会影响设备状态
unsigned char buf[1];
int result = read(file, buf, 1);
// 如果读取成功,说明设备存在
if (result == 1) {
printf(" 0x%02X Present\n", addr);
}
}
printf("\nNote: 0x78-0x7F are reserved addresses.\n");
close(file);
return 0;
}
Python 示例#
使用 Adafruit Blinka busio#
import time
import sys
import board
import busio
def main(bus_num=0):
"""Scan I2C devices on specified bus."""
# SCL and SDA pins for different I2C buses
print(f"Hello Blinka! Using I2C bus {bus_num}")
_, scl, sda = board.pin.i2cPorts[bus_num]
i2c = busio.I2C(scl, sda)
while not i2c.try_lock():
pass
try:
devices = i2c.scan()
print(f"I2C devices found on bus {bus_num}: {[hex(i) for i in devices]}")
finally:
i2c.unlock()
if __name__ == "__main__":
bus = 0
if len(sys.argv) > 1:
if sys.argv[1] in ["-h", "--help"]:
print("Usage: python pi_busio_i2c.py [bus_num]")
print(" bus_num I2C bus number (default: 0)")
print(" 0 - I2C0 (SCL/SDA)")
print(" 1 - I2C1 (SCL1/SDA1)")
print(" 2 - I2C2 (SCL2/SDA2)")
sys.exit(0)
bus = int(sys.argv[1])
main(bus)
不使用库扫描设备#
#!/usr/bin/env python3
import fcntl
import sys
I2C_SLAVE = 0x0703
def i2c_scan(bus_num=0):
with open(f"/dev/i2c-{bus_num}", "r+b", buffering=0) as f:
print(f"Scanning I2C bus {bus_num}:")
print("Address Device")
print("------- ------")
for addr in range(0x00, 0x100):
try:
fcntl.ioctl(f.fileno(), I2C_SLAVE, addr)
f.read(1) # 尝试读取一个字节
print(f" 0x{addr:02X} Present")
except:
pass
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] in ["-h", "--help"]:
print("Usage: python i2c.py [bus_num]")
print(" bus_num I2C bus number (default: 0)")
print(" -h, --help Show this help message")
sys.exit(0)
bus = 0
if len(sys.argv) > 1:
bus = int(sys.argv[1])
i2c_scan(bus)
相关文件#
设备树文件:
arch/riscv/boot/dts/canaan/k230.dtsi平台驱动:
drivers/i2c/busses/i2c-designware-platdrv.c核心驱动:
drivers/i2c/busses/i2c-designware-core.hKconfig:
drivers/i2c/busses/Kconfig
