I2C 开发指南
1. 模块介绍
1.1. 术语定义
术语 | 定义 | 注释说明 |
---|---|---|
SDA | I2C数据线 | |
SCL | I2C时钟线 | |
I2C algorithm | I2C通信方法 | 提供产生I2C总线访问的通信函数 |
I2C adapter | I2C适配器 | 对I2C控制器的软件抽象 |
I2C client | I2C客户端 | 一个client对应一个I2C device |
1.2. 模块简介
I2C模块是一个两线接口,通过SCL和SDA两根线即可完成数据的传输。I2C V1.0的设计完全遵从I2C总线协议标准,不支持SMBus协议。
基本特性如下:
- 支持master和slave模式
- 最高传输速率为400Kb/s
- 支持7bit和10bit寻址
- 且硬件支持I2C总线挂死恢复机制
2. I2C配置
2.1. 内核配置
2.1.1. master驱动使能
Device Drivers
I2C support--->
<*> I2C support
<*> I2C device interface
I2C Hardware Bus support--->
<*> ArtInChip I2C support
2.1.2. slave驱动使能
Device Drivers
I2C support--->
<*> I2C support
<*> I2C device interface
I2C Hardware Bus support--->
<*> ArtInChip I2C support
<*> ArtInChip I2C as slave mode
<*> I2C slave support
<*> I2C eeprom slave driver
注解
在将I2C作为slave时,需要一个backend程序,作为I2C slave的功能逻辑实现。此处选择内核自带的eeprom,即I2C作为slave时,是当做一个eeprom的功能在使用。也可以根据实际情况自己实现相应的backend代码
2.1.3. eeprom驱动使能
使用I2C接口与eeprom通信,是一种应用非常广泛的场景。此处介绍如何使能内核的eeprom 驱动(需要先使能I2C的master驱动)。
Device Drivers
Misc devices--->
EEPROM support--->
<*> I2C EEPROMs/RAMs/ROMs from most vendors
2.2. DTS配置
2.2.1. I2C公共参数配置
以I2C0为例
i2c0: i2c@19220000 {
compatible = "artinchip,aic-i2c-v1.0";
reg = <0x0 0x19220000 0x0 0x400>;
interrupts-extended = <&plic0 84 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cmu CLK_I2C0>;
resets = <&rst RESET_I2C0>;
#address-cells = <1>;
#size-cells = <0>;
};
2.2.2. I2C master配置
&i2c0 {
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins_a>;
status = "okay";
eeprom@50 {
compatible = "atmel,24c64";
reg = <0x50>;
pagesize = <32>;
};
};
注解
该配置是将eeprom挂在I2C0总线上的配置,reg属性表示slave设备的地址,pagesize表示eeprom一页有32byte。若在I2C总线上挂其它设备,对DTS进行相应修改即可。
2.2.3. I2C slave配置
&i2c0 {
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins_a>;
status = "okay";
slave@54 {
compatible = "slave-24c02";
reg = <0x40000054>;
};
};
注解
内核的I2C子系统框架会通过查看reg属性的bit30,判断I2C是工作在主机模式还是从机模式。若bit30为1,则为从机模式。上述配置表示I2C工作在从机模式,设备地址为0x54。
若需要设置从机为10bit寻址,则需要设置reg属性的bit31为1,如下图所示,配置从机地址为0x139。
&i2c0 {
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins_a>;
status = "okay";
slave@139 {
compatible = "slave-24c02";
reg = <0xC0000139>;
};
};
3. 调试指南
3.1. I2C通信调试
在使能I2C的驱动后,可以通过i2c-tools中的i2cdetect工具,快速检测I2C通信功能是否正常。使能驱动后,会创建出相应的I2C适配器,但是I2C通信功能是否正常仍是不确定的,可通过如下命令进行测试:
i2cdetect -y -r 0
此命令用来测试I2C总线0上的地址分布情况。如果通信正常,即使总线上没有挂接任何I2C设备,那么也会立即返回结果。如果在该测试中返回transfer timeout,那么需要先检查I2C的SDA和SCL是否有上拉(该问题一般是由于没有上拉导致)。如果有上拉,那么需要进一步进行调试。
3.2. 调试开关
I2C的驱动由一些dev_dbg的调试信息,默认情况下是不会打印的,当需要进行跟踪调试时,可通过以下步骤打开这些调试信息。
3.2.1. 调整log等级
通过menuconfig调整内核的log等级
Kernel hacking--->
printk and dmesg options--->
(8) Default console loglevel (1-15)
3.2.2. 打开调试开关
Kernel hacking--->
Artinchip Debug--->
[*] I2C driver debug
4. 测试指南
4.1. 测试环境
4.1.1. 硬件
- 测试板:带有两个I2C接口的测试板
- PC:用于和测试板交互
- 串口线:连接测试板的调试串口
4.1.2. 软件
- PC端串口终端软件
- i2c-tools第三方软件包
4.2. 测试配置
将测试板的两个I2C,一个配置为master,一个配置为slave。两个I2C接口对接。 编译第三方测试工具i2c-tools,利用i2c-tools提供的工具进行测试
4.3. i2c-tools测试
4.3.1. i2cdetect
i2cdetect用于测试系统中有哪些I2C总线,以及I2C总线上有哪些地址被使用
i2cdetect -l :列出系统中所有的I2C总线
i2cdetect -y -r 0 :查询I2C-0总线上哪些地址有挂接I2C设备。如下如所示,0x51地址上有挂接I2C设备
4.3.2. i2cset
i2cset用于每次向I2C设备写一个字节的数据
i2cset -f -y 0 0x54 1 0x39 :I2C从设备地址为0x54,将从设备中地址1处的数据设置为0x39
4.3.3. i2cget
i2cget用于每次从I2C设备读取一个字节的数据
i2cget -f -y 0 0x54 1 :I2C从设备地址为0x54,读取从设备数据地址为1处的1字节数据
4.3.4. i2ctransfer
i2ctransfer用于与I2C设备之间传输数据,每次可读写多个数据
i2ctransfer -f -y 0 w17@0x54 0 0x5a- :I2C设备地址为0x54,向从设备写入16byte数据,0为将要写入数据的起始地址,写入的数据为0x5a,0x59,0x58…
i2ctransfer -f -y 0 w1@0x54 0 r16 :I2C设备地址为0x54,从I2C设备读取16byte数据,读数据的起始地址为0
4.3.5. eeprog
eeprog是读写eeprom的工具,每次读写的message只有一个字节。若要读写8个字节,则会分成8个message进行读写
eeprog -f /dev/i2c-0 0x51 -r 0:8 -16 :I2C 设备地址为0x51,读取的数据起始地址是0,读取8byte数据,-16表示I2C设备的数据地址需要16bit表示
date | eeprog /dev/i2c-0 0x51 -w 0x200 -16 :将date命令返回的数据写入到eeprom中,写入的起始地址是0x200
注意
i2c-tools默认是不支持eeprog的编译的,并且使用eeprog时需要确保被操作的eeprom没有通过DTS挂载到I2C总线,否则会一直返回该eeprom处于busy状态。
5. 设计说明
5.1. 源码说明
源代码位于:linux-5.10/drivers/i2c/busses/
I2C的驱动文件如下:
文件 | 说明 |
---|---|
i2c-artinchip.h | aic I2C公用头文件,I2C模块的寄存器定义,结构体定义等 |
i2c-artinchip-master.c | I2C作为master时的驱动文件 |
i2c-artinchip-slave.c | I2C作为slave时的驱动文件 |
i2c-artinchip-common.c | I2C一些公用寄存器读写函数的实现,以及plaform_driver的定义 |
5.2. 模块架构
linux中I2C子系统的体系结构如下图所示
在I2C子系统中,SOC厂商需要实现的就是I2C adapter部分的驱动,I2C adapter是对I2C controller的软件抽象。具体到上图,就是实现I2C adapter的algorithm以及特定SOC的I2C代码部分。I2C模块支持master和slave两种模式,所以I2C adapter的驱动实现也就分为两部分:I2C master驱动和I2C slave驱动。
5.2.1. I2C master
I2C作为master时,驱动的实现主要包括4个部分:
- 硬件参数配置:主要是设置I2C工作的主机模式,7bit或10bit寻址,寻址的从机地址设置,FIFO设置以及总线传输速率等。
- SCL时序参数设置:根据设置的总线传输速率,设置SCL的高低电平时间。
- i2c_algorithm的实现:作为主机端,主要是master_xfer的实现。在驱动实现中,以message为单位进行数据的收发,数据的传输采用中断的方式。
- 中断的处理:处理master端的数据收发,并产生相应的start、ack、nack、restart、stop信号。
5.2.2. I2C slave
I2C作为从机时,需要一个相应的后端软件(对I2C从设备的软件模拟),该后端软件与I2C adapter驱动,组合成具有相应功能的I2C从设备。内核的I2C子系统框架中提供了一个EEPROM的软件后端,与I2C slave驱动一起,可以作为一个具有I2C接口的EEPROM使用。
I2C作为slave时,驱动的实现主要 包括3个部分:
- 硬件参数配置:设置I2C工作的从模式,FIFO设置等。
- i2c_algorithm的实现:作为从机端,主要是reg_slave和unreg_slave的实现。reg_slave用于将一个i2c_client注册到从模式的i2c adapter上,unreg_slave的功能与reg_slave相反。
- 中断的处理:处理I2C从机接收到的各种中断信号,并调用相应的回调函数进行数据的读写。
综上,I2C模块的驱动实现,主要的工作有:
- 提供I2C控制器的platform驱动,初始化I2C适配器,判断I2C模块工作的主从模式,执行不同的初始化流程。
- I2C模块作为主机时,提供I2C适配器的algorithm,并用具体适配器的xxx_xfer函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋值给i2c_adapter的algo指针。处理master端时序的设置以及I2C作为主机时的各种中断信号处理。
- I2C模块作为从机时,提供I2C适配器的algorithm,实现具体适配器的reg_slave和unreg_slave函数,并把i2c_algorithm指针赋值给i2c_adapter的algo指针。处理I2C作为从机时的各种中断信号处理。
5.3. 关键流程设计
5.3.1. 初始化流程
I2C模块驱动的初始化流程如下: