1. I2C-GPIO设备树定义的两种方法方法1:使用gpios属性(Linux 4.9.88中的方法)方法2:使用sda-gpios和scl-gpios属性(Linux 5.4中新增的方法)2. I2C-GPIO驱动层次与函数调用栈分析2.1 I2C-GPIO驱动层次图2.2 源码分析解释传输函数I2C-GPIO驱动架构关键传输函数分析1. 底层GPIO操作函数 (i2c-gpio.c)2. I2C位操作算法核心函数 (i2c-algo-bit.c)3. 传输过程详解3. 怎么使用I2C-GPIO4. 实例实验4.1. 根据原理图编写设备树4.2 确认内核已经配置了I2C-GPIO4.3 上机实验4.4 编译I2C-GPIO驱动4.5 测试
参考资料:
- i2c_spec.pdf
- Linux文档
Linux-5.4\Documentation\devicetree\bindings\i2c\i2c-gpio.yaml
Linux-4.9.88\Documentation\devicetree\bindings\i2c\i2c-gpio.txt
- Linux驱动源码
Linux-5.4\drivers\i2c\busses\i2c-gpio.c
Linux-4.9.88\drivers\i2c\busses\i2c-gpio.c
软件实现的I2C总线驱动虽然速度不如硬件I2C控制器,但具有极高的灵活性,可以在任何有GPIO引脚的系统上实现I2C通信,非常适合嵌入式系统和原型开发。
1. I2C-GPIO设备树定义的两种方法
根据Linux 4.9.88和Linux 5.4版本的I2C-GPIO驱动代码,我可以对比两种GPIO引脚定义方式:
方法1:使用gpios属性(Linux 4.9.88中的方法)
说明:
- 在
Linux 4.9.88
版本中,I2C-GPIO驱动通过of_i2c_gpio_get_pins
函数获取GPIO引脚
- 该函数使用
of_gpio_count
和of_get_gpio
函数从设备树的gpios
属性中获取SDA和SCL引脚
- 第一个引脚(索引0)被识别为SDA,第二个引脚(索引1)被识别为SCL
- 这是Linux 4.9.88版本中唯一支持的方法
方法2:使用sda-gpios和scl-gpios属性(Linux 5.4中新增的方法)
说明:
- 在Linux 5.4版本中,I2C-GPIO驱动增加了对
sda-gpios
和scl-gpios
属性的支持
- 这种方法使用独立的属性分别定义SDA和SCL引脚,更加明确
- 驱动会首先尝试使用
sda-gpios
和scl-gpios
属性,如果不存在则回退到使用gpios
属性
- 这种方法符合Linux设备树的命名规范,更加直观和明确
在Linux 4.9.88版本中,只支持使用
gpios
属性的方法1。而在Linux 5.4版本中,两种方法都支持,但推荐使用方法2,因为它更加明确和符合Linux设备树的命名规范。如果您使用的是Linux 4.9.88版本,只能使用方法1。如果您使用的是Linux 5.4或更高版本,建议使用方法2。
2. I2C-GPIO驱动层次与函数调用栈分析
2.1 I2C-GPIO驱动层次图
2.2 源码分析解释传输函数
I2C-GPIO驱动架构
I2C-GPIO驱动是一个软件实现的I2C总线驱动,它通过GPIO引脚模拟I2C总线的SDA和SCL信号线。整个驱动分为两个主要部分:
- i2c-gpio.c: 负责GPIO引脚的配置和操作,提供底层的SDA/SCL控制函数
- i2c-algo-bit.c: 实现I2C协议的位操作算法,处理I2C总线的时序和传输
关键传输函数分析
1. 底层GPIO操作函数 (i2c-gpio.c)
- i2c_gpio_setsda_dir/val: 设置SDA引脚的方向或值
- i2c_gpio_setscl_dir/val: 设置SCL引脚的方向或值
- i2c_gpio_getsda: 读取SDA引脚的值
- i2c_gpio_getscl: 读取SCL引脚的值
这些函数是I2C位操作算法的基础,通过GPIO API实现对物理引脚的控制。
2. I2C位操作算法核心函数 (i2c-algo-bit.c)
- bit_xfer: 主要的传输函数,处理整个I2C消息传输过程
- 发送起始条件
- 处理设备地址
- 根据消息类型执行读或写操作
- 发送停止条件
该函数接收一个消息数组和消息数量,按顺序处理每个消息。它负责:
- i2c_start/i2c_repstart: 产生I2C起始条件和重复起始条件
这些函数通过控制SDA和SCL线产生I2C协议规定的起始信号。
- bit_doAddress: 处理设备地址传输
该函数负责发送设备地址,支持7位和10位地址模式,并处理读写方向位。
- sendbytes/readbytes: 数据传输函数
这些函数分别处理数据的发送和接收,包括应答/非应答的处理。
- i2c_outb/i2c_inb: 字节级传输函数
这些函数实现单个字节的发送和接收,包括位操作和时序控制。
- i2c_stop: 产生I2C停止条件
该函数通过控制SDA和SCL线产生I2C协议规定的停止信号。
3. 传输过程详解
- 初始化阶段:
i2c_gpio_probe
函数注册I2C适配器- 配置GPIO引脚并设置回调函数
- 传输阶段:
- 当上层调用I2C传输函数时,请求传递到
bit_xfer
bit_xfer
发送起始条件,然后循环处理每个消息- 对每个消息,先发送设备地址,然后根据读/写标志调用相应函数
- 读操作调用
readbytes
,写操作调用sendbytes
- 最后发送停止条件
- 时序控制:
- 通过
sdalo/sdahi
和scllo/sclhi
函数控制SDA和SCL线 - 使用
udelay
函数实现精确的时序延迟 sclhi
函数支持时钟拉伸(某些设备可能需要更多时间处理数据)
- 错误处理:
- 检测设备是否应答
- 处理超时情况
- 支持重试机制
3. 怎么使用I2C-GPIO
设置设备数,在里面添加一个节点即可,示例代码看上面:
compatible = "i2c-gpio";
- 使用pinctrl把 SDA、SCL所涉及引脚配置为GPIO、开极
- 可选
- 指定SDA、SCL所用的GPIO
- 指定频率(2种方法):
i2c-gpio,delay-us = <5>; /* ~100 kHz */
- clock-frequency = <400000>;
#address-cells = <1>
;
#size-cells = <0>
;
i2c-gpio,sda-open-drain
:- 它表示其他驱动、其他系统已经把SDA设置为open drain了
- 在驱动里不需要在设置为open drain
- 如果需要驱动代码自己去设置SDA为open drain,就不要提供这个属性
i2c-gpio,scl-open-drain
:- 它表示其他驱动、其他系统已经把SCL设置为open drain了
- 在驱动里不需要在设置为open drain
- 如果需要驱动代码自己去设置SCL为open drain,就不要提供这个属性
4. 实例实验
4.1. 根据原理图编写设备树
4.1 原理图
/
4.2 编写设备树
把上述代码,放入
arch/arm/boot/dts/100ask_imx6ull-14x14.dts
的根节点下面。4.2 确认内核已经配置了I2C-GPIO
查看内核目录下的
.config
,如果未设置CONFIG_I2C_GPIO
,上机实验时需要配置内核、编译I2C-GPIO驱动。4.3 上机实验
4.1 设置工具链
- IMX6ULL
4.2 编译、替换设备树
- 编译设备树:
在Ubuntu的IMX6ULL内核目录下执行如下命令,
得到设备树文件:
arch/arm/boot/dts/100ask_imx6ull-14x14.dtb
- 复制到NFS目录:
- 开发板上挂载NFS文件系统
- vmware使用NAT(假设windowsIP为192.168.1.100)
- vmware使用桥接,或者不使用vmware而是直接使用服务器:假设Ubuntu IP为192.168.1.137
- 更新设备树
- 重启开发板
4.4 编译I2C-GPIO驱动
1. 配置内核
在IMX6ULL内核源码目录下执行
make menuconfig
命令,如下配置内核:2. 编译模块
设置工具链后,在内核目录下执行:
4.5 测试
插入EEPROM模块,在开发板上执行: