【I2C】Linux使用GPIO模拟I2C

2023-11-18

1. I2C GPIO系统架构简介

在Linux项目中,如果出现硬件硬件I2C不够用的情况下,我们就可以通过GPIO模拟I2C来解决。
Lnux内核的i2c-gpio是使用GPIO模拟I2C协议的驱动,在内核中已经实现了,我们要做的只需要配置2个GPIO(SDA和SCL)即可。

i2c-gpio的大致框架如下:
在这里插入图片描述
i2c-gpio.c

  • 解析设备树中的引脚配置信息
  • 提供GPIO SDA和SCL引脚配置接口。

i2c-algo-bit.c

  • 向I2C Core注册一个adapter
  • 提供I2C通信时的算法,然后通过i2c-gpio.c提供GPIO配置接口来收发数据。

注册成功后,"i2c-dev"驱动就会自动创建对应的"/dev/i2c-x"字符设备,然后我们就可以在应用层和驱动层操作该总线。

2. 如何使能I2C GPIO驱动

2.1 config配置

在对应的板级deconfig文件中,设置CONFIG_I2C_GPIO=y
对应的选项为:

Device Drivers->
    I2C support  --->
        I2C Hardware Bus support  --->
            <*> GPIO-based bitbanging I2C

确认配置后,i2c-gpio相关驱动就会被编译进内核。

2.2 dts配置

  • 修改一
    我这里是在IMX6ULL平台上测试的,修改文件:arch/arm/boot/dts/imx6ul.dtsi
    在这里插入图片描述
    !!!为什么需要修改aliases呢?!!!
    因为在添加添加adapter时,会通过aliases的别名编号配置adapter->nr总线编号。注册成功后,会创建/dev/i2c-4设备。
    在这里插入图片描述
  • 修改二
    添加需要模拟i2c的gpio,一定是先放sda再放scl,因为它是在i2c-gpio.c里面定义好的,必须这么写才可以。
    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";
    };
    
    图片效果如下:
    在这里插入图片描述
    !!!什么时候需要添加open drain属性?!!!
    使用GPIO模拟I2C模式时,一般GPIO需要工作在开漏模式。在of_i2c_gpio_get_props函数中,解析是否有定义open drain相关属性。如下:
    在这里插入图片描述
    当定义i2c-gpio,sda-open-draini2c-gpio,scl-open-drain属性后,说明是其它子系统已经将该GPIO配置成开漏输出了,这里不再进行开漏的配置。如果dts里面不定义,就启动GPIOD_OUT_HIGH_OPEN_DRAIN配置GPIO。所以,我这里并没有定义该属性,需要它在这里配置为开漏。
    在这里插入图片描述
  • 修改三
    使能模拟i2c5总线,并且在该总线下挂载ap3216c设备。
    &i2c5 {
    	status = "okay";
    	
    	ap3216c@1e {
    		compatible = "lite-on,ap3216c";
    		reg = <0x1e>;
    	};
    };
    

2.3 测试验证

重新编译烧录固件后,这里新增了/dev/i2c-4总线设备,它就是我们新增的GPIO模拟I2C总线设备。
在这里插入图片描述
使用i2c_tools测试该总线,可以正常的识别到设备,说明移植已经成功了。(备注:需要了解i2c_tools使用的,可以参考这篇博客《【I2C】基于Linux移植i2c-tool工具》
在这里插入图片描述

3. 简单分析i2c-gpio.c驱动

前面已经提到i2c-gpio.c驱动主要是3个功能:

3.1 解析设备树

在probe函数中会调用of_i2c_gpio_get_props函数来解析相关属性:

  • 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只支持输出模式,不支持输入模式。

具体代码如下:

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", &reg))
		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_get_desc解析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);

3.2 配置SDA和SCL

配置操作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;

3.3 注册到i2c-algo-bit.c

将配置好struct i2c_adapter的信息注册到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; // 其实这里adapter的编号是-1,真正的编号是后面注册时aliases获取的。前面已经有分析。
ret = i2c_bit_add_numbered_bus(adap);

4. 简单分析i2c-algo-bit.c驱动

前面已经提到i2c-algo-bit.c驱动主要是2个功能:

4.1 提供I2C通信时的算法

__i2c_bit_add_bus函数中会这一个i2c_bit_algo算法接口,我们使用i2c_transfer收发数据时,最终都会调用到i2c_bit_algo的bit_xfer函数收发数据。

const struct i2c_algorithm i2c_bit_algo = {
	.master_xfer = bit_xfer,
	.master_xfer_atomic = bit_xfer_atomic,
	.functionality = bit_func,
};

adap->algo = &i2c_bit_algo;

代码里面定义了很多模拟i2c时序的函数,就算我们自己写GPIO模拟I2C驱动,也都必须实现这些函数。
在这里插入图片描述

4.2 注册Adapter

向I2C Core注册一个adapter,注册成功后,"i2c-dev"驱动就会自动创建对应的"/dev/i2c-x"字符设备,然后我们就可以在应用层和驱动层操作该总线。具体代码如下:

static int __i2c_bit_add_bus(struct i2c_adapter *adap,
			     int (*add_adapter)(struct i2c_adapter *))
{
	...
	
	ret = add_adapter(adap);
	if (ret < 0)
		return ret;

	return 0;
}

int i2c_bit_add_numbered_bus(struct i2c_adapter *adap)
{
	return __i2c_bit_add_bus(adap, i2c_add_numbered_adapter);
}

5. 参考资料

Linux内核驱动:gpio模拟i2c驱动:
https://blog.csdn.net/landishu/article/details/118441943

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【I2C】Linux使用GPIO模拟I2C 的相关文章

  • Xvfb 冻结初始化 GLX 扩展

    我正在尝试运行无头 Xvfb 服务器来捕获 Amazon EC2 micro 上的屏幕截图 但它在 GLX 上陷入了困境 我使用此脚本安装了 GLX Xvfb 和所有库 https gist github com joekiller 414
  • Pthreads - 高内存使用率

    我正在用 C 编写一些东西 在 256Mb 系统上的 Linux 中创建大量 Pthread 我通常有 200Mb 的免费空间 当我使用少量线程运行该程序时 它可以工作 但是一旦我让它创建大约 100 个线程 它就会出现错误 因为系统内存不
  • 何时用引号将 shell 变量括起来?

    我应该或不应该在 shell 脚本中用引号括住变量吗 例如 下列说法正确的是 xdg open URL eq 2 or xdg open URL eq 2 如果是这样 为什么 一般规则 如果它可以为空或包含空格 或实际上任何空格 或特殊字符
  • Apache 端口转发 80 到 8080 并访问 Apache (80) 中托管的应用程序,即 phpMyadmin 和 Tomcat (8080)

    我想访问托管在 tomcat 服务器 8080 中的应用程序 myapp 当前可以通过以下方式访问http example com 8080 myapp http example com 8080 myapp in http example
  • 应用程序中两个不同版本的库

    考虑一个场景 其中有两个不同版本的共享库 考虑 A 1 so 链接到 B so A 2 so 链接到 C so 现在 B so 和 C so 都链接到 d exe 当 B so 想要调用 A 1 so 中的函数时 它最终会调用 A 2 so
  • 对于任何真实数据集,数据压缩比的最小可能值是多少

    我在写信ZLIB类似于嵌入式硬件压缩器的 API 它使用 deflate 算法来压缩给定的输入流 在进一步讨论之前 我想解释一下数据压缩率 数据压缩率定义为未压缩大小与压缩大小之间的比率 压缩比通常大于一 这意味着压缩数据通常比未压缩数据小
  • 如何在C(Linux utf8终端)中打印“盒子抽屉”Unicode字符?

    我正在尝试显示 方框图范围 2500 257F 中的 Unicode 字符 它应该是标准 utf8 Unicode 标准 版本 6 2 我根本做不到 我首先尝试使用旧的 ASCII 字符 但 Linux 终端以 utf8 显示 并且没有显示
  • 在 scapy 中通过物理环回发送数据包

    我最近发现了 Scapy 它看起来很棒 我正在尝试查看 NIC 上物理环回模块 存根上的简单流量 但是 Scapy sniff 没有给出任何结果 我正在做的发送数据包是 payload data 10 snf sniff filter ic
  • 使用 ioctl 在 C++ 中以编程方式添加路由

    我编写了简单的 C 函数 添加了新路线 void addRoute int fd socket PF INET SOCK DGRAM IPPROTO IP struct rtentry route memset route 0 sizeof
  • bash 将输出重定向到文件,但结果不完整

    重定向命令输出的问题已经被问过很多次了 但是我有一个奇怪的行为 我使用的是 bash shell debian 版本 4 3 30 1 release 并尝试将输出重定向到文件 但并非所有内容都记录在文件中 我尝试运行的 bin 文件是 l
  • Docker忽略limits.conf(试图解决“打开文件太多”错误)

    我正在运行一个 Web 服务器 该服务器正在处理数千个并发 Web 套接字连接 为了实现这一点 在 Debian linux 我的基本镜像是 google debian wheezy 在 GCE 上运行 上 打开文件的默认数量设置为 100
  • “git add”返回“致命:外部存储库”错误

    我刚刚进入 git 的奇妙世界 我必须提交我对程序所做的一系列更改 位于名为的目录中 var www myapp 我创建了一个新目录 home mylogin gitclone 从这个目录中 我做了一个git clone针对公共回购 我能够
  • /sys/device/ 和 dmidecode 报告的不同 CPU 缓存大小

    我正在尝试获取系统中不同缓存级别的大小 我尝试了两种技术 a 使用 sys device 中的信息 这是输出 cat sys devices system cpu cpu0 cache index1 size 32K cat sys dev
  • Python 3.4.3 subprocess.Popen 在没有管道的情况下获取命令的输出?

    我试图将命令的输出分配给变量 而不让命令认为它正在通过管道传输 原因是 如果正在通过管道传输 则相关命令会给出未格式化的文本作为输出 但如果从终端运行 则会给出颜色格式化的文本 我需要获取这种颜色格式的文本 到目前为止我已经尝试了一些事情
  • 在汇编中使用 printf 会导致管道传输时输出为空,但可以在终端上使用

    无输出 https stackoverflow com questions 54507957 printf call from assembly do not print to stdout即使在终端上 当输出不包含换行符时也有相同的原因
  • 为什么 fork 炸弹没有使 android 崩溃?

    这是最简单的叉子炸弹 我在许多 Linux 发行版上执行了它 但它们都崩溃了 但是当我在 android 终端中执行此操作时 即使授予后也没有效果超级用户权限 有什么解释为什么它没有使 Android 系统崩溃吗 一句话 ulimit Li
  • 在内核代码中查找函数的最佳方法[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我开始浏览内核代码 遇到的一件事是如何跟踪函数调用 结构定义等 有没有一种好的方法可以快速跳转到函数定义并退出 我尝试过 Source N
  • 执行命令而不将其保留在历史记录中[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 在进行软件开发时 经常需要在命令行命令中包含机密信息 典型示例是将项目部署到服务器的凭据设置为环境变量 当我不想将某些命令存储在命令历史记
  • SSH,运行进程然后忽略输出

    我有一个命令可以使用 SSH 并在 SSH 后运行脚本 该脚本运行一个二进制文件 脚本完成后 我可以输入任意键 本地终端将恢复到正常状态 但是 由于该进程仍在我通过 SSH 连接的计算机中运行 因此任何时候它都会登录到stdout我在本地终
  • FileOutputStream.close() 中的设备 ioctl 不合适

    我有一些代码可以使用以下命令将一些首选项保存到文件中FileOutputStream 这是我已经写了一千遍的标准代码 FileOutputStream out new FileOutputStream file try BufferedOu

随机推荐