围城
分类: LINUX
2016-07-23 10:34:26
2. 低三位为引脚设定地址,可以由外部引脚来设定(并非所有器件都可以设定);
主控器向被控器发送的信息种类有:启动信号、停止信号、7位地址码、读/写控制位、10位地址码、数据字节、重启动信号、应答信号、时钟脉冲。
被控器向主控器发送的信息种类有:应答信号、数据字节、时钟低电平。
下面对I2C总线通信过程中出现的几种信号状态和时序进行分析。
①总线空闲状态。
I2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
②启动信号。
在时钟线SCL保持高电平期间,数据线SDA上的电平被拉低(即负跳变),定义为I2C总线总线的启动信号,它标志着一次数据传输的开始。
启动信号是一种电平跳变时序信号,而不是一个电平信号。启动信号是由主控器主动建立的,在建立该信号之前I2C总线必须处于空闲状态,如图1所示。
图1 I2C总线上的启动信号和停止信号
③停止信号。
在时钟线SCL保持高电平期间,数据线SDA被释放,使得SDA返回高电平(即正跳变),称为I2C总线的停止信号,它标志着一次数据传输的终止。
停止信号也是一种电平跳变时序信号,而不是一个电平信号,停止信号也是由主控器主动建立的,建立该信号之后,I2C总线将返回空闲状态。
④数据位传送。
在I2C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。
进行数据传送时,在SCL呈现高电平期间,SDA上的电平必须保持稳定,低电平为数据0,高电平为数据1。
只有在SCL为低电平期间,才允许SDA上的电平改变状态。逻辑0的电平为低电压,而逻辑1的电平取决于器件本身的正电压VDD(当使用独立电源时),如图2所示。
图2 I2C总线上的数据位传送
⑤应答信号。
I2C总线上的所有数据都是以8位字节传送的,发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。
应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。
如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P,如图3所示。
图3 I2C总线上的应答时序
⑥插入等待时间。
如果被控器需要延迟下一个数据字节开始传送的时间,则可以通过把时钟线SCL电平拉低并且保持,使主控器进入等待状态。
一旦被控器释放时钟线,数据传输就得以继续下去,这样就使得被控器得到足够时间转移已经收到的数据字节,或者准备好即将发送的数据字节。
带有CPU的被控器在对收到的地址字节做出应答之后,需要一定的时间去执行中断服务子程序,来分析或比较地址码,其间就把SCL线钳位在低电平上,直到处理妥当后才释放SCL线,进而使主控器继续后续数据字节的发送,如图4所示。
图4 I2C总线上的插入等待时间
⑦重启动信号。
在主控器控制总线期间完成了一次数据通信(发送或接收)之后,如果想继续占用总线再进行一次数据通信(发送或接收),而又不释放总线,就需要利用重启动Sr信号时序。
重启动信号Sr既作为前一次数据传输的结束,又作为后一次数据传输的开始。利用重启动信号的优点是,在前后两次通信之间主控器不需要释放总线,这样就不会丢失总线的控制权,即不让其他主器件节点抢占总线。
⑧时钟同步。
如果在某一I2C总线系统中存在两个主器件节点,分别记为主器件1和主器件2,其时钟输出端分别为CLK1和CL【0,它们都有控制总线的能力。
假设在某一期间两者相继向SCL线发出了波形不同的时钟脉冲序列CLK1和CLK2(时钟脉冲的高、低电平宽度都是依靠各自内部专用定时产生的),在总线控制权还没有裁定之前这种现象是可能出现的。
鉴于I2C总线的“线与”特性,使得时钟线SCL上得到的时钟信号波形,既不像主器件1所期望的CLK1,也不像主器件2所期望的CLK2,而是两者进行逻辑与的结果。
CLKI和CLK2的合成波形作为共同的同步时钟信号,一旦总线控制权裁定给某一主器件,则总线时钟信号将会只由该主器件产生,如图5所示。
图5 I2C总线上的时钟同步
⑨总线冲突和总线仲裁。
假如在某I2C总线系统中存在两个主器件节点,分别记为主器件1和主器件2,其数据输出端分别为DATA1和DATA2,它们都有控制总线的能力,这就存在着发生总线冲突(即写冲突)的可能性。
假设在某一瞬间两者相继向总线发出了启动信号,鉴于:I2C总线的“线与”特性,使得在数据线SDA上得到的信号波形是DATA1和DATA2两者相与的结果,该结果略微超前送出低电平的主器件1,其DATA1的下降沿被当做SDA的下降沿。
在总线被启动后,主器件1企图发送数据“101……”,主器件2企图发送数据“100101……”。
两个主器件在每次发出一个数据位的同时都要对自己输出端的信号电平进行抽检,只要抽检的结果与它们自己预期的电平相符,就会继续占用总线,总线控制权也就得不到裁定结果。
主器件1的第3位期望发送“1”,也就是在第3个时钟周期内送出高电平。
在该时钟周期的高电平期间,主器件1进行例行抽检时,结果检测到一个不相匹配的电平“0”,这时主器件1只好决定放弃总线控制杈;因此,主器件2就成了总线的惟一主宰者,总线控制权也就最终得出了裁定结果,从而实现了总线仲裁的功能。
从以上总线仲裁的完成过程可以得出:仲裁过程主器件1和主器件2都不会丢失数据;各个主器件没有优先级别之分,总线控制权是随机裁定的,即使是抢先发送启动信号的主器件1最终也并没有得到控制杈。
系统实际上遵循的是“低电平优先”的仲裁原则,将总线判给在数据线上先发送低电平的主器件,而其他发送高电平的主器件将失去总线控制权,如图6所示。
图6 I2C总线上的总线仲裁
⑩总线封锁状态。
在特殊情况下,如果需要禁止所有发生在I2C总线上的通信活动,封锁或关闭总线是一种可行途径,只要挂接于该总线上的任意一个器件将时钟线SCL锁定在低电平上即可。
I2C总线支持多主控模式,任何能够进行发送和接收的设备都可以成为主设备。主控能够控制数据的传输和时钟频率,在任意的时刻只能有一个主控。
组成I2C总线的两个信号为数据线SDA和时钟线SCL。为避免总信号线的混乱,要求各设备连接到总线的输出端必须是开漏输出或集电极开路输出的结构。根据这种结构的“线与”逻辑,I2C总线上任意器件输出低电平都会使相应总线上的信号线变低。
总线空闲时,上拉电阻使SDA和SCL线都保持高电平。
数据线 SDA 的电平状态必须在时钟线 SCL 处于高电平期间保持稳定不变。SDA 的电平状态只有在 SCL 处于低电平期间才允许改变。但是在 I2C总线的起始和结束时例外。
当SCL稳定在高电平时,SDA由高到低的变化将产生一个开始位,而由低到高的变化则产生一个停止位。开始位和停止位都是由I2C主设备产生的。如果从设备采用7位地址,则主设备在发起传输前,需先发送一字节的地址信息,前7位为设备地址,最后1位为读写标志。之后每次传输的数据也是一个字节,从MSB位(Most Significant Bit 最高有效位,对应有LSB: Least Significant Bit 最低有效位)开始传输。每个字节传完后,在SCL的第9个上升沿到来之前,接受方应该发出一个ACK位。
应答位的时钟脉冲仍由主机产生,而应答位的数据状态则遵循“谁接收谁产生”的原则,即总是由接收器产生应答位。主机向从机发送数据时,应答位由从机产生;主机从从机接收数据时,应答位由主机产生。I2C总线标准规定:应答位为 0 表示接收器应答(ACK) ,常常简记为 A;为 1 则表示非应答(NACK) ,常常简记为NA。发送器发送完 LSB 之后,应当释放 SDA 线(拉高 SDA,输出晶体管截止) ,以等待接收器产生应答位。
在切换数据的传输方向时,可以不必先产生停止条件再开始下次传输,而是直接再一次产生开始条件。I2C 总线在已经处于忙的状态下,再一次直接产生起始条件的情况被称为重复起始条件。例如:访问某一具有 I2C总线接口的 E2PROM 存储器时,主机先向存储器输入存储单元的地址信息(发送数据) ,然后再读取其中的存储内容(接收数据)。
带有 I2C 总线的器件除了有从机地址(Slave Address)外,还可能有子地址。从机地址是指该器件在 I2C 总线上被主机寻址的地址, 而子地址是指该器件内部不同部件或存储单元的编址。 与从机地址一样,子地址实际上也是像普通数据那样进行传输的,传输格式仍然是与数据相统一的,区分传输的到底是地址还是数据要靠收发双方具体的逻辑约定。子地址的长度必须由整数个字节组成,可能是单字节(8 位子地址) ,也可能是双字节(16 位子地址) ,还可能是 3 字节以上,这要看具体器件的规定。
I2C体系结构分3个部分:I2C核心、I2C总线驱动、I2C设备驱动。
I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法上层的、与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。
I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部。包含了i2c_adapter、i2c_algorithm和控制I2C适配器产生通信信号的函数。通过I2C总线驱动的代码,我们可以控制I2C适配器以主控方式产生开始位、停止位读写周期,以及以从设备方式被读写,产生ACK等。
I2C设备驱动是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。包含了i2c_driver和i2c_client。
所有的I2C设备都在sysfs文件系统中显示,存于/sys/bus/i2c/目录下,以适配器地址和芯片地址的形式列出。
i2c_adapter对应于物理上的一个适配器,而i2c_algorithm对应一套通信方法。一个I2C适配器需要i2c_algorithm提供的通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用的i2c_algorithm的指针。
i2c_driver对应一套驱动方法,是纯粹的用于辅助作用的数据结构,它不对应于任何的物理实体。i2c_client对应于真实的物理设备,每个I2C设备都需要一个i2c_client来描述。i2c_client一般被包含在I2C字符设备的私有信息结构体中。
i2c_driver于i2c_client发生关联的时刻在i2c_driver的attach_adapter()函数被运行时。attach_adapter()会探测物理设备,当确定一个client存在时,把该client使用的i2c_client数据结构的adapter指针指向对应的i2c_adapter,driver指针指向该i2c_driver,并会调用i2c_adapter的client_register()函数。相反的过程会发生在i2c_driver的detach_client()函数被调用的时候。
i2c_transfer()函数用于进行I2C适配器和I2C设备之间的一组消息交互,i2c_master_send()函数和i2c_master_recv()函数内部会调用i2c_transfer()函数分别完成一条写消息和一条读消息。i2c_transfer()函数本身并不具备驱动适配器物理硬件完成消息交互的能力,它只是寻找到i2c_adapter对应的i2c_algorithm,并使用i2c_algorithm的master_xfer函数真正驱动硬件流程。
I2C总线驱动模块加载函数需完成两个工作:
1.初始化I2C适配器所使用的硬件资源,如申请I/O地址、中断号等。
2.通过i2c_add_adapter()添加i2c_adapter的数据结构,当然这个i2c_adapter数据结构的成员已经被xxx适配器的相应函数指针所初始化。
I2C总线驱动模块的卸载函数要完成的工作与加载函数相反。
I2C总线通信方法:我们要为特定的I2C适配器实现其通信方法,主要实现i2c_algorithm的master_xfer()函数和functionality()函数。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消息。
master_xfer()函数处理I2C消息数组,对于数组中的每个消息,判断消息类型,若为读消息,则赋从设备地址为(msg->addr<<1)| 1 ,否则为msg->addr<<1(赋地址为setaddr)。对每个消息产生一个开始位,紧接着传送从设备地址,然后开始数据的发送或接收,对最后的消息还需产生一个停止位。
I2C设备驱动(i2c_driver和i2c_client)。
I2C设备驱动要使用i2c_driver和i2c_client数据结构并填充其中的成员函数。i2c_client一般被包含在设备的私有信息结构体yyy_data中,而i2c_driver则适合被定义成全局变量并初始化。
I2C设备驱动的模块加载函数中会做:
(1)通过register_chrdev()函数将I2C设备注册为一个字符设备。
(2)通过I2C核心的i2c_add_driver()函数添加i2c_driver。
模板卸载函数中则相反。
yyy_init()->i2c_add_driver()->yyy_attach_adapter()->i2c_probe()->yyy_detect()->i2c_client初始化->i2c_attach_client()->yyy_init_client
yyy_exit()->i2c_del_driver()->yyy_detach_client()->i2c_detach_client()
作为一种字符类设备,Linux I2C设备驱动的文件操作接口与普通的设备驱动是完全一致的。I2C设备的写操作经历了如下几个步骤:
(1)从用户空间到字符设备驱动写函数接口,写函数构造I2C消息数组。
(2)写函数把构造的I2C消息数组传递给I2C核心的传输函数i2c_transfer()。
(3)I2C核心的传输函数i2c_transfer()找到对应适配器algorithm的通信方法函数master_xfer()去最终完成I2C消息的处理。
read()/write()(用户空间)->yyy_read()/yyy_write()(I2C设备驱动文件操作接口)->i2c_transfer()(I2C核心)->master_xfer()(I2C总线驱动)
i2c-dev.c文件完全可以被看做一个I2C设备驱动,不过,它实现的一个i2c_client是虚拟、临时的,随着设备文件的打开而产生,并随设备文件的关闭而撤销,并没有被添加到i2c_adapter的clients链表中。i2c-dev.c针对每个I2C适配器生成一个主设备号为89的设备文件,实现了i2c_driver的成员函数以及文件操作接口,所以,i2c-dev.c的主体式“i2c_driver成员函数+字符设备驱动”。
i2c-dev.c中提供i2cdev_read()、i2cdev_write()函数来对应用户空间要使用的read()和write()文件操作接口,这两个函数分别调用I2C核心的i2c_master_recv()和i2c_master_send()函数来构造一条I2C消息并引发适配器algorithm通信函数的调用,完成消息的传输。i2cdev_read()和i2cdev_write()函数不具备太强的通用性,灭有太大的使用价值,只能适用于非RepStart模式的情况。对于两条以上消息组成的读写,在用户空间需要组织i2c_msg消息数组并调用I2C_RDWR IOCTL命令。