一、IIC 总线概述:

IIC 即Inter-Integrated Circuit(集成电路总线)
I2C总线是PHLIPS公司推出的一种串行总线, I2C总线只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL。

每个接到I2C总线上的器件都有唯一的地址。主机与其它器件间的数据传送可以是由主机发送数据到其它器件,这时主机即为发送器。由总线上接收数据的器件则为接收器。

二、IIC 总线通信协议:

要掌握IIC的通信协议,需要掌握以下6个通信信号:
1.起始信号
2.终止信号
3.写数据
4.读数据
5.应答信号
6.非应答信号

  • 起始和终止信号
    SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。

  • 应答信号
    IIC 总线协议规定,每传送一个字节数据后,都要有一个应答信号以确定数据传送是否被对方收到。应答信号由接受设备产生,在SCL为高电平期间,接受设备将SDA拉低为低电平,表示数据传输正确,产生应答(ACK)

  • 数据传送
    I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。

根据AT24C02的芯片,可编写以下信号函数程序:

//1.起始信号 SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;
void IIC_Start(void)
{SDA = 1;SCL = 1; delay_us(1); //15us >> 4.7us SDA = 0; delay_us(1); SCL = 0; } //2.终止信号 SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。 void IIC_Stop(void) { SDA = 0; SCL = 1; delay_us(1); //15us >> 4.7us SDA = 1; delay_us(1); SCL = 0; } void IIC_SendByte(unsigned char dat) //3.写数据 { unsigned char i; for (i = 0; i < 8; i++) { if((dat<<i)&0x80) { SDA = 1; } else { SDA = 0; } SCL = 1; //开始让数据维持稳定 delay_us(1); SCL = 0; delay_us(1); } SDA = 1; //释放总线 , 发送完8位,主机置高电平 SCL = 1; delay_us(1); if (SDA) //SDA 低电平 从机回馈低电平 { ack = 0; //0 == ack 代表无ack信号, 从机不应答,发送不成功 } else { ack = 1; //从机应答,发送成功 } SCL = 0; delay_us(5); } unsigned char IIC_RecvByte(void) //4. 读数据 { unsigned char i, temp; SDA = 1; //保险 高的 与 上低的 是低的, 线与 for (i = 0; i < 8; i++) { SCL = 0; // 告诉 数据可以变化 SDA 脉冲线 //只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。 delay_us(1); SCL = 1; // 数据保持稳定, 开始读 delay_us(1); temp<<=1; if (SDA) { temp = temp + 1; } } SCL = 0; delay_us(10); return temp; } void IIC_ACK(void) //5. 应答信号 { SDA = 0; SCL = 1; delay_us(1); SCL = 0; } void IIC_NOACK(void) //6. 非应答信号 { SDA = 1; SCL = 1; delay_us(1); SCL = 0; }

根据时序图,可编写24C02的读写函数程序:

unsigned char AT24CXX_WriteStr(unsigned char devaddr, unsigned char romaddr, unsigned char *s, unsigned char num) { unsigned char i; IIC_Start(); IIC_SendByte(devaddr); if (0 == ack) { return 0; } IIC_SendByte(romaddr); if (0 == ack) { return 0; } for (i = 0; i < num; i++) { IIC_SendByte(*s); if (0 == ack) { return 0; } s++; } IIC_Stop(); return 1; }

unsigned char AT24CXX_ReadStr(unsigned char devaddr, unsigned char romaddr, unsigned char *s, unsigned char num) { unsigned char i; IIC_Start(); IIC_SendByte(devaddr); if (0 == ack) //无应答 返回0 失败 { return 0; } IIC_SendByte(romaddr); if (0 == ack) { return 0; } IIC_Start(); IIC_SendByte(devaddr + 1); if (0 == ack) { return 0; } for (i = 0; i < num - 1; i++) { *s = IIC_RecvByte(); IIC_ACK(); s++; } *s = IIC_RecvByte(); IIC_NOACK(); IIC_Stop(); return 1; }

三、有关 IIC 总线常见面试题:(参考)

  • 介绍一下你了解的I2C?

I2C总线是飞利浦(PHLIPS)公司推出的一种串行总线,用于连接微控制器及其外围设备, I2C串行总线有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL。 它仅通过两根信号线就可以完成对所有挂载在I2C总线上的从器件进行操作。这样的好处是可以大大的节省我们微处理器的IO口资源。

  • I2C到底可以挂载多少个器件呢?

答:IIC协议规定,在启动总线后第1字节的高7位是从节点的寻址地址,其中高四位为器件类型识别符,接着三位为片选,最后一位为读写位,当为1时为读操作,为0时为写操作,所以具体挂载多少个器件由I2C地址决定,7位寻址地址减去1个广播地址0x00不用,所以有2^7=128 - 1 = 127,那就是127个地址, 所以理论上可以挂127个从器件。

  • I2C如何同时挂载多个同一种器件(地址相同的器件)?

答:理论上是不会这样设计的,如果一定要这样做的话,可以通过硬件上设计,控制器件是否挂载总线来实现(方法可用一个开关电路切断器件SDA或者SCL是否接入总线来实现)

  • I2C总线的主机与从机之间是如何通信的呢?

I2C总线的主机与从机之间的通信主要和I2C的时序有关。在通信开始的时候SCL与SDA都置为高电平,此时为总线空闲时间。当SCL为高电平期间SDA的电平被拉低,标志这总线的启动。当SCL为高电平期间SDA的电平被拉高,标志这总线的终止。在进行数据传送时,SCL为高电平期间,SDA上的数据必须保持稳定,只有在SCL的信号为低电平时,SDA上的高电平才允许变化。所以只要我们根据芯片手册正确的写好IIC的时序,按时序发送器件地址(不同的器件的地址不同)以及数据,就可以使主机与从机之间通信。

  • I2C总线的仲裁你知道吗?

总线上可能挂接有多个器件,有时会发生两个或多个主器件同时想占用总线的情况,这种情况叫做总线竞争。I2C总线具有多主控能力,可以对发生在SDA线上的总线竞争进行仲裁,其仲裁原则是这样的:当多个主器件同时想占用总线时,如果某个主器件发送高电平,而另一个主器件发送低电平,则发送电平与此时SDA总线电平不符的那个器件将自动关闭其输出级。总线竞争的仲裁是在两个层次上进行的。首先是地址位的比较,如果主器件寻址同一个从器件,则进入数据位的比较,从而确保了竞争仲裁的可靠性。由于是利用I2C总线上的信息进行仲裁,因此不会造成信息的丢失。

  • I2C时钟信号(SCL)的同步问题

在I2C总线上传送信息时的时钟同步信号是由挂接在SCL线上的所有器件的逻辑“与”完成的。SCL线上由高电平到低电平的跳变将影响到这些器件,一旦某个器件的时钟信号下跳为低电平,将使SCL线一直保持低电平,使SCL线上的所有器件开始低电平期。此时,低电平周期短的器件的时钟由低至高的跳变并不能影响SCL线的状态,于是这些器件将进入高电平等待的状态。当所有器件的时钟信号都上跳为高电平时,低电平期结束,SCL线被释放返回高电平,即所有的器件都同时开始它们的高电平期。其后,第一个结束高电平期的器件又将SCL线拉成低电平。这样就在SCL线上产生一个同步时钟。可见,时钟低电平时间由时钟低电平期最长的器件确定,而时钟高电平时间由时钟高电平期最短的器件确定。

  • I2C总线的其他注意点

1、进行数据传送时,在SCL为高电平期间,SDA线上电平必须保持稳定,只有SCL为低时,才允许SDA线上电平改变状态。并且每个字节传送时都是高位在前。
2、对于应答信号,ACK=0时为有效应答位,说明从机已经成功接收到该字节,若为1则说明接受不成功。
3、如果从机需要延迟下一个数据字节开始传送的时间,可以通过把SCL电平拉低并保持来强制主机进入等待状态。
4、主机完成一次通信后还想继续占用总线在进行一次通信,而又不释放总线,就要利用重启动信号Sr。它既作为前一次数据传输的结束,又作为后一次传输的开始。
5、总线冲突时,按“低电平优先”的仲裁原则,把总线判给在数据线上先发送低电平的主器件。
6、在特殊情况下,若需禁止所有发生在I2C总线上的通信,可采用封锁或关闭总线,具体操作为在总线上的任一器件将SCL锁定在低电平即可。
7、SDA仲裁和SCL时钟同步处理过程没有先后关系,而是同时进行的。

I2C的基础概念和框架 

一、IIC 基础概念

IIC(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。IIC总线产生于在80年代,最初为音频和视频设备开发,如今主要在服务器管理中使用,其中包括单个组件状态的通信。例如管理员可对各个组件进行查询,以管理系统的配置或掌握组件的功能状态,如电源和系统风扇。可随时监控内存、硬盘、网络、系统温度等多个参数,增加了系统的安全性,方便了管理。


1、 IIC总线的特点

IIC总线最主要的优点是其简单性有效性。由于接口直接在组件之上,因此IIC总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。总线的长度可高达25英尺,并且能够以10Kbps的最大传输速率支持40个组件。IIC总线的另一个优点是,它支持多主控(multimastering), 其中任何能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。

2、IIC总线工作原理

a -- 总线构成

IIC总线是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送,最高传送速率100kbps。各种被控制电路均并联在这条总线上,但就像电话机一样只有拨通各自的号码才能工作,所以每个电路和模块都有唯一的地址,在信息的传输过程中,IIC总线上并接的每一模块电路既是主控器(或被控器),又是发送器(或接收器),这取决于它所要完成的功能。

CPU发出的控制信号分为地址码控制量两部分:

1) 地址码用来选址,即接通需要控制的电路,确定控制的种类;

2) 控制量决定该调整的类别(如对比度、亮度等)及需要调整的量。

这样,各控制电路虽然挂在同一条总线上,却彼此独立,互不相关。

b -- 信号类型

IIC总线在传送数据过程中共有四种类型信号:

开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据;

结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据;

数据传输信号:在开始条件以后,时钟信号SCL的高电平周期期问,当数据线稳定时,数据线SDA的状态表示数据有效,即数据可以被读走,开始进行读操作。在时钟信号SCL的低电平周期期间,数据线上数据才允许改变。每位数据需要一个时钟脉冲。

应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。

目前有很多半导体集成电路上都集成了IIC接口。带有IIC接口的单片机有:CYGNAL的 C8051F0XX系列,PHILIPSP87LPC7XX系列,MICROCHIP的PIC16C6XX系列等。很多外围器件如存储器、监控芯片等也提供IIC接口。

3、总线基本操作

IIC规程运用主/从双向通讯。器件发送数据到总线上,则定义为发送器,器件接收数据则定义为接收器。主器件和从器件都可以工作于接收和发送状态。 总线必须由主器件(通常为微控制器)控制,主器件产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。SDA线上的数据状态仅在SCL为低电平的期间才能改变,SCL为高电平的期间,SDA状态的改变被用来表示起始和停止条件。

a -- 控制字节

在起始条件之后,必须是器件的控制字节,其中高四位为器件类型识别符(不同的芯片类型有不同的定义,EEPROM一般应为1010),接着三位为片选,最后一位为读写位,当为1时为读操作,为0时为写操作。

b -- 写操作

写操作分为字节写和页面写两种操作,对于页面写根据芯片的一次装载的字节不同有所不同。关于页面写的地址、应答和数据传送的时序。

c -- 读操作

读操作有三种基本操作:当前地址读、随机读和顺序读。图4给出的是顺序读的时序图。应当注意的是:最后一个读操作的第9个时钟周期不是“不关心”。为了结束读操作,主机必须在第9个周期间发出停止条件或者在第9个时钟周期内保持SDA为高电平、然后发出停止条件。

d -- 总线仲裁

主机只能在总线空闲的时候启动传输。两个或多个主机可能在起始条件的最小持续内产生一个起始条件,结果在总线上产生一个规定的起始条件。

当SCL线是高电平时,仲裁在SDA线发生:这样,在其他主机发送低电平时,发送高电平的主机将断开它的数据输出级,因为总线上的电平和它自己的电平不同。

仲裁可以持续多位。从地址位开始,同一个器件的话接着就是数据位(如果主机-发送器),或者比较相应位(如果主机-接收器)。IIC总线的地址和数据信息由赢得仲裁的主机决定,在这个过程中不会丢失信息。

仲裁不能在下面情况之间进行:

1)重复起始条件和数据位;
2)停止条件和数据位;
3)重复起始条件和停止条件。


4、特性总结

IIC肯定是2线的(不算地线)IIC协议确实很科学,比3/4线的SPI要好,当然线多通讯速率相对就快了

IIC的原则是

a -- 在SCL=1(高电平)时,SDA千万别忽悠!!!否则,SDA下跳则"判罚"为"起始信号S",SDA上跳则"判罚"为"停止信号P".

b -- 在SCL=0(低电平)时,SDA随便忽悠!!!(可别忽悠过火到SCL跳高)

c -- 每个字节后应该由对方回送一个应答信号ACK做为对方在线的标志.非应答信号一般在所有字节的最后一个字节后.一般要由双方协议签定.

d -- SCL必须由主机发送,否则天下大乱

e -- 首字节是"片选信号",即7位从机地址加1位方向(读写)控制.从机收到(听到)自己的地址才能发送应答信号(必须应答!!!)表示自己在线.其他地址的从机不允许忽悠!!!(当然群呼可以忽悠但只能听不许说话)

f -- 读写是站在主机的立场上定义的."读"是主机接收从机数据,"写"是主机发送数据给从机.

g-- 重复位主要用于主机从发送模式到接收模式的转换"信号",由于只有2线,所以收发转换肯定要比SPI复杂,因为SPI可用不同的边沿来收发数据,而IIC不行.

h -- 在硬件IIC模块,特别是MCU/ARM/DSP等每个阶段都会得到一个准确的状态码,根据这个状态码可以很容易知道现在在什么状态和什么出错信息.

i -- 7位IIC总线可以挂接127个不同地址的IIC设备,0号"设备"作为群呼地址.10位IIC总线可以挂接更多的10位IIC设备.

二、 Linux下IIC驱动架构

Linux定义了系统的IIC驱动体系结构,在Linux系统中,IIC驱动由3部分组成,即IIC核心IIC总线驱动IIC设备驱动。这3部分相互协作,形成了非常通用、可适应性很强的IIC框架。

上图完整的描述了linux i2c驱动架构,虽然I2C硬件体系结构比较简单,但是i2c体系结构在linux中的实现却相当复杂。

那么我们如何编写特定i2c接口器件的驱动程序?就是说上述架构中的那些部分需要我们完成,而哪些是linux内核已经完善的或者是芯片提供商已经提供的?

1、架构层次分类

   第一层:提供i2c adapter的硬件驱动,探测、初始化i2c adapter(如申请i2c的io地址和中断号),驱动soc控制的i2c adapter在硬件上产生信号(start、stop、ack)以及处理i2c中断。覆盖图中的硬件实现层

  第二层:提供i2c adapter的algorithm,用具体适配器的xxx_xferf()函数来填充i2c_algorithm的master_xfer函数指针,并把赋值后的i2c_algorithm再赋值给i2c_adapter的algo指针。覆盖图中的访问抽象层、i2c核心层

   第三层:实现i2c设备驱动中的i2c_driver接口,用具体的i2c device设备的attach_adapter()、detach_adapter()方法赋值给i2c_driver的成员函数指针。实现设备device与总线(或者叫adapter)的挂接覆盖图中的driver驱动层

  第四层:实现i2c设备所对应的具体device的驱动,i2c_driver只是实现设备与总线的挂接,而挂接在总线上的设备则是千差万别的,所以要实现具体设备device的write()、read()、ioctl()等方法,赋值给file_operations,然后注册字符设备(多数是字符设备)。覆盖图中的driver驱动层

 

     第一层和第二层又叫i2c总线驱动(bus),第三第四属于i2c设备驱动(device driver)

在linux驱动架构中,几乎不需要驱动开发人员再添加bus,因为linux内核几乎集成所有总线bus,如usb、pci、i2c等等。并且总线bus中的(与特定硬件相关的代码)已由芯片提供商编写完成,例如三星的s3c-2440平台i2c总线bus为/drivers/i2c/buses/i2c-s3c2410.c

第三第四层与特定device相干的就需要驱动工程师来实现了。

2、Linux下I2C驱动体系结构三部分详细分析

a -- IIC核心

IIC 核心提供了IIC总线驱动和设备驱动的注册、注销方法,IIC通信方法(即“algorithm”,笔者认为直译为“运算方法”并不合适,为免引起误解, 下文将直接使用“algorithm”)上层的、与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。

在我们的Linux驱动的i2c文件夹下有algos,busses,chips三个文件夹,另外还有i2c-core.ci2c-dev.c两个文件。

i2c-core.c文件实现了I2Ccore框架,是Linux内核用来维护和管理的I2C的核心部分,其中维护了两个静态的List,分别记录系统中的I2Cdriver结构和I2Cadapter结构。I2Ccore提供接口函数,允许一个I2Cadatper,I2Cdriver和I2Cclient初始化时在I2Ccore中进行注册,以及退出时进行注销。同时还提供了I2C总线读写访问的一般接口,主要应用在I2C设备驱动中。

 

b -- IIC总线驱动

IIC总线驱动是对IIC硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至直接集成在CPU内部。总线驱动的职责,是为系统中每个I2C总线增加相应的读写方法。但是总线驱动本身并不会进行任何的通讯,它只是存在那里,等待设备驱动调用其函数。

IIC总线驱动主要包含了IIC适配器数据结构i2c_adapterIIC适配器的algorithm数据结构i2c_algorithm控制IIC适配器产生通信信号的函数。经由IIC总线驱动的代码,我们可以控制IIC适配器以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。

 Busses文件夹下的i2c-mpc.c文件实现了PowerPC下I2C总线适配器驱动,定义描述了具体的I2C总线适配器的i2c_adapter数据结构,实现比较底层的对I2C总线访问的具体方法。I2Cadapter 构造一个对I2Ccore层接口的数据结构,并通过接口函数向I2Ccore注册一个控制器。I2Cadapter主要实现对I2C总线访问的算法,iic_xfer() 函数就是I2Cadapter底层对I2C总线读写方法的实现。同时I2Cadpter 中还实现了对I2C控制器中断的处理函数。

 

c -- IIC设备驱动

IIC设备驱动是对IIC硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的IIC适配器上,通过IIC适配器与CPU交换数据。设备驱动则是与挂在I2C总线上的具体的设备通讯的驱动。通过I2C总线驱动提供的函数,设备驱动可以忽略不同总线控制器的差异,不考虑其实现细节地与硬件设备通讯。

IIC设备驱动主要包含了数据结构i2c_driver和i2c_client,我们需要根据具体设备实现其中的成员函数。

i2c-dev.c文件中实现了I2Cdriver,提供了一个通用的I2C设备的驱动程序,实现了字符类型设备的访问接口,实现了对用户应用层的接口,提供用户程序访问I2C设备的接口,包括实现open,release,read,write以及最重要的ioctl等标准文件操作的接口函数。我们可以通过open函数打开 I2C的设备文件,通过ioctl函数设定要访问从设备的地址,然后就可以通过 read和write函数完成对I2C设备的读写操作。

通过I2Cdriver提供的通用方法可以访问任何一个I2C的设备,但是其中实现的read,write及ioctl等功能完全是基于一般设备的实现,所有的操作数据都是基于字节流,没有明确的格式和意义。为了更方便和有效地使用I2C设备,我们可以为一个具体的I2C设备开发特定的I2C设备驱动程序,在驱动中完成对特定的数据格式的解释以及实现一些专用的功能。

3、重要的结构体

因为IIC设备种类太多,如果每一个IIC设备写一个驱动程序,那么显得内核非常大。不符合软件工程代码复用,所以对其层次话:

这里简单的将IIC设备驱动分为设备层总线层。理解这两个层次的重点是理解4个数据结构,这4个数据结构是i2c_driver、i2c_client、i2c_algorithm、i2c_adapter。i2c_driver、i2c_client属于设备层;i2c_algorithm、i2c_adapter属于总线型。如下图:

设备层关系到实际的IIC设备,总线层包括CPU中的IIC总线控制器和控制总线通信的方法。值得注意的是:一个系统中可能有很多个总线层,也就是包含多个总线控制器;也可能有多个设备层,包含不同的IIC设备

        由IIC总线规范可知,IIC总线由两条物理线路组成,这两条物理线路是SDA和SCL。只要连接到SDA和SCL总线上的设备都可以叫做IIC设备。

a -- i2c_client 

一个IIC设备由i2c_client数据结构进行描述:

[cpp] view plain copy
  1. struct  i2c_client
  2. {
  3. unsigned short  flags;                          //标志位
  4. unsigned short  addr;                //设备的地址,低7位为芯片地址
  5. char name[I2C_NAME_SIZE];             //设备的名称,最大为20个字节
  6. struct  i2c_adapter *adapter;           //依附的适配器i2c_adapter,适配器指明所属的总线
  7. struct  i2c_driver *driver;             //指向设备对应的驱动程序
  8. struct device  dev;                 //设备结构体
  9. int irq;                       //设备申请的中断号
  10. struct list_head  list;                //连接到总线上的所有设备
  11. struct list_head   detected;           //已经被发现的设备链表
  12. struct completion  released;           //是否已经释放的完成量
  13. };

设备结构体i2c_client中addr的低8位表示设备地址。设备地址由读写位、器件类型和自定义地址组成,如下图:

第7位是R/W位,0表示写,2表示读,所以I2C设备通常有两个地址,即读地址和写地址;

类型器件由中间4位组成,这是由半导体公司生产的时候就已经固化了;

自定义类型由低3位组成。由用户自己设置;

IIC设备还有一些重要的注意事项:

1、i2c_client数据结构是描述IIC设备的“模板”,驱动程序的设备结构中应包含该结构;

2、adapter指向设备连接的总线适配器,系统可能有多个总线适配器。内核中静态指针数组adapters记录所有已经注册的总线适配器设备;

3、driver是指向设备驱动程序,这个驱动程序是在系统检测到设备存在时赋值的;

b -- IIC设备驱动     i2c_driver

[cpp] view plain copy
  1. struct  i2c_driver
  2. {
  3. int id;                         //驱动标识ID
  4. unsigned int class;               //驱动的类型
  5. int (*attach_adapter)(struct i2c_adapter *);             //当检测到适配器时调用的函数
  6. int (*detach_adapter)(struct i2c_adapter*);              //卸载适配器时调用的函数
  7. int (*detach_client)(struct i2c_client *)   __deprecated;             //卸载设备时调用的函数
  8. //以下是一种新类型驱动需要的函数,这些函数支持IIC设备动态插入和拔出。如果不想支持只实现上面3个。要不实现上面3个。要么实现下面5个。不能同时定义
  9. int  (*probe)(struct i2c_client *,const struct  i2c_device_id *);              //新类型设备探测函数
  10. int (*remove)(struct i2c_client *);                   //新类型设备的移除函数
  11. void (*shutdown)(struct i2c_client *);              //关闭IIC设备
  12. int (*suspend)(struct  i2c_client *,pm_messge_t mesg);           //挂起IIC设备
  13. int (*resume)(struct  i2c_client *);                               //恢复IIC设备
  14. int (*command)(struct i2c_client *client,unsigned int cmd,void *arg);        //使用命令使设备完成特殊的功能。类似ioctl()函数
  15. struct devcie_driver  driver;                         //设备驱动结构体
  16. const struct  i2c_device_id *id_table;                       //设备ID表
  17. int (*detect)(struct i2c_client *,int  kind,struct  i2c_board_info *);          //自动探测设备的回调函数
  18. const  struct i2c_client_address_data          *address_data;                 //设备所在的地址范围
  19. struct  list_head    clients;                    //指向驱动支持的设备
  20. };

结构体i2c_driver和i2c_client的关系较为简单,其中i2c_driver表示一个IIC设备驱动,i2c_client表示一个IIC设备。关系如下图:

c -- i2c_adapter

IIC总线适配器就是一个IIC总线控制器,在物理上连接若干个IIC设备。IIC总线适配器本质上是一个物理设备,其主要功能是完成IIC总线控制器相关的数据通信:

[cpp] view plain copy
  1. struct i2c_adapter
  2. {
  3. struct module *owner;                        //模块计数
  4. unsigned  int id;                                  //alogorithm的类型,定义于i2c_id.h中
  5. unsigned   int  class;                           //允许探测的驱动类型
  6. const struct i2c_algorithm *algo;         //指向适配器的驱动程序
  7. void *algo_data;                                  //指向适配器的私有数据,根据不同的情况使用方法不同
  8. int (*client_register)(struct  i2c_client *);          //设备client注册时调用
  9. int (*client_unregister(struct  i2c_client *);       //设备client注销时调用
  10. u8 level;
  11. struct  mutex  bus_lock;                             //对总线进行操作时,将获得总线锁
  12. struct  mutex  clist_lock ;                            //链表操作的互斥锁
  13. int timeout;                                                  //超时
  14. int retries;                                                     //重试次数
  15. struct device dev;                                          //指向 适配器的设备结构体
  16. int  nr ;
  17. struct  list_head      clients;                            //连接总线上的设备的链表
  18. char name[48];                                              //适配器名称
  19. struct completion     dev_released;               //用于同步的完成量
  20. };

d -- i2c_algorithm

每一个适配器对应一个驱动程序,该驱动程序描述了适配器与设备之间的通信方法:

[cpp] view plain copy
  1. struct  i2c_algorithm
  2. {
  3. int  (*master_xfer)(struct  i2c_adapter *adap,  struct  i2c_msg *msg, int num);              //传输函数指针,指向实现IIC总线通信协议的函数,用来确定适配器支持那些传输类型
  4. int  (*smbus_xfer)(struct  i2c_adapter *adap, u16  addr, unsigned  short flags, char  read_write, u8 command, int size, union  i2c_smbus_data  *data);    //smbus方式传输函数指针,指向实现SMBus总线通信协议的函数。SMBus和IIC之间可以通过软件方式兼容,所以这里提供了一个函数,但是一般都赋值为NULL
  5. u32  (*functionality)(struct  i2c_adapter *);                   //返回适配器支持的功能
  6. };

IIC设备驱动程序大致可以分为设备层和总线层。设备层包括一个重要的数据结构,i2c_client。总线层包括两个重要的数据结构,分别是i2c_adapter和i2c_algorithm。一个i2c_algorithm结构表示适配器对应的传输数据方法。3个数据结构关系:

IIC设备层次结构较为简单,但是写IIC设备驱动程序却相当复杂。

IIC设备驱动程序的步骤:

4、各结构体的作用与它们之间的关系

a -- i2c_adapter与i2c_algorithm

  i2c_adapter对应与物理上的一个适配器,而i2c_algorithm对应一套通信方法,一个i2c适配器需要i2c_algorithm中提供的(i2c_algorithm中的又是更下层与硬件相关的代码提供)通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指针。

i2c_algorithm中的关键函数master_xfer()用于产生i2c访问周期需要的start stop ack信号,以i2c_msg(即i2c消息)为单位发送和接收通信数据。

  i2c_msg也非常关键,调用驱动中的发送接收函数需要填充该结构体

[cpp] view plain copy
  1. struct i2c_msg {
  2. __u16 addr; /* slave address            */
  3. __u16 flags;
  4. __u16 len;      /* msg length               */
  5. __u8 *buf;      /* pointer to msg data          */
  6. };

b --i2c_driver和i2c_client

i2c_driver对应一套驱动方法,其主要函数是attach_adapter()和detach_client()

i2c_client对应真实的i2c物理设备device,每个i2c设备都需要一个i2c_client来描述

2c_driver与i2c_client的关系是一对多。一个i2c_driver上可以支持多个同等类型的i2c_client.

c -- i2c_adapter和i2c_client

  i2c_adapter和i2c_client的关系与i2c硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adapter,由于一个适配器上可以连接多个i2c设备,所以i2c_adapter中包含依附于它的i2c_client的链表。

 从i2c驱动架构图中可以看出,linux内核对i2c架构抽象了一个叫核心层core的中间件,它分离了设备驱动device driver和硬件控制的实现细节(如操作i2c的寄存器),core层不但为上面的设备驱动提供封装后的内核注册函数,而且还为小面的硬件事件提供注册接口(也就是i2c总线注册接口),可以说core层起到了承上启下的作用。

面试你该如何回答---->IIC总线协议?

面试的时候主要会遇到的IIC问题如下

  • 介绍一下你了解的I2C?

I2C总线是飞利浦(PHLIPS)公司推出的一种串行总线,用于连接微控制器及其外围设备, I2C串行总线有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL。 它仅通过两根信号线就可以完成对所有挂载在I2C总线上的从器件进行操作。这样的好处是可以大大的节省我们微处理器的IO口资源。

  • I2C到底可以挂载多少个器件呢?

答:IIC协议规定,在启动总线后第1字节的高7位是从节点的寻址地址,其中高四位为器件类型识别符,接着三位为片选,最后一位为读写位,当为1时为读操作,为0时为写操作,所以具体挂载多少个器件由I2C地址决定,7位寻址地址减去1个广播地址0x00不用,所以有2^7=128 - 1 = 127,那就是127个地址, 所以理论上可以挂127个从器件。

  • I2C如何同时挂载多个同一种器件(地址相同的器件)?

答:理论上是不会这样设计的,如果一定要这样做的话,可以通过硬件上设计,控制器件是否挂载总线来实现(方法可用一个开关电路切断器件SDA或者SCL是否接入总线来实现)

  • I2C总线的主机与从机之间是如何通信的呢?

I2C总线的主机与从机之间的通信主要和I2C的时序有关。在通信开始的时候SCL与SDA都置为高电平,此时为总线空闲时间。当SCL为高电平期间SDA的电平被拉低,标志这总线的启动。当SCL为高电平期间SDA的电平被拉高,标志这总线的终止。在进行数据传送时,SCL为高电平期间,SDA上的数据必须保持稳定,只有在SCL的信号为低电平时,SDA上的高电平才允许变化。所以只要我们根据芯片手册正确的写好IIC的时序,按时序发送器件地址(不同的器件的地址不同)以及数据,就可以使主机与从机之间通信。

  • I2C总线的仲裁你知道吗?

总线上可能挂接有多个器件,有时会发生两个或多个主器件同时想占用总线的情况,这种情况叫做总线竞争。I2C总线具有多主控能力,可以对发生在SDA线上的总线竞争进行仲裁,其仲裁原则是这样的:当多个主器件同时想占用总线时,如果某个主器件发送高电平,而另一个主器件发送低电平,则发送电平与此时SDA总线电平不符的那个器件将自动关闭其输出级。总线竞争的仲裁是在两个层次上进行的。首先是地址位的比较,如果主器件寻址同一个从器件,则进入数据位的比较,从而确保了竞争仲裁的可靠性。由于是利用I2C总线上的信息进行仲裁,因此不会造成信息的丢失。

  • I2C时钟信号(SCL)的同步问题

在I2C总线上传送信息时的时钟同步信号是由挂接在SCL线上的所有器件的逻辑“与”完成的。SCL线上由高电平到低电平的跳变将影响到这些器件,一旦某个器件的时钟信号下跳为低电平,将使SCL线一直保持低电平,使SCL线上的所有器件开始低电平期。此时,低电平周期短的器件的时钟由低至高的跳变并不能影响SCL线的状态,于是这些器件将进入高电平等待的状态。当所有器件的时钟信号都上跳为高电平时,低电平期结束,SCL线被释放返回高电平,即所有的器件都同时开始它们的高电平期。其后,第一个结束高电平期的器件又将SCL线拉成低电平。这样就在SCL线上产生一个同步时钟。可见,时钟低电平时间由时钟低电平期最长的器件确定,而时钟高电平时间由时钟高电平期最短的器件确定。

  • I2C总线的其他注意点

1、进行数据传送时,在SCL为高电平期间,SDA线上电平必须保持稳定,只有SCL为低时,才允许SDA线上电平改变状态。并且每个字节传送时都是高位在前。


2、对于应答信号,ACK=0时为有效应答位,说明从机已经成功接收到该字节,若为1则说明接受不成功。


3、如果从机需要延迟下一个数据字节开始传送的时间,可以通过把SCL电平拉低并保持来强制主机进入等待状态。


4、主机完成一次通信后还想继续占用总线在进行一次通信,而又不释放总线,就要利用重启动信号Sr。它既作为前一次数据传输的结束,又作为后一次传输的开始。


5、总线冲突时,按“低电平优先”的仲裁原则,把总线判给在数据线上先发送低电平的主器件。


6、在特殊情况下,若需禁止所有发生在I2C总线上的通信,可采用封锁或关闭总线,具体操作为在总线上的任一器件将SCL锁定在低电平即可。


7、SDA仲裁和SCL时钟同步处理过程没有先后关系,而是同时进行的。

对 IIC 总线的理解、调用函数以及常见面试问题相关推荐

  1. Linux底层IIC 总线的理解、调用函数以及常见面试问题

    对 IIC 总线的理解.调用函数以及常见面试问题 一.IIC 总线概述: IIC 即Inter-Integrated Circuit(集成电路总线) I2C总线是PHLIPS公司推出的一种串行总线, ...

  2. 这个是小蜜蜂老师给出的蓝桥杯练习题目,我通过这个题目加深了对iic总线的理解和应用。

    以下是我写得代码,望各位大佬指正. #include"reg52.h" #include"iic.h" //-------共阳数码管的段码编码表(无小数点)-- ...

  3. 基于51单片机实现模拟IIC总线时序

    最近用到测量光线的模块BH1750FVI时需要用到IIC总线操作, 于是就又费功夫学习了下, 基本上算是了解了, 所以呢, 就用51的IO口, 模拟出了总线时序, 并能正确操纵需要用IIC总线访问地一 ...

  4. 以C语言的方式理解IIC总线

    为了加深对I2C总线的理解,用C语言模拟IIC总线,边看源代码边读波形: 如下图所示的写操作的时序图: 读时序的理解同理.对于时序不理解的朋友请参考"I2C总线之(二)-时序" 完 ...

  5. js调用php函数兵每秒刷新,深入理解JavaScript立即调用函数表达式(IIFE)

    立即调用函数 目录一.了解立即调用函数表达式 二.立即调用函数表达式报错了? 三.使用立即调用函数的正确姿势 四.常见使用场景 写在最后 一.了解立即调用函数表达式 1.1 思维导图 1.2 什么是立 ...

  6. python中的引用怎么理解_Python函数通过引用调用

    基本上有三种'函数调用':通过价值 通过引用传递 通过对象引用传递 Python是一种PASS-BY-OBJECT-REFERENCE编程语言. 首先,重要的是要理解一个变量,变量(对象)的值是两个独 ...

  7. 正确理解以下名词及其含义:(1)源程序,目标程序,可执行程序(2)程序编辑,程序编译,程序连接(3)程序,程序模块,程序文件 (4)函数,主函数,被调用函数,库函数

    正确理解以下名词及其含义: (1)源程序,目标程序,可执行程序. ​ 源程序:指未编译的按照一定的程序设计语言规范书写的文本文件,是一系列人类可读的计算机语言指令 ​ 目标程序:为源程序经编译可直接被 ...

  8. Debug Hacks (1): 理解用GOT/PLT调用函数的原理

    Debug Hacks (1): 理解用GOT/PLT调用函数的原理 1. GOT/PLT GOT是Global Offset Table,是保存库函数地址的区域.程序运行时,库函数的地址会设置到GO ...

  9. 正确理解以下名词及其含义1源程序,目标程序,可执行程序2程序编辑,程序编译,程序连接3程序,程序模块,程序文件4函数,主函数,被调用函数,库函数5程序调试,程序测试

    正确理解以下名词及其含义: (1)源程序,目标程序,可执行程序. 源程序:指未编译的按照一定的程序设计语言规范书写的文本文件,是一系列人类可读的计算机语言指令目标程序:为源程序经编译可直接被计算机运行 ...

最新文章

  1. ctf 文件头crc错误_[CTF隐写]png中CRC检验错误的分析
  2. apache用proxy 实现URL 转发
  3. flutter字体不跟随系统_flutter 禁止字体大小跟随系统字体改变大小
  4. 博客统计:腾讯分析这些数据哪儿来的?
  5. java服务器端编程
  6. [Leedcode][JAVA][第33题][搜索旋转排序数组]
  7. Qt ToolBar工具栏里同时显示图标和文字
  8. BZOJ 5277 IQ题orz
  9. java案例代码21-电影院购票系统[重要]
  10. webStorm汉化
  11. 用生活案例讲述:erp系统是什么?有哪些作用?
  12. ASP.NET+Sql Server 2008 +highchart控件从数据库中获取数据生成饼状图柱状图
  13. ​【汇总】CV 图像分类常见的 36 个模型
  14. BIM模型文件下载——四层仿古别墅模型
  15. C#实现上位机与PLC通信
  16. 12306多线程抢票
  17. matlab滤波实验,卡尔曼滤波实验及matlab实现
  18. QT报错:Makefile.Debug : moc_xxx.cpp error1
  19. 涉黄网站爆四大生财链:站长月入近两万
  20. 父类引用指向子类对象是什么意思

热门文章

  1. python中的header_python中header是什么意思
  2. 输出菱形图案Python
  3. 计算机应用基础原文,计算机应用基础(本) - 平时作业
  4. zxing二维码的使用
  5. Android 2.3应用开发实战
  6. 【中亦安图】风险提醒之Oracle RAC高可用失效(2)
  7. win7 下安装vs2010 pro 失败解决方法
  8. 毕业论文发表在什么期刊
  9. 2017华为算法大赛总结
  10. pdfviewpager_Android客户端实现查阅PDF文件功能