I2C硬件原理及驱动

      I2C总线是由Philips公司开发的两线式串行总线,用于连接微控制器和外围设备。
      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命令。          
 

你可能感兴趣的:(内核相关)