0 引言
由于I2C总线的通用性,Linux作为一款优秀的
1 I2C总线的硬件构成
I2C 总线协议只有两条总线线路,一条是串行数据线(SDA),一条是串行时钟线(SCL)。SDA 负责数据的传输,SCL 负责数据传输的时钟同步。I2C 设备通过这两条总线连接到处理器的I2C总线控制器上,不同设备之间通过7 位地址来区别,而且数据的传输是双向的,方向的确定由1位二进制数确定,地址位加方向位是操作I2C 设备的惟一标示,I2C 设备与CPU 的连接如图1所示。
I2C 总线上有3 种类型的信号,分别是:开始信号,结束信号和应答信号。这些信号都是由SDA和SCL上的电平变化来表示的。
开始信号(S):当SCL为高电平时,SDA由高电平向低电平跳变,表示开始传输数据。
结束信号(P):当SCL为高电平时,SDAY由低电平向高电平跳变,表示结束传输数据。
相应信号(ACK):从机接收到8位数据后,在第9个时钟周期,拉低SDA电平,表示已经接收到数据。
当总线空闲时,SDA 和SCL 都处于高电平,主机检测到总线空闲就可以向从机发送数据。主机首先发送开始信号S,接着发出8位数据(包括前7位的从机地址和1 为的方向位),然后等待从机发回确认信号ACK.
当第8位为0时,表示向从机传输数据,主机收到确认信号后就可以连续的向从机写入8 位数据;当第8 位为1时,表示向从读取数据,这时主机就可以接收来自从机的一系列数据。最后当总个数据传输过程完成后,由主机发送结束信号P,表示本次的数据传输完成。
2 Linux 的I2C设备驱动程序的层次结构
因为I2C设备的种类繁多,如果为每一款I2C设备都编写一个驱动程序,显然不太现实也不太可能做到。所以,Linux中是对I2C 设备驱动采取了层次化处理,分为总线层和设备层。将I2C设备驱动的一些共同属性抽象起来归结起来作为总线层,而将具体I2C设备特殊操作作为设备层。在Linux中I2C设备驱动中用到的数据结构[4,7-8]的关系如图2 所示。关于这部分代码位于Linux内核源码树的/driver/i2c中。
理解这层次结构重点是要理解4个数据结构,分别是属于设备层的i2c_driver 与i2c_client,属于总线层的i2c_adapter与i2c_algorithm.下面分别对这四个数据结构做简要的说明。
struct i2c_driver:具体的每一个I2C设备都应该对应着的一个驱动,这个结构体里面定义了Linux设备模型中用于I2C 总线管理的一系列函数指针和I2C 设备的信息。其中最重要的两个成员是适配器检测函数指针at-tach_adapter,和设备ID表id_table.
struct i2c_client:一个连接在SDA 和SCL 总线上的具体设备是由i2c_client结构体描述的,定义了两个成员变量表示这个具体设备所对应的适配器和驱动。
struct i2c_adapter:此结构体表示CPU 里面具体的I2C控制器,本质上也是对应着一个物理设备,其中最要的成员变量是指向适配器驱动的程序的algo 结构体指针。
struct i2c_algorithm:里面定义了具体适配器驱动程序的函数指针。特别是master_xfer函数指针,这个函数实现了适配器最底层的操作方法,也是I2C设备驱动中总线层里面要编写的重要函数。
i2c_dev 里面定义了读写I2C 设备应用层的读写接口,但由于其缺少通用性,一般很少用到所以并不做详细的介绍。
i2c_core在驱动框架中起到了承上启下的作用,里面定义了许多重要的函数。例如:adapter注册/注销函数,增加/删除设备驱动函数,增加/删除I2C设备的函数,I2C传输,发送和接收函数。这些函数都是在编写I2C设备驱动程序中必须要用到的接口函数,正是由于这些通用的接口函数才使得代码具有很强的可移植性和重用性。