I2C核心、总线、设备驱动、数据传输
I2C 总线特征
• 只要求两条总线线路: 一条串行数据线 SDA/一条串行时钟线 SCL。 SDA 和 SCL 都是双向线路, 当总线空闲时这两条线路都是高电平都通过一个电流源或上拉电阻连接到正的电源电压
• 每个连接到总线的器件都可以通过唯一的地址和一直存在的简单的主机、从机关系软件设定地址 主机可以作为主机发送器或主机接收器
•它是一个真正的多主机总线,如果两个或更多主机同时初始化数据传输可以通过冲突检测和仲裁,防止数据被破坏
• 串行的 8 位双向数据传输位速率在标准模式下可达 100kbit/s 快速模式下可达 400kbit/s 高速
• 片上的滤波器可以滤去总线数据线上的毛刺波
• 连接到相同总线的 IC 数量只受到总线的最大电容 400pF 限制
I2C体系结构
1、I2C核心
- I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(即Algorithm)上层的与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。
2、I2C总线驱动
-
I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部。
-
I2C总线驱动主要包含***I2C适配器数据结构i2c_adapter***、I2C适配器的Algorithm数据结构***i2c_algorithm和控制I2C适配器产生通信信号***的函数。
-
经由I2C总线驱动的代码,我们可以控制I2C适配器以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。
-
i2c_adpater与i2c_client的关系与I2C硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adpater。由于一个适配器可以连接多个I2C设备,所以一个i2c_adpater也可以被多个i2c_client依附,i2c_adpater中包括依附于它的i2c_client的链表。
3、I2C设备驱动
-
I2C设备驱动(也称为客户驱动)是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。
-
I2C设备驱动主要包含数据结构i2c_driver和i2c_client,我们需要根据具体设备实现其中的成员函数。
-
i2c_driver对应于一套驱动方法,其主要成员函数是probe()、remove()、suspend()、resume()等,另外,struct i2c_device_id形式的id_table是该驱动所支持的I2C设备的ID表。i2c_client对应于真实的物理设备,每个I2C设备都需要一个i2c_client来描述。i2c_driver与i2c_client的关系是一对多,一个i2c_driver可以支持多个同类型的i2c_client。
-
对于I2C驱动编程,应实现以下几点:
-
总线驱动实现点
·提供I2C适配器的硬件驱动,探测、初始化I2C适配器(如申请I2C的I/O地址和中断号)、驱动CPU控制的I2C适配器从硬件上产生各种信号以及处理I2C中断等。
·提供I2C适配器的Algorithm,用具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋值给i2c_adapter的algo指针。
-
设备驱动实现点
·实现I2C设备驱动中的i2c_driver接口,用具体设备yyy的yyy_probe()、yyy_remove()、yyy_suspend()、yyy_resume()函数指针和i2c_device_id设备ID表赋值给i2c_driver的probe、remove、suspend、resume和id_table指针。
·实现I2C设备所对应类型的具体驱动,i2c_driver只是实现设备与总线的挂接,而挂接在总线上的设备则千差万别。例如,如果是字符设备,就实现文件操作接口,即实现具体设备yyy的yyy_read()、yyy_write()和yyy_ioctl()函数等;如果是声卡,就实现ALSA驱动。
-
I2C核心
-
I2C核心(drivers/i2c/i2c-core.c)中提供了一组不依赖于硬件平台的接口函数,这个文件一般不需要被工程师修改,但是理解其中的主要函数非常关键,因为I2C总线驱动和设备驱动之间以I2C核心作为纽带。I2C核心中的主要函数如下。
-
(1)增加/删除i2c_adapter
int i2c_add_adapter(struct i2c_adapter *adap); void i2c_del_adapter(struct i2c_adapter *adap);
(2)增加/删除i2c_driver
int i2c_register_driver(struct module *owner, struct i2c_driver *driver); void i2c_del_driver(struct i2c_driver *driver); #define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)
(3)I2C传输、发送和接收
int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num); int i2c_master_send(struct i2c_client *client,const char *buf ,int count); int i2c_master_recv(struct i2c_client *client, char *buf ,int count);
I2C适配器驱动
1、I2C适配器的注册与注销
-
由于I2C总线控制器通常是在内存上的,所以它本身也连接在platform总线上,要通过platform_driver和platform_device的匹配来执行。因此尽管I2C适配器给别人提供了总线,它自己也被认为是接在platform总线上的一个客户。Linux的总线、设备和驱动模型实际上是一个树形结构,每个节点虽然可能成为别人的总线控制器,但是自己也被认为是从上一级总线枚举出来的。
-
通常我们会在与I2C适配器所对应的platform_driver的probe()函数中完成两个工作。
·初始化I2C适配器所使用的硬件资源,如申请I/O地址、中断号、时钟等。
·通过i2c_add_adapter()添加i2c_adapter的数据结构,当然这个i2c_adapter数据结构的成员已经被xxx适配器的相应函数指针所初始化。 -
通常我们会在platform_driver的remove()函数中完成与加载函数相反的工作。
·释放I2C适配器所使用的硬件资源,如释放I/O地址、中断号、时钟等。
·通过i2c_del_adapter()删除i2c_adapter的数据结构。
2、I2C总线的通信方法
-
需要为特定的I2C适配器实现通信方法,主要是实现i2c_algorithm的functionality()函数和master_xfer()函数。
-
functionality()函数非常简单,用于返回algorithm所支持的通信协议,如I2C_FUNC_I2C、I2C_FUNC_10BIT_ADDR、I2C_FUNC_SMBUS_READ_BYTE、I2C_FUNC_SMBUS_WRITE_BYTE等。
-
master_xfer()函数在I2C适配器上完成传递给它的i2c_msg数组中的每个I2C消息。函数模板如下
static int i2c_adapter_xxx_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { ... for (i = 0; i < num; i++) { i2c_adapter_xxx_start(); /* 产生开始位 */ if (msgs[i]->flags &I2C_M_RD) { /* 是读消息 */ i2c_adapter_xxx_setaddr((msg->addr << 1) | 1); /* 发送从设备读地址 */ i2c_adapter_xxx_wait_ack(); /* 获得从设备的 ack */ i2c_adapter_xxx_readbytes(msgs[i]->buf, msgs[i]->len); /* 读取 msgs[i] ->len长的数据到 msgs[i]->buf */ } else { /* 是写消息 */ i2c_adapter_xxx_setaddr(msg->addr << 1); /* 发送从设备写地址 */ i2c_adapter_xxx_wait_ack(); /* 获得从设备的 ack */ i2c_adapter_xxx_writebytes(msgs[i]->buf, msgs[i]->len); /* 读取 msgs[i]->len长的数据到 msgs[i]->buf */ } } i2c_adapter_xxx_stop(); /* 产生停止位 */ }
master_xfer()函数模板中的i2c_adapter_xxx_start()、i2c_adapter_xxx_setaddr()、i2c_adapter_xxx_wait_ack()、i2c_adapter_xxx_readbytes()、i2c_adapter_xxx_writebytes()和i2c_adapter_xxx_stop()函数用于完成适配器的底层硬件操作,与I2C适配器和CPU的具体硬件直接相关,需要由工程师根据芯片的数据手册来实现。i2c_adapter_xxx_readbytes()用于从从设备上接收一串数据,i2c_adapter_xxx_writebytes()用于向从设备写入一串数据,这两个函数的内部也会涉及I2C总线协议中的ACK应答。
- master_xfer时序如下:
I2C设备驱动
I2C设备驱动要使用i2c_driver和i2c_client数据结构并填充i2c_driver中的成员函数。i2c_client一般被包含在设备的私有信息结构体yyy_data中,而i2c_driver则适合被定义为全局变量并初始化,如下所示为已被初始化的i2c_driver。
static struct i2c_driver yyy_driver = {
.driver = {
.name = "yyy",
} ,
.probe = yyy_probe,
.remove = yyy_remove,
.id_table = yyy_id,
};
I2C数据传输
- I2C 总线是一个多主机的总线 这就是说可以连接多于一个能控制总线的器件到总线,由于主机通常是微控制器,因此数据传输过程通常为:
微控制器A寻址微控制器B -> 微控制器A(主机-发送器)发送数据到微控制器B(从机-接收器) -> 微控制器A停止传输
-
当有多个主机尝试发送消息到I2C总线,为了避免混乱,I2C总线将进行仲裁,当其他主机产生“0”,而首先产生“1”的主机将丢失仲裁,仲裁时产生的时钟信号是用线连接到SCL线的主机产生的时钟的同步结合。
-
I2C总线上产生的时钟信号通常是主机器件的责任,当在总线上传输数据时,每个主机产生自己的时钟信号,主机发出的总线时钟信号只有在以下的情况才能被改变:慢速的从机器件控制时钟线并延长时钟信号 或者在发生仲裁时被另一个主机改变。
-
SDA 和 SCL 都是双向线路,都通过一个电流源或上拉电阻连接到正的电源电压,当总线空闲时这两条线路都是高电平。
- SDA 线上的数据必须在时钟的高电平周期保持稳定,数据线的高或低电平状态只有在 SCL 线的时钟信号是低电平时才能改变。
- 在 I2C 总线中,唯一出现的是被定义为起始 S 和停止P 条件的情况,其中一种情况是在 SCL 线是高电平时,SDA 线从高电平向低电平切换 这个情况表示起始条件;当 SCL 是高电平时,SDA 线由低电平向高电平切换表示停止条件。起始和停止条件一般由主机产生,总线在起始条件后被认为处于忙的状态,在停止条件的某段时间后,总线被认为再次处于空闲状态
1、传输数据
- 字节格式:发送到 SDA 线上的每个字节必须为 8 位 每次传输可以发送的字节数量不受限制 每个字节后必须跟一个响应位,首先传输的是数据的最高位MSB,如果从机要完成一些其他功能后(例如一个内部中断服务程序 才能接收或发送下一个完整的数据字节)可以使时钟线 SCL 保持低电平迫使主机进入等待状态,当从机准备好接收下一个数据字节并释放时钟线 SCL 后,数据传输继续,在一些情况下 可以用与 I2C 总线格式不一样的格式(例如兼容 CBUS 的器件)甚至在传输一个字节时 用这样的地址起始的报文可以通过产生停止条件来终止,此时不会产生响应。
- 响应:
- 数据传输必须带响应,相关的响应时钟脉冲由主机产生,在响应的时钟脉冲期间,发送器释放 SDA 线(高)。在响应的时钟脉冲期间,接收器必须将 SDA 线拉低,使它在这个时钟脉冲的高电平期间保持稳定的低电平。
- 当从机不能响应从机地址时(或是因为无法再接收更多的字节时) 例如它正在执行一些实时函数不能接收或发送,从机需要使数据线维持高电平,主机然后产生一个停止条件终止传输或者产生重复条件开始新的传输。
- 若传输过程中,当主控器接收数据时接收到最后一个数据字节后,必须给被控器发送一个非应答位(高电平),从机发送器必须释放数据传输线,允许主机产生一个数据终止或重复起始条件。
2、仲裁和时钟发生
- 同步:SCL同步是由于总线具有线“与”的逻辑功能,即只要有一个节点发送低电平时,总线上就表现为低电平。 所有主机在 SCL 线上产生它们自己的时钟来传输 I2C 总线上的报文 数据只在时钟的高电平周期有效,因此需要一个确定的时钟进行逐位仲裁。SCL 线的高到低切换会使器件开始数它们的低电平周期 而且一旦器件的时钟变低电平 它会使 SCL 线保持这种状态直到到达时钟的高电平。但是 如果另一个时钟仍处于低电平周期 这个时钟的低到高切换不会改变 SCL 线的状态 因此SCL 线被有最长低电平周期的器件保持低电平,此时,低电平周期短的器件会进入高电平的等待状态。一言以蔽之,产生的同步 SCL 时钟的低电平周期由低电平时钟周期最长的器件决定 而高电平周期由高电平时钟周期最短的器件决定。
- 仲裁:仲裁可以持续多位 它的第一个阶段是比较地址位,如果每个主机都尝试寻址相同的器件,仲裁会继续比较数据位(如果是主机-发送器),或者比较响应位(如果是主机-接收器),因为 I2C 总线的地址和数据信息由赢得仲裁的主机决定 在仲裁过程中不会丢失信息,丢失仲裁的主机可以产生时钟脉冲直到丢失仲裁的该字节末尾。由于 Hs 模式的主机有一个唯一的 8 位主机码,因此一般在第一个字节就可以结束仲裁。如果主机也结合了从机功能,而且在寻址阶段丢失仲裁,它很可能就是赢得仲裁的主机在寻址的器件,因此丢失仲裁的主机必须立即切换到它的从机模式。
当主机1、2同时发送起始信号时,两个主机都发送了高电平信号。这时总线上呈现的信号为高电平,两个主机都检测到总线上的信号与自己发送的信号相同,继续发送数据。第2个时钟周期,2个主机都发送低电平信号,在总线上呈现的信号为低电平,仍继续发送数据。在第3个时钟周期,主机1发送高电平信号,而主机2发送低电平信号。根据总线的线“与”的逻辑功能,总线上的信号为低电平,这时主机1检测到总线上的数据和自己所发送的数据不一样,就断开数据的输出级,转为从机接收状态。
- SDA仲裁和SCL时钟同步处理过程没有先后关系,而是同时进行的。
3、数据传输的可能格式
- 主机-发送器传输数据到从机-接收器,传输方向不变。
- 第一个字节之后,主机立即读从机,主机-发送器变为主机-接收器,从机-接收器变为主机-发送器,第一次响应仍由从机产生,之前发生了一个非应答信号的主机产生停止条件。
- 复合传输:数据的传输方向取决于R/W标志位决定,重新改变读写方向时,需要再次发送起始条件。
4、7位寻址
- I2C 总线的寻址过程是通常在起始条件后的第一个字节决定了主机选择哪一个从机,例外的情况是可以寻址所有器件的“广播呼叫”地址,使用这个地址时理论上所有器件都会发出一个响应,但是也可以使器件忽略这个地址。
- 第一个字节的头 7 位组成了从机地址。最低位LSB 是第 8 位,它决定了报文的方向,第一个字节的最低位是 0,表示主机会写信息到被选中的从机,为1则表示主机会向从机读信息。
- 第一个字节位的含义见下表: