LV130-GPIO模拟I2C
一、GPIO 模拟 I2C 简介
在 Linux 项目中,如果出现硬件 I2C 不够用的情况,就可以使用模拟 i2c 来解决。其实 I2C 只是规定了数据传输的协议,并没有说一定要使用物理的 I2C 外设,我们知道 2C 协议信号如下:

这里其实就是一根时钟线,一根数据线,这些我们完全可以用两个 GPIO 来进行模拟。之前学习单片机的时候其实一开始用的都是 gpio 模拟 i2c:STM32_HAL_Prj/Drivers/BSP/IIC/myiic.c

使用 GPIO 模拟 I2C 的要点:
- 引脚设为 GPIO。
- GPIO 设为输出、开极/开漏(open collector/open drain)。
- 要有上拉电阻,为什么需要上拉电阻,在前面已经学习过了,主要就是可以方便的实现数据双向传输。以及实现多个设备对 I2C 总线的使用。
二、GPIO 引脚定义
既然是 GPIO 模拟 I2C,那其实,只需要两个 I2C 就可以了,但是在不同版本的内核中,会支持一些新的写法。
Example nodes:
#include <dt-bindings/gpio/gpio.h>
i2c@0 {
compatible = "i2c-gpio";
sda-gpios = <&pioA 23 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)>;
scl-gpios = <&pioA 24 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN)>;
i2c-gpio,delay-us = <2>; /* ~100 kHz */
#address-cells = <1>;
#size-cells = <0>;
rv3029c2@56 {
compatible = "rv3029c2";
reg = <0x56>;
};
};i2c@0 {
compatible = "i2c-gpio";
gpios = <&pioA 23 0 /* sda */
&pioA 24 0 /* scl */
>;
i2c-gpio,sda-open-drain;
i2c-gpio,scl-open-drain;
i2c-gpio,delay-us = <2>; /* ~100 kHz */
#address-cells = <1>;
#size-cells = <0>;
rv3029c2@56 {
compatible = "rv3029c2";
reg = <0x56>;
};
};三、i2c-gpio 基本框架
Lnux 内核的 i2c-gpio 是使用 GPIO 模拟 I2C 协议的驱动,在内核中已经实现了,我们要做的只需要配置 2 个 GPIO(SDA 和 SCL)即可。
(1)解析设备树中的引脚配置信息
(2)提供 GPIO SDA 和 SCL 引脚配置接口。
(1)向 I2C Core 注册一个 adapter
(2)提供 I2C 通信时的算法,然后通过 i2c-gpio.c 提供 GPIO 配置接口来收发数据。
注册成功后,
"i2c-dev"驱动就会自动创建对应的"/dev/i2c-x"字符设备,然后我们就可以在应用层和驱动层操作该总线。
四、如何添加一个模拟 I2C?
1. 驱动相关文件放在那里?
GPIO 模拟 I2C 协议的驱动位于 busses - drivers/i2c/busses 目录。驱动名称为“i2c-gpio”,驱动文件为 i2c-gpio.c - drivers/i2c/busses/i2c-gpio.c,这个也是 platform 驱动:

2. 使能内核的 I2C GPIO 驱动
我们在内核源码目录下输入:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v6_v7_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig按以下路径找到对应的配置项:
Device Drivers->
I2C support --->
I2C Hardware Bus support --->
<*> GPIO-based bitbanging I2C
确认配置后,i2c-gpio 相关驱动就会被编译进内核。当然我们也可以编译成驱动模块,然后手动加载,不过还是直接编译到内核更方便一些。
3. 设备树配置
3.1 模拟 I2C 节点
i2c-gpio 的 i2c_adapter 设备树节点这样写:
i2c5:i2c5_gpio {
#address-cells = <1>;
#size-cells = <0>;
compatible = "i2c-gpio";
gpios = <&gpio1 29 GPIO_ACTIVE_HIGH>, /* sda */
<&gpio1 28 GPIO_ACTIVE_HIGH>; /* scl */
i2c-gpio,delay-us = <5>; /* ~100 kHz */
status = "disabled";
};我们打开 imx6ul.dtsi 添加以上内容,需要注意的是添加需要模拟 i2c 的 gpio 的时候,一定是先放 sda 再放 scl,因为它是在 i2c-gpio.c 里面定义好的,必须这么写才可以。
3.2 修改 aliases
这里以 imx6ul.dtsi - arch/arm/boot/dts/imx6ul.dtsi 为例,我们添加如下内容:
/ {
#address-cells = <1>;
#size-cells = <1>;
//......
aliases {
ethernet0 = &fec1;
ethernet1 = &fec2;
gpio0 = &gpio1;
gpio1 = &gpio2;
gpio2 = &gpio3;
gpio3 = &gpio4;
gpio4 = &gpio5;
i2c0 = &i2c1;
i2c1 = &i2c2;
i2c2 = &i2c3;
i2c3 = &i2c4;
i2c4 = &i2c5; // 这行是新添加的
mmc0 = &usdhc1;
//......
};
};为什么需要修改 aliases 呢? 因为在添加添加 adapter 时,会通过 aliases 的别名编号配置 adapter->nr 总线编号。注册成功后,会创建 /dev/i2c-4 设备。在 i2c_add_adapter() 函数中:
int i2c_add_adapter(struct i2c_adapter *adapter)
{
struct device *dev = &adapter->dev;
int id;
if (dev->of_node) {
id = of_alias_get_id(dev->of_node, "i2c");
if (id >= 0) {
adapter->nr = id;
return __i2c_add_numbered_adapter(adapter);
}
}
//......
adapter->nr = id;
return i2c_register_adapter(adapter);
}这样在注册 i2c_adapter 的时候 i2c_add_adapter() 函数就会获取到对应的一个编号。
3.3 open drain 属性
使用 GPIO 模拟 I2C 模式时,一般 GPIO 需要工作在开漏模式。在 of_i2c_gpio_get_props() 函数中,解析是否有定义 open drain 相关属性。如下:
static void of_i2c_gpio_get_props(struct device_node *np,
struct i2c_gpio_platform_data *pdata)
{
u32 reg;
of_property_read_u32(np, "i2c-gpio,delay-us", &pdata->udelay);
if (!of_property_read_u32(np, "i2c-gpio,timeout-ms", ®))
pdata->timeout = msecs_to_jiffies(reg);
pdata->sda_is_open_drain =
of_property_read_bool(np, "i2c-gpio,sda-open-drain");
pdata->scl_is_open_drain =
of_property_read_bool(np, "i2c-gpio,scl-open-drain");
pdata->scl_is_output_only =
of_property_read_bool(np, "i2c-gpio,scl-output-only");
}当定义 i2c-gpio, sda-open-drain 和 i2c-gpio, scl-open-drain 属性后,说明是其它子系统已经将该 GPIO 配置成开漏输出了,这里不再进行开漏的配置。如果 dts 里面不定义,就启动 GPIOD_OUT_HIGH_OPEN_DRAIN 配置 GPIO,我们可以看一下 i2c_gpio_probe():
if (pdata->sda_is_open_drain)
gflags = GPIOD_OUT_HIGH;
else
gflags = GPIOD_OUT_HIGH_OPEN_DRAIN;
//......
if (pdata->scl_is_open_drain)
gflags = GPIOD_OUT_HIGH;
else
gflags = GPIOD_OUT_HIGH_OPEN_DRAIN;所以,这里可以不定义该属性,它将会在函数 i2c_gpio_probe() 中配置为开漏。
3.4 添加 i2c 设备
有了 i2c 适配器,就可以添加对应的 i2c 设备了,这里就和前面一样了。为了方便后面测试模拟 I2C 总线,这里还是需要一个设备:
&i2c5 {
status = "okay";
ap3216c@1e {
compatible = "alpha,ap3216c";
reg = <0x1e>;
};
};不过,我们并没有实际的设备挂载在上面,后面可以直接用下面的命令创建模拟的 i2c 设备:
// 创建一个i2c_client, .name = "eeprom", .addr=0x50, .adapter是i2c-4
# echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-4/new_device
// 删除一个i2c_client
# echo 0x50 > /sys/bus/i2c/devices/i2c-4/delete_device3.5 开发板测试
我们更新开发板的设备树,就会发现这里新增了 /dev/i2c-4 总线设备,它就是我们新增的 GPIO 模拟 I2C 总线设备。

使用 i2c_tools 测试该总线,可以正常的识别到设备,说明移植已经成功了。但是我这里实际上并没有接入 i2c 设备,就算手动创建一个 i2c 设备,也只会创建对应的设备,但是 i2cdetect 不会有反应,因为我们实际并没有对应的 i2c 设备存在,这就会导致 i2c 向这个地址发数据的时候,并不会收到回应,所以就探测不出来了:

五、i2c-gpio 驱动分析
前面知道,GPIO 模拟 I2C 协议的驱动位于 busses - drivers/i2c/busses 目录。驱动名称为“i2c-gpio”,驱动文件为 i2c-gpio.c - drivers/i2c/busses/i2c-gpio.c,是一个平台设备驱动,它的平台设备驱动结构体为 i2c_gpio_driver
static struct platform_driver i2c_gpio_driver = {
.driver = {
.name = "i2c-gpio",
.of_match_table = of_match_ptr(i2c_gpio_dt_ids),
},
.probe = i2c_gpio_probe,
.remove = i2c_gpio_remove,
};1. i2c_gpio_driver.driver.of_match_table
先来看一下这个匹配表,对应的数组为 i2c_gpio_dt_ids
static const struct of_device_id i2c_gpio_dt_ids[] = {
{ .compatible = "i2c-gpio", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, i2c_gpio_dt_ids);所以我们前面定义的模拟 I2C 的 compatible 属性名称就包含的有 "i2c-gpio"。
2. i2c_gpio_driver.probe
当 i2c 适配器设备和驱动匹配的时候,就会执行.probe 函数,在这里就是 i2c_gpio_probe()
static int i2c_gpio_probe(struct platform_device *pdev)
{
//......
// (1)解析设备树
if (np) {
of_i2c_gpio_get_props(np, pdata);
} else {
//......
}
//......
// (2)解析 dts 设备树文件里面定义 gpios 配置:gpios = <&gpio1 29 GPIO_ACTIVE_HIGH>, <&gpio1 28 GPIO_ACTIVE_HIGH>;
if (pdata->sda_is_open_drain)
gflags = GPIOD_OUT_HIGH;
else
gflags = GPIOD_OUT_HIGH_OPEN_DRAIN;
priv->sda = i2c_gpio_get_desc(dev, "sda", 0, gflags);
if (IS_ERR(priv->sda))
return PTR_ERR(priv->sda);
if (pdata->scl_is_open_drain)
gflags = GPIOD_OUT_HIGH;
else
gflags = GPIOD_OUT_HIGH_OPEN_DRAIN;
priv->scl = i2c_gpio_get_desc(dev, "scl", 1, gflags);
if (IS_ERR(priv->scl))
return PTR_ERR(priv->scl);
//......
// (3)配置操作 SDA 和 SCL 2 个 GPIO 的函数接口,后面可以通过它设置和获取 GPIO 的高低电平
bit_data->setsda = i2c_gpio_setsda_val;
bit_data->setscl = i2c_gpio_setscl_val;
if (!pdata->scl_is_output_only)
bit_data->getscl = i2c_gpio_getscl;
bit_data->getsda = i2c_gpio_getsda;
//......
// (4)注册到 i2c-algo-bit.c
adap->algo_data = bit_data;
adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
adap->dev.parent = dev;
adap->dev.of_node = np;
adap->nr = pdev->id;
ret = i2c_bit_add_numbered_bus(adap);
if (ret)
return ret;
//......
return 0;
}2.1 解析设备树
我们看一下这个 of_i2c_gpio_get_props()
static void of_i2c_gpio_get_props(struct device_node *np,
struct i2c_gpio_platform_data *pdata)
{
u32 reg;
of_property_read_u32(np, "i2c-gpio,delay-us", &pdata->udelay);
if (!of_property_read_u32(np, "i2c-gpio,timeout-ms", ®))
pdata->timeout = msecs_to_jiffies(reg);
pdata->sda_is_open_drain =
of_property_read_bool(np, "i2c-gpio,sda-open-drain");
pdata->scl_is_open_drain =
of_property_read_bool(np, "i2c-gpio,scl-open-drain");
pdata->scl_is_output_only =
of_property_read_bool(np, "i2c-gpio,scl-output-only");
}- i2c-gpio, delay-us:配置每个 bit 的使用时间,也就是 I2C 通信时 Clock 的频率。
- i2c-gpio, timeout-ms:配置 i2c 通信时的超时时间,如果超过这个时间没有收到 ack,说明通信失败。
- i2c-gpio, sda-open-drain:是否有在其它子系统里面定义了 sda gpio 为开漏模式,如果有就定义该属性。
- i2c-gpio, scl-open-drain:是否有在其它子系统里面定义了 scl gpio 为开漏模式,如果有就定义该属性。
- i2c-gpio, scl-output-only:配置 scl gpio 只支持输出模式,不支持输入模式。
2.2 注册到 i2c-algo-bit.c
我们看一下 i2c_bit_add_numbered_bus():
int i2c_bit_add_numbered_bus(struct i2c_adapter *adap)
{
return __i2c_bit_add_bus(adap, i2c_add_numbered_adapter);
}它调用的是__i2c_bit_add_busr()函数:
static int __i2c_bit_add_bus(struct i2c_adapter *adap,
int (*add_adapter)(struct i2c_adapter *))
{
//......
/* register new adapter to i2c module... */
adap->algo = &i2c_bit_algo;
//......
ret = add_adapter(adap);
//......
return 0;
}可以看到这里为 i2c-gpio 添加了对应的 i2c_algorithm ,这里对应的是 i2c_bit_algo,它的 master_xfer 成员指向的函数就为模拟 i2c 添加用于产生 i2c 访问周期需要的 start stop ack 信号操作函数。就算我们自己写 GPIO 模拟 I2C 驱动,也都必须实现这些函数。
const struct i2c_algorithm i2c_bit_algo = {
.master_xfer = bit_xfer,
.functionality = bit_func,
};这个我们后面再看。然后就是调用 add_adapter() 向 I2C Core 注册一个 adapter,注册成功后,"i2c-dev" 驱动就会自动创建对应的 "/dev/i2c-x" 字符设备,然后我们就可以在应用层和驱动层操作该总线。
3. i2c_bit_algo
3.1 i2c_bit_algo.functionality
i2c_bit_algo.functionality 对应的函数是 bit_func()
static u32 bit_func(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C | I2C_FUNC_NOSTART | I2C_FUNC_SMBUS_EMUL |
I2C_FUNC_SMBUS_READ_BLOCK_DATA |
I2C_FUNC_SMBUS_BLOCK_PROC_CALL |
I2C_FUNC_10BIT_ADDR | I2C_FUNC_PROTOCOL_MANGLING;
}这里就是返回支持的 I2C 协议。
3.2 i2c_bit_algo.master_xfer
i2c_bit_algo.master_xfer 对应的是 bit_xfer() 函数,这个函数就是为 i2c 的访问产生 i2c 访问周期需要的 start stop ack 信号:
static int bit_xfer(struct i2c_adapter *i2c_adap,
struct i2c_msg msgs[], int num)
{
//......
i2c_start(adap);
for (i = 0; i < num; i++) {
//......
if (pmsg->flags & I2C_M_RD) {
/* read bytes into buffer*/
ret = readbytes(i2c_adap, pmsg);
//......
} else {
/* write bytes from buffer */
ret = sendbytes(i2c_adap, pmsg);
//......
}
}
ret = i;
//......
i2c_stop(adap);
//......
return ret;
}当我们使用 i2c_transfer() 函数收发数据的时候,就会调用到这个函数。
六、GPIO 模拟 I2Cdemo
1. demo 源码
这个 demo 源码可以看这里:30_i2c_subsystem/04_i2c_gpio/device_tree/imx6ull-alpha-emmc/imx6ull-alpha-emmc.dtsi · 苏木/imx6ull-driver-demo - 码云 - 开源中国
这个 demo 主要是修改设备树,驱动是不需要的,另外还修改了 kernel/dts_common/imx6ul.dtsi · 苏木/imx6ull-driver-demo - 码云 - 开源中国
2. 开发板测试
就和本篇笔记 第四节的 3.5 的测试现象一样。
参考资料:
【驱动】linux 下 I2C 驱动架构全面分析 - Leo.cheng - 博客园
Linux i2c 子系统(一) _动手写一个 i2c 设备驱动_Linux 编程_Linux 公社-Linux 系统门户网站
I2C 子系统–mpu6050 驱动实验 野火嵌入式 Linux 驱动开发实战指南——基于 i.MX6ULL 系列 文档