4.3.9. I2C 调试指南

4.3.9.1. 概述

X5 Soc 提供标准的 I2C 总线,包含串行数据线(SDA)和串行时钟线(SCL),连接在 I2C 总线器件为主设备设备,主设备设备之间通过 I2C 进行数据通信。

4.3.9.2. 特点

I2C 控制器支持以下功能:

  • 支持三种速度模式:

    • standard mode(0-100Kb/s);

    • fast mode(<=400Kb/s) && fast mode plus(<=1000Kb/s);

    • high-speed mode(<=3.4Mb/s)。

  • 支持主从模式配置

  • 支持7位和10位寻址模式

X5总共提供8个 I2C 控制器,其中7个(I2C0~6)位于 LSIO 子系统,1个(I2C7)位于 DSP 子系统。 8个 I2C 控制器只有 I2C4 支持 high-speed mode。

典型应用

I2C 通讯设备之间常用连接方式如下图,常用控制摄像头模块、音频设备显示模块,实现图像捕获、音频处理显示。

image-20241216-223057

功能原理

I2C 总线一种同步串行通信协议,允许多个设备(slave devices)通过时钟线(SCL)和数据线(SDA)与一个设备(master device)通信。每个设备一个唯一地址,主设备通过这些地址特定设备通信。

4.3.9.3. 驱动代码

drivers/i2c/i2c-dev.c 				# I2C 字符设备接口代码
drivers/i2c/i2c-core-base.c 			# I2C 框架代码
drivers/i2c/busses/i2c-designware-platdrv.c 	# I2C 驱动代码源文件

内核配置位置

/* arch/arm64/configs/hobot_x5_soc_defconfig */
CONFIG_I2C_CHARDEV=y 			# I2C 驱动应用层配置宏
CONFIG_I2C_DESIGNWARE_PLATFORM=y 	# DW I2C 驱动配置宏

内核 DTS 节点配置

X5 SOC 总共支持8路 I2C 总线。

X5 I2C 控制器设备树定义位于 BSP 源码的 kernel 文件夹arch/arm64/boot/dts/hobot/x5.dtsi 文件内。

备注: x5.dtsi 中节点主要声明 SoC 共有特性,和具体电路板无关,通常情况无需修改。

4.3.9.4. I2C 使用

嵌入式 Linux 系统中,I2C 总线使用经常涉及到 U-Boot 阶段使用、Kernel 阶段使用用户空间(User Space)使用。在三个阶段,I2C 的配置操作各有不同,下面介绍阶段使用方法。

I2C Uboot 阶段使用

在 U-Boot 中,i2c 命令用于调试访问 I2C 总线及其挂载设备。通过命令,可以进行 I2C 总线扫描、读取/写入 I2C 设备寄存器操作。

输入 help i2c 可以查看 i2c 命令帮助信息:

=> help i2c
i2c - I2C sub-system

Usage:
i2c bus [muxtype:muxaddr:muxchannel] - show I2C bus info
i2c crc32 chip address[.0, .1, .2] count - compute CRC32 checksum
i2c dev [dev] - show or set current I2C bus
i2c loop chip address[.0, .1, .2] [# of objects] - looping read of device
i2c md chip address[.0, .1, .2] [# of objects] - read from I2C device
i2c mm chip address[.0, .1, .2] - write to I2C device (auto-incrementing)
i2c mw chip address[.0, .1, .2] value [count] - write to I2C device (fill)
i2c nm chip address[.0, .1, .2] - write to I2C device (constant address)
i2c probe [address] - test for and show device(s) on the I2C bus
i2c read chip address[.0, .1, .2] length memaddress - read to memory
i2c write memaddress chip address[.0, .1, .2] length [-s] - write memory
          to I2C; the -s option selects bulk write in a single transaction
i2c flags chip [flags] - set or get chip flags
i2c olen chip [offset_length] - set or get chip offset length
i2c reset - re-init the I2C Controller
i2c speed [speed] - show or set I2C bus speed

1.显示 I2C 总线信息

=> i2c bus
Bus 0:  i2c@340d0000

2.选择当前的 I2C 总线

=> i2c dev 0
Setting bus to 0

3.通过 i2c probe 命令扫描总线设备地址

=> i2c probe
Valid chip addresses: 1C

输出列出当前 I2C 总线响应设备地址,这里是0x1C。

4.设置 I2C 总线速度

查看当前 I2C 总线速度:

=> i2c speed
Current bus speed=100000

设置 I2C 总线速度为 400 KHz:

=> i2c speed 400000
Setting bus speed to 400000 Hz

5.读取 I2C 设备寄存器

使用 i2c md 命令读取 I2C 设备寄存器数据:

=> i2c md 0x1C 0x00 0x20
0000: 06 0f 0f c0 00 00 00 00 3f 33 61 0d 09 00 00 00    ........?3a.....
0010: ff 00 00 00 00 00 00 33 a0 15 a0 33 a0 24 dc 33    .......3...3.$.3

0x1C 是设备地址(根据扫描设备地址填写)。 0x00 是寄存器起始地址。 0x20 是读取字节数。

6.向 I2C 设备写入数据

使用 i2c mw 命令向 I2C 设备寄存器写入数据:

=> i2c mw 0x1C 0x00 06 1

0x1C 是设备地址。 0x00 是寄存器地址。 06 是写入数据。 1 表示写入 1 个字节。

I2C Kernel 阶段使用

I2C 速度配置

默认的 I2C 速率为100Kb/s,支持100k/200k/400k/1M/3.4M 四种速率,可通过修改 DTS 中相应 I2C 节点的 clock-frequency 等完成速率修改。

修改 i2c4 控制器运行速率为例:

  • 如果需要使用3.4MHz,需要对应板卡对应的 dts 的 i2c4 的节点配置以下参数:

    ...
    &i2c4 {
    	status = "okay";
    	pinctrl-names = "default";
    	pinctrl-0 = <&pinctrl_i2c4>;
    	clock-frequency = <3400000>;
    	i2c-scl-falling-time-ns = <78>;
    	i2c-sda-falling-time-ns = <78>;
    }
    ...
    
  • 如果需要使用1MHz,需要对应板卡对应的 dts 的 i2c4 的节点配置以下参数:

    ...
    &i2c4 {
    	status = "okay";
    	pinctrl-names = "default";
    	pinctrl-0 = <&pinctrl_i2c4>;
    	clock-frequency = <1000000>;
    	i2c-scl-falling-time-ns = <60>;
    	i2c-sda-falling-time-ns = <60>;
    }
    ...
    
  • 如果需要使用400kHz,需要对应板卡对应的 dts 的 i2c4 的节点配置以下参数:

    ...
    &i2c4 {
    	status = "okay";
    	pinctrl-names = "default";
    	pinctrl-0 = <&pinctrl_i2c4>;
    	clock-frequency = <400000>;
    	i2c-scl-falling-time-ns = <190>;
    }
    ...
    

说明:

  • status: 表示节点状态,”okay” 表示设备启用,”disabled” 表示设备禁用。

  • pinctrl-names 和 pinctrl-0: 通过引脚控制子系统(Pin Control)指定该 I2C 控制器使用引脚配置。

  • clock-frequency: 设置 I2C 总线时钟频率,单位为 Hz。例如,<400000> 表示 400kHz。

  • i2c-scl-falling-time-ns 和 i2c-sda-falling-time-ns: 这两个参数用于配置 I2C 时钟线(SCL)和数据线(SDA)的下降时间,单位纳秒(ns)。

备注: “i2c-scl-falling-time-ns”及“i2c-sda-falling-time-ns”这两个参数不同的 PCB 设计可能需要通过示波器测量调整。

I2C Slave 模式使用

概述

X5 的 I2C 控制器基于 DesignWare IP,在使CONFIG_I2C_DESIGNWARE_SLAVE 后,可一路 I2C 配置Slave模式,由外部 MCU/SoC 作为 Master 通过 i2C 总线访问 X5,此时 X5 作为总线的 Slave 设备响应访问。

注意:

  • 一路 &i2cN 节点下,不要同时配置 Slave 模式设备节点与 Master 模式设备节点;该路控制器专用于 Slave 或专用于 Master。

  • Slave 模式总线时钟外部 Master 提供,速率电阻对方板卡硬件设计决定;X5 侧保证 SCL/SDA/GND 正确连接,两板共地。

使用配置

小节内核 slave-eeprom 驱动示例:在内核 RAM 中模拟 AT24C02(256 字节)EEPROM,来验证 I2C1 Slave 功能。产品化开发自行实现 Slave 端的驱动。

驱动代码

drivers/i2c/i2c-core-slave.c              # I2C Slave 核心层
drivers/i2c/busses/i2c-designware-slave.c # DW I2C Slave 控制器驱动
drivers/i2c/i2c-slave-eeprom.c            # 软件模拟 EEPROM 的 Slave 驱动

内核配置

arch/arm64/configs/hobot_x5_soc_defconfighobot_x5_soc_perf_defconfig确认以下选项:

CONFIG_I2C=y                      # I2C 核心框架
CONFIG_I2C_CHARDEV=y              # 用户态 /dev/i2c-N(Master 板测试工具需要)
CONFIG_I2C_SLAVE=y                # Slave 模式框架
CONFIG_I2C_SLAVE_EEPROM=y         # slave-eeprom 配置
CONFIG_I2C_DESIGNWARE_PLATFORM=y  # DW I2C 控制器驱动
CONFIG_I2C_DESIGNWARE_SLAVE=y     # DW I2C Slave 支持

内核 DTS 节点配置

板级 DTS 中配置如下:

&i2c1 {
    status = "okay";
    snps,slave-sar-enable;

    eeprom_slave: eeprom@64 {
        compatible = "slave-24c02";
        reg = <0x40000064>;
    };
};

说明:

  • snps,slave-sar-enable:Slave 模式使控制器总线对 IC_SAR 所配置从机地址进行应答(ACK)。

  • compatibleslave-24c02 表示模拟 256 字节 AT24C02;亦可选用 slave-24c512 等(可参考 i2c-slave-eeprom.c 驱动)。

  • reg:7 位从机地址I2C_OWN_SLAVE_ADDRESS1 << 30,即 0x40000000)按位或,该示例 7 位地址0x64,等价reg = <0x40000064>;其中 I2C_OWN_SLAVE_ADDRES开启该 I2C 控制器 Slave 模式标识。

功能测试

使用两块 X5 EVB:一块Slave 板(配置上述 DTS),一块Master 板(普通 I2C Master 配置);将两板 SCL、SDA、GND 相连;I2C 总线具备合适上拉(可由任一侧 EVB 提供,以硬件设计为准)。

1. Slave 板

确认控制器进入 Slave 模式:

# cat /sys/bus/i2c/devices/i2c-1/name
Synopsys DesignWare I2C Slave adapter

模拟 EEPROM 写入测试数据查看:

# printf 'D-Robotics X5-EVB' | dd of=/sys/bus/i2c/devices/i2c-1/1-1064/slave-eeprom bs=1 seek=0 conv=notrunc
17+0 records in
17+0 records out
17 bytes copied, 0.0118063 s, 1.4 kB/s
#
# hexdump -C /sys/bus/i2c/devices/i2c-1/${SLAVE_DEV}/slave-eeprom
00000000  44 2d 52 6f 62 6f 74 69  63 73 20 58 35 2d 45 56  |D-Robotics X5-EV|
00000010  42 ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |B...............|
00000020  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00000100

2. Master 板

扫描总线设备:

# i2cdetect -y -r 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- 64 -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

读写模拟 EEPROM 的 0x00 偏移(b 表示单字节数据格式读):

# 读偏移 0x00 的 1 字节
# i2cget -y 1 0x64 0x00 b
0x44
#

# 向偏移 0x00 写入 0x68 并回读
# i2cset -y 1 0x64 0x00 0x68
# i2cget -y 1 0x64 0x00
0x68
#

3. Slave 板核对确认

Master 写入后,在 Slave 板上看到 0x00 偏移更新:

# hexdump -C /sys/bus/i2c/devices/i2c-1/${SLAVE_DEV}/slave-eeprom
00000000  68 2d 52 6f 62 6f 74 69  63 73 20 58 35 2d 45 56  |h-Robotics X5-EV|
00000010  42 ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |B...............|
00000020  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00000100

User Space

通常,I2C 设备内核驱动程序控制,但可以用户访问总线所有设备,通过/dev/i2c-%d 接口访问,关于这一功能,内核文档的 Documentation/i2c/dev-interface 提供详细说明。

下面一个简单的i2c的用户访问代码实例:

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>

/**
 * 打开指定的 I2C 总线设备文件
 */
static int open_i2c_bus(uint32_t bus)
{
	char filename[20];
	snprintf(filename, sizeof(filename), "/dev/i2c-%d", bus);
	int file = open(filename, O_RDWR);
	if (file < 0)
	{
		perror("Failed to open the I2C bus");
	}
	return file;
}

/**
 * 设置 I2C 地址
 */
static int set_i2c_address(int file, uint8_t i2c_addr)
{
	if (ioctl(file, I2C_SLAVE, i2c_addr) < 0)
	{
		perror("Failed to set I2C address");
		return -1;
	}
	return 0;
}

/**
 * I2C 读操作:16-bit 寄存器,16-bit 数据
 */
static int32_t i2c_read_reg16_data16(int file, uint8_t i2c_addr, uint16_t reg_addr, uint16_t *value)
{
	uint8_t sendbuf[2];
	uint8_t readbuf[2];
	struct i2c_rdwr_ioctl_data data;
	struct i2c_msg msgs[2];

	sendbuf[0] = (uint8_t)((reg_addr >> 8u) & 0xFF);
	sendbuf[1] = (uint8_t)(reg_addr & 0xFF);

	msgs[0].addr = i2c_addr;
	msgs[0].flags = 0; // 写操作
	msgs[0].len = 2;
	msgs[0].buf = sendbuf;

	msgs[1].addr = i2c_addr;
	msgs[1].flags = I2C_M_RD; // 读操作
	msgs[1].len = 2;
	msgs[1].buf = readbuf;

	data.msgs = msgs;
	data.nmsgs = 2;

	if (ioctl(file, I2C_RDWR, &data) < 0)
	{
		perror("Failed to read from the I2C bus");
		return -1;
	}

	*value = (uint16_t)((readbuf[0] << 8) | readbuf[1]);
	return 0;
}

/**
 * I2C 写操作:16-bit 寄存器,16-bit 数据
 */
static int32_t i2c_write_reg16_data16(int file, uint8_t i2c_addr, uint16_t reg_addr, uint16_t value)
{
	uint8_t sendbuf[4];

	sendbuf[0] = (uint8_t)((reg_addr >> 8u) & 0xFF);
	sendbuf[1] = (uint8_t)(reg_addr & 0xFF);
	sendbuf[2] = (uint8_t)((value >> 8u) & 0xFF);
	sendbuf[3] = (uint8_t)(value & 0xFF);

	if (write(file, sendbuf, sizeof(sendbuf)) != sizeof(sendbuf))
	{
		perror("Failed to write to the I2C bus");
		return -1;
	}

	return 0;
}

/**
 * I2C 读操作:16-bit 寄存器,8-bit 数据
 */
static int32_t i2c_read_reg16_data8(int file, uint8_t i2c_addr, uint16_t reg_addr, uint8_t *value)
{
	uint8_t sendbuf[2];
	uint8_t readbuf[1];
	struct i2c_rdwr_ioctl_data data;
	struct i2c_msg msgs[2];

	sendbuf[0] = (uint8_t)((reg_addr >> 8u) & 0xFF);
	sendbuf[1] = (uint8_t)(reg_addr & 0xFF);

	msgs[0].addr = i2c_addr;
	msgs[0].flags = 0; // 写操作
	msgs[0].len = 2;
	msgs[0].buf = sendbuf;

	msgs[1].addr = i2c_addr;
	msgs[1].flags = I2C_M_RD; // 读操作
	msgs[1].len = 1;
	msgs[1].buf = readbuf;

	data.msgs = msgs;
	data.nmsgs = 2;

	if (ioctl(file, I2C_RDWR, &data) < 0)
	{
		perror("Failed to read from the I2C bus");
		return -1;
	}

	*value = readbuf[0];
	return 0;
}

/**
 * I2C 写操作:16-bit 寄存器,8-bit 数据
 */
static int32_t i2c_write_reg16_data8(int file, uint8_t i2c_addr, uint16_t reg_addr, uint8_t value)
{
	uint8_t sendbuf[3];

	sendbuf[0] = (uint8_t)((reg_addr >> 8u) & 0xFF);
	sendbuf[1] = (uint8_t)(reg_addr & 0xFF);
	sendbuf[2] = value;

	if (write(file, sendbuf, sizeof(sendbuf)) != sizeof(sendbuf))
	{
		perror("Failed to write to the I2C bus");
		return -1;
	}

	return 0;
}

/**
 * 显示帮助信息
 */
void print_help(const char *prog_name)
{
	printf("Usage: %s [options]\n", prog_name);
	printf("Options:\n");
	printf("  -b, --bus <bus>        I2C bus number\n");
	printf("  -a, --addr <address>   I2C device address\n");
	printf("  -r, --reg <reg>        Register address\n");
	printf("  -v, --value <value>    Value to write\n");
	printf("  -w, --write            Write mode (default is read)\n");
	printf("  -x, --16bit            Use 16-bit data (default is 8-bit)\n");
	printf("  -h, --help             Show this help message\n");
}

/**
 * 主函数:处理用户输入和 I2C 操作
 */
int main(int argc, char **argv)
{

	uint32_t bus = 0;
	uint8_t i2c_addr = 0;
	uint16_t reg_addr = 0;
	uint16_t value16 = 0;
	uint8_t value8 = 0;
	int is_read = 1;  // 默认读操作
	int is_16bit = 0; // 默认 8-bit 数据

	static struct option long_options[] = {
		{"bus", required_argument, 0, 'b'},
		{"addr", required_argument, 0, 'a'},
		{"reg", required_argument, 0, 'r'},
		{"value", required_argument, 0, 'v'},
		{"write", no_argument, 0, 'w'},
		{"16bit", no_argument, 0, 'x'},
		{"help", no_argument, 0, 'h'},
		{0, 0, 0, 0}};
	int opt;
	while ((opt = getopt_long(argc, argv, "b:a:r:v:wxh", long_options, NULL)) != -1)
	{
		switch (opt)
		{
		case 'b':
			bus = strtoul(optarg, NULL, 0);
			break;
		case 'a':
			i2c_addr = (uint8_t)strtoul(optarg, NULL, 0);
			break;
		case 'r':
			reg_addr = (uint16_t)strtoul(optarg, NULL, 0);
			break;
		case 'v':
			if (is_16bit)
				value16 = (uint16_t)strtoul(optarg, NULL, 0);
			else
				value8 = (uint8_t)strtoul(optarg, NULL, 0);
			is_read = 0;
			break;
		case 'w':
			is_read = 0;
			break;
		case 'x':
			is_16bit = 1;
			break;
		case 'h':
		default:
			print_help(argv[0]);
			return 0;
		}
	}
	if (argc == 1) {
		print_help(argv[0]);
		return 0;
	}
	int file = open_i2c_bus(bus);
	if (file < 0)
		return -1;

	if (set_i2c_address(file, i2c_addr) < 0)
	{
		close(file);
		return -1;
	}

	if (is_read)
	{
		if (is_16bit)
		{
			if (i2c_read_reg16_data16(file, i2c_addr, reg_addr, &value16) == 0)
			{
				printf("Read 16-bit value: 0x%04X\n", value16);
			}
		}
		else
		{
			if (i2c_read_reg16_data8(file, i2c_addr, reg_addr, &value8) == 0)
			{
				printf("Read 8-bit value: 0x%02X\n", value8);
			}
		}
	}

	if (!is_read)
	{
		if (is_16bit)
		{
			if (i2c_write_reg16_data16(file, i2c_addr, reg_addr, value16) == 0)
			{
				printf("Wrote 16-bit value: 0x%04X\n", value16);
			}
		}
		else
		{
			if (i2c_write_reg16_data8(file, i2c_addr, reg_addr, value8) == 0)
			{
				printf("Wrote 8-bit value: 0x%02X\n", value8);
			}
		}
	}

	close(file);
	return 0;
}

测试命令结果参考如下(X5 为主设备,sc230 为设备,接入 MIPI CSI3 口,挂载在 I2C bus 7):

#读取 I2C 总线 7 上地址为 0x30 的设备中寄存器地址 0x3e01 的 8 位数据:
#./i2c_tool -b 7 -a 0x30 -r 0x3e01
Read 8-bit value: 0x02

#读取 I2C 总线 7 上地址为 0x30 的设备中寄存器地址 0x3e01 的 16 位数据:
#./i2c_tool -b 7 -a 0x30 -r 0x3e01 -x
Read 16-bit value: 0x0200

#向 I2C 总线 7 上地址为 0x30 的设备中寄存器地址 0x3e01 写入 8 位数据 0x1A
#./i2c_tool -b 7 -a 0x30 -r 0x3e01 -v 0x5A
Wrote 8-bit value: 0x5A

#向 I2C 总线 7 上地址为 0x30 的设备中寄存器地址 0x3e01 写入 16 位数据 0x1A10
#./i2c_tool -b 7 -a 0x30 -r 0x3e01 -v 0x1A10 -w -x
Wrote 16-bit value: 0x1A10

注意:当前测试使用是 MIPI CSI3 接口,其他接口对应的 I2C bus 信息可以参考摄像头 ( MIPI CSI) 接口章节。

开源工具:i2c-tools

i2c-tools 是一套开源工具,该工具已经交叉编译包含在 X5 系统软件的 rootfs 中,客户可以直接使用:

  • i2cdetect — 用来列举 I2C bus 及该 bus 上所有设备

  • i2cdump — 显示 I2C 设备所有 register 值

  • i2cget — 读取 I2C 设备某个 register 的

  • i2cset — 写入 I2C 设备某个 register 的

  • i2ctransfer — 可以读、写 I2C 设备某个或者多个 register 的

I2C Sysfs 频率控制节点

X5实现内核 I2C 控制器 sysfs 节点配置 I2C 控制器运行频率功能:

root@buildroot:/# ls /sys/class/i2c-adapter/i2c-4/speed
/sys/class/i2c-adapter/i2c-4/speed
root@buildroot:~# ls /sys/class/i2c-adapter/i2c-4/scl-falling-time-ns
/sys/class/i2c-adapter/i2c-4/scl-falling-time-ns
root@buildroot:~# ls /sys/class/i2c-adapter/i2c-4/sda-falling-time-ns
/sys/class/i2c-adapter/i2c-4/sda-falling-time-ns

使用 cat 命令获取当前 I2C 控制器运行频率当前配置的 SCL/SDA 下降沿时间参数:

root@buildroot:/# cat /sys/class/i2c-adapter/i2c-4/speed
100000
root@buildroot:~# cat /sys/class/i2c-adapter/i2c-4/scl-falling-time-ns
0ns
root@buildroot:~# cat /sys/class/i2c-adapter/i2c-4/sda-falling-time-ns
0ns

使用 echo 命令修改目标频率(单位:Hz)配置对应控制器的 speed 节点:

root@buildroot:/# echo 400000 > /sys/class/i2c-adapter/i2c-4/speed
root@buildroot:/# cat /sys/class/i2c-adapter/i2c-4/speed
400000

修改 I2C 下降沿时间参数:

root@buildroot:/# echo 190 > /sys/class/i2c-adapter/i2c-4/scl-falling-time-ns
root@buildroot:/# cat /sys/class/i2c-adapter/i2c-4/scl-falling-time-ns
190ns

注意:

  • 操作 sysfs 速率配置节点不会修改 dts 内配置的“i2c-scl-falling-time-ns”以及“i2c-sda-falling-time-ns”。因此,在修改 sysfs 节点配置速率后,需要根据 PCB 的实际情况,配置 scl-falling-time-ns 及 sda-falling-time-ns。

  • scl-falling-time-ns 及 sda-falling-time-ns 主要作为调试节点提供,推荐用户通过 DTS 配置默认参数,而不是直接修改 sysfs 节点。

  • 配置正确的 scl-falling-time-ns 和 sda-falling-time-ns 可能导致 I2C 控制器无法正常运作,请谨慎使用。

4.3.9.5. I2C 常见问题

使用 I2C 总线过程中,可能遇到一些典型问题,以下列出常见问题对应排查解决方法。

驱动能力不足

问题描述: 在 I2C 通信过程中,i2c 信号无法有效拉低,导致通信失败。

可能原因:

  • I2C 控制器当前配置驱动能力不足,无法提供足够电流拉低 i2c 信号线。

  • 硬件上拉电阻配置不合理,阻值过低,影响了 I2C 的电平。

排查方法:

  • 示波器观测波形:

    • 通过示波器检测 I2C 信号波形,重点查看信号是否能够有效拉低逻辑低电平,根据主从设备阈值留有一定裕量。

  • 检查硬件电阻配置:

    • 确认 SDA 和 SCL 信号线上拉电阻阻值,推荐值为 速率 <=400Kb/s 时,使用2.2K、4.7K 上拉;速率 > 400Kb/s 时,使用1K 上拉;若发现问题,可以尝试减小上拉电阻值,以平衡 i2c 下能力。

解决方案:

  • 修改设备树增加驱动能力,以 i2c4为例参照修改,并修改段 drive-strength。

pconf_drv_pu_ds4_1v8: pconf-dev-pu-ds4-1v8 {
	bias-pull-up;
	power-source = <HORIZON_IO_PAD_VOLTAGE_1V8>;
	drive-strength = <4>;
};

&i2c4grp {
	horizon,pins = <
		LSIO_I2C4_SCL  LSIO_PINMUX_3 BIT_OFFSET0  MUX_ALT0 &pconf_drv_pu_ds4_1v8
		LSIO_I2C4_SDA  LSIO_PINMUX_3 BIT_OFFSET2  MUX_ALT0 &pconf_drv_pu_ds4_1v8
	>;
};
  • 优化上拉电阻: 根据实际波形情况,调整 SDA 和 SCL 线电阻,减小上拉阻值提升信号驱动能力。

设备供电时序异常

问题描述: 在 I2C 通信初始化阶段,主设备尝试访问设备,但通信失败,设备无法响应。

可能原因:

  • 设备供电初始化时序错误较慢,导致设备准备设备访问。

排查方法:

  • 检测设备供电时序:

    • 使用示波器逻辑分析仪观察设备时序,确认供电是否稳定,是否符合 Spec 的要求。

  • 确认设备工作状态:

    • 使用 i2cdetect 工具扫描 I2C 总线,验证设备是否正常检测到:

i2cdetect -y 4  # 4为 I2C 控制器实际编号

如果检测设备地址,说明设备尚未准备供电存在问题。

解决方案:

  • 设备上电时序由主设备控制时,需要确认上电时序是否符合要求,并确保设备初始化完成再启动 I2C 通信。