当前位置:   article > 正文

【嵌入式Linux内核驱动】IIC子系统 | 硬件原理 | 应用编程 | 内核驱动 | 总体框架

【嵌入式Linux内核驱动】IIC子系统 | 硬件原理 | 应用编程 | 内核驱动 | 总体框架

1. 硬件原理

1.1 IIC 基础

IIC协议简介—学习笔记_iic标准协议_越吃越胖的黄的博客-CSDN博客

I2C(Inter-Integrated Circuit)是一种串行通信协议,用于连接微控制器、传感器、存储器和其他外设。

I2C使用两条线(SDA和SCL)进行通信,可以连接多个设备,每个设备都有一个唯一的地址。I2C总线上的设备可以充当主设备或从设备。主设备负责发起通信,从设备负责响应通信请求。

I2C协议具有以下特点:

  1. 两根线
  2. 同步、半双工
  3. 带应答
  4. 一主多从、多主多从

1.2 硬件连接

要能手撕出来

image-20230403191048720

仲裁:SDA线的仲裁也是建立在总线具有线“与”逻辑功能(线与逻辑,即两个以上的输出端直接互连就可以实现“AND”的逻辑功能。两个一出一,一个一出零、没有一出零)的原理上的。节点在发送1位数据后,比较总线上所呈现的数据与自己发送的是否一致。是,继续发送;否则,进行比较,输出低电平进行发送,输出高电平退出。SDA线的仲裁可以保证I2C总线系统在多个主节点同时企图控制总线时通信正常进行并且数据不丢失。总线系统通过仲裁只允许一个主节点可以继续占据总线。

1.3 时序

要能手撕出来

时序总结:

image-20230405220920577

总线空闲状态SCL和SDA均为高电平,接上拉电阻。
起始信号(START)在SCL保持高电平期间,SDA由高电平被拉低。由主控器发出。
数据位传送(DATA)在SCL保持高电平期间,SDA上的电平保持稳定,低电平为数据0、高电平为数据1。用法:主控器和被控器都可发出。
应答信号(ACK)在SCL保持高电平期间,SDA保持低电平。IIC总线上所有数据都是以8位字节传送的,发送器每发送一个字节,就在第9个时钟脉冲期间释放SDA(高电平),由接收器反馈一个ACK。
非应答信号(NACK)在SCL保持高电平期间,SDA保持高电平。如果接收器是主控器,则它在收到最后一个字节后,发送一个NACK,通知被控器结束数据发送,并释放SDA(高电平),以便主控器发送一个STOP。
停止信号(STOP)在SCL保持高电平时间,SDA由低电平被释放(拉高)。由主控器发出。

各时序部分的代码(要能手撕出来):

起始信号

void IIC_Start(void)
{
	SDA_H;						//拉高SDA线
	SCL_H;						//拉高SCL线
	DelayUs(iicInfo.speed);		//延时,速度控制
	
	SDA_L;						//当SCL线为高时,SDA线一个下降沿代表开始信号
	DelayUs(iicInfo.speed);		//延时,速度控制
	SCL_L;						//钳住SCL线,以便发送数据
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

停止信号

void IIC_Stop(void)
{
	SDA_L;						//拉低SDA线
	SCL_L;						//拉低SCL先
	DelayUs(iicInfo.speed);		//延时,速度控制
	
	SCL_H;						//拉高SCL线
	SDA_H;						//拉高SDA线,当SCL线为高时,SDA线一个上升沿代表停止信号
	DelayUs(iicInfo.speed);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

应答信号

//产生ACK应答,读取从机一字节数据后还要接着读的时候使用
//SCL在SDA一直为低电平期间完成低高电平转换
void IIC_Ack(void)
{	
	SCL_L;						//拉低SCL线
	SDA_L;						//拉低SDA线<----
	DelayUs(iicInfo.speed);
	SCL_H;						//拉高SCL线
	DelayUs(iicInfo.speed);
	SCL_L;						//拉低SCL线
}
//不产生ACK应答,读取从机一字节数据后不读了的时候使用    
//SCL在SDA一直为高电平期间完成低高电平转换
void IIC_NAck(void)
{
	SCL_L;						//拉低SCL线
	SDA_H;						//拉高SDA线<----
	DelayUs(iicInfo.speed);
	SCL_H;						//拉高SCL线
	DelayUs(iicInfo.speed);
	SCL_L;						//拉低SCL线
}
//等待ACK应答    
//发送完一个字节后(释放SDA)的下一个时钟高电平时期,读取SDA电平,0为收到应答
_Bool IIC_WaitAck(unsigned int timeOut)
{
	SDA_H;DelayUs(iicInfo.speed);			//拉高SDA线
	SCL_H;DelayUs(iicInfo.speed);			//拉高SCL线
	
	while(SDA_R)							//如果读到SDA线为1,则等待。应答信号应是0
	{
		if(--timeOut)
		{
			printf("WaitAck TimeOut\r\n");
			IIC_Stop();						//超时未收到应答,则停止总线			
			return IIC_Err;					//返回失败
		}		
		DelayUs(iicInfo.speed);
	}	
	SCL_L;									//拉低SCL线,以便继续收发数据	
	return IIC_OK;							//返回成功	
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

发送数据

void IIC_Send(unsigned char byte)
{
	unsigned char count = 0;
	
    SCL_L;							//拉低时钟开始数据传输	
    for(; count < 8; count++)		//循环8次,每次发送一个bit
    {
		if(byte & 0x80)				//【先发送最高位,大端传输】
			SDA_H;
		else
			SDA_L;
		
		byte <<= 1;					//byte左移1位
		
		DelayUs(iicInfo.speed);
		SCL_H;
		DelayUs(iicInfo.speed);
		SCL_L;
    }
}   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

接收数据

unsigned char IIC_Read(void)
{	
	unsigned char count = 0, receive = 0;	
	SDA_H;						//拉高SDA线,开漏状态下,需线拉高以便读取数据
	
    for(; count < 8; count++ )	//循环8次,每次发送一个bit
	{
		SCL_L;                  //拉低,从机放数据
		DelayUs(iicInfo.speed);
		SCL_H;                  //拉高,主机读数据
		
        receive <<= 1;			//左移一位	
        if(SDA_R)				//如果SDA线为1,则receive变量自增,每次自增都是对bit0的+1,然后下一次循环会先左移一次
			receive++;		
		DelayUs(iicInfo.speed);
    }	
    return receive;	
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

1.4 代码

要能手撕软件模拟IIC通讯代码

一、写数据

字节写

image-20230222160511010
void IIC_SEND_BYTE(u8 slaveaddr,u8 registeraddr,u8 byte);//发送到具体从机地址及相关寄存器一个字节
{
	IIC_Start();           //通讯开始
	IIC_Send(slaveaddr);   //先发送从机的地址,寻址从机
	IIC_WaitAck();         //得到回应,说明,电路中有这个外设器件
	IIC_Send(registeraddr);//寻址这个器件中的相关寄存器
	IIC_WaitAck();         //得到回应,说明这个寄存器存在
	IIC_Send(byte);        //发送一个字节
	IIC_WaitAck();         //等待回应
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

页写

image-20230222160628572

指定设备+指定地址+指定数据

void IIC_SEND_BYTES(u8 slaveaddr,u8 registeraddr,u8 *pbuffer,u16 num );//发送一串数据给具体从机的相关寄存器
{
	IIC_Start();             //开始通讯
	IIC_Send(slaveaddr);     //寻址从机
	IIC_WaitAck();           //等待回应
	IIC_Send(registeraddr);  //寻址寄存器
	IIC_WaitAck();           //等待回应

	for(t=0;t<num;t++)       //发送数组中的数据
	{
		IIC_Send(*(pbuffer+t));
		IIC_WaitAck();       //每发送一个字节,都需要一个应答
	}
	IIC_Stop();              //终止通讯
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

二、读数据

当前地址读

image-20230222161141885

指定设备+从机当前地址指针指示的地址下,读从机的数据

指定地址读

image-20230222161435865

指定设备+指定地址+读从机数据

  • 指定设备写+指定地址(让从机指针先指过去)

  • 指定设备读+读指定地址

void I2C_READ_BYTES(u8 slaveaddr,u8 registeraddr,u8 *pbuffer,u16 num );//读取具体从机的相关寄存器一串字节
{
    I2C_Start();           //开始通讯
	I2C_Send(slaveaddr);   //寻址从机
	I2C_WaitAck();         //等待回应
	I2C_Send(registeraddr);//寻址寄存器
	I2C_WaitAck();         //等待回应

	I2C_Start();           //重新通讯
	I2C_Send(slaveaddr+1); //改为读数据
	I2C_WaitAck();         //等待回应

	for(t=0;t<num;t++)     //存储数据
	{
		*(pbuffer+t)=I2C_Read();
		if (t== num-1)     //字节没发完,必须给出应答,发完的给个非应答信号
	 	{
	   	 	IIC_Ack();
		}
		else
		{
	    	IIC_NAck();
		}
	}
	IIC_Stop();            //通讯结束
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

1.5常见面试问题

I2C可以挂载多少个器件

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

每个设备都必须具有唯一的7位或10位地址(在7位地址模式下,地址是0-127之间的数字,在10位地址模式下,地址是0-1023之间的数字)。

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

通过GPIO控制IIC芯片上的地址选择引脚选择改变器件地址

或可以通过硬件上设计,控制器件是否挂载总线来实现(方法可用一个开关电路切断器件SDA或者SCL是否接入总线来实现)。

I2C总线的仲裁你知道吗?

I2C总线具有多主控能力,有时会发生两个或多个主器件同时想占用总线的情况,这种情况叫做总线竞争。I2C可以对发生在SDA线上的总线竞争进行仲裁,I2C总线的仲裁逻辑是建立在线与功能上的,有一个拉低SDA总线就是低。

其仲裁原则是这样的:每一个主器件每次发送一位数据,然后比较总线上所呈现的电平与自己所发送的数据是否一致,如果一致,继续发送下一位数据,否则就退出竞争。

总线竞争的仲裁在两个层次上进行:首先是地址位的比较,如果主器件寻址同一个从器件,则进入数据位比较,从而确保竞争仲裁的可靠性。由于是利用IIC总线上的信息进行仲裁,不会造成信息的丢失。

如果一个设备收到了其他设备发送的仲裁比特和地址,那么它会停止发送数据并释放总线,让另一个设备继续控制总线。如果没有任何一个设备释放总线,则总线将一直处于锁定状态,直到有设备释放总线。如果I2C总线一直处于锁定状态,那么任何设备都无法进行数据传输,因为总线被其他设备占用。这意味着任何设备都不能发送数据,也不能接收数据。因此,设备之间的通信将被阻塞,直到总线被释放。如果总线被锁定的时间太长,可能会导致数据传输的失败和系统的异常。为了避免这种情况,需要在设计和实现I2C系统时仔细考虑总线仲裁机制,并确保在系统中合理地分配和协调I2C总线上的设备。

I2C总线的其他注意点

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

I2C十位设备地址

任何IIC设备都有一个7位地址,理论上,现实中只能有127种不同的IIC设备。实际上,已有IIC的设备种类远远多于这个限制,在一条总线上出现相同的地址的IIC设备的概率相当高。为了突破这个限制,很多设备使用了双重地址——7位地址加引脚地址。IIC标准也预知了这种限制,提出10位的地址方案。

10位的地址方案对IIC协议的影响有两点:

  • 第一,地址帧为两个字节长,原来的是一个字节。
  • 第二,第一个字节前五位最高有效位用作10位地址标识,约定是“11110”。

img

除了10位地址标识,标准还预留了一些地址码用作其它用途,如下表:

img

I2C时钟拉伸

在IIC通信中,主设备决定了时钟速度。因为时钟脉冲信号是由主设备显式发出的。但是,当从设备没办法跟上主设备的速度时,从设备需要一种机制来请求主设备慢一点,这种机制称为时钟拉伸。而基于IIC结构的特殊性,这种机制得到实现。当从设备需要降低传输的速度的时候,它可以按下时钟线,逼迫主设备进入等待状态,直到从设备释放时钟线,通信才继续。

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

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

如果被控器希望主控器降低传送速度可以通过将SCL主动拉低延长其低电平时间的方法来通知主控器,当主控器在准备下一次传送发现SCL的电平被拉低时就进行等待,直至被控器完成操作并释放SCL线的控制控制权。这样,主控器实际上受到被控器的时钟同步控制。可见SCL线上的低电平是由时钟低电平最长的器件决定;高电平的时间由高电平时间最短的器件决定。这就是时钟同步,它解决了I2C总线的速度同步。

I2C的通讯速度有几种?

彻底搞懂IIC总线(5)I2C总线传输速度详解 - 德力威尔王术平 - 博客园 (cnblogs.com)

(2条消息) spi、iic、can高速传输速度与选择_万_大_帅的博客-CSDN博客_spi速度

终于看懂了!通信协议 IIC 与 SPI 最全对比 - 知乎 (zhihu.com)

速度模式最高比特率( bit/s)备注
标准模式(Sm)100K双向传输,高速模式兼容低速模式
快速模式(Fm)400K
快速模式增强(Fm+)1M
高速模式(HSm)3.4M
超快速模式(UFm)5M单向传输,不兼容其他模式
  1. 标准模式(Standard Mode):标准模式是I2C总线最基本的通讯速度。在标准模式下,时钟频率最高可以达到100 kHz,总线电容最大为400 pF。这种通讯速度适用于较长的总线和较慢的设备,如LCD显示屏、温度传感器等。
  2. 快速模式(Fast Mode):快速模式是I2C总线的一种较快的通讯速度。在快速模式下,时钟频率最高可以达到400 kHz,总线电容最大为400 pF。这种通讯速度适用于数据传输速度要求较高的设备,如EEPROM、实时时钟等。
  3. 快速模式加(Fast Mode Plus):快速模式加是I2C总线的一种改进版快速模式。在快速模式加下,时钟频率最高可以达到1 MHz,总线电容最大为400 pF。这种通讯速度适用于对数据传输速度要求更高的设备,如高速ADC、高速数字信号处理器等。
  4. 高速模式(High-Speed Mode):高速模式是I2C总线的一种较快的通讯速度。在高速模式下,时钟频率最高可以达到3.4 MHz,总线电容最大为400 pF。这种通讯速度适用于对数据传输速度要求非常高的设备,如高清视频处理器、高速闪存等。
  5. 超高速模式(Ultra Fast-mode):超高速模式是I2C总线的一种非常快的通讯速度。在超高速模式下,时钟频率最高可以达到5 MHz,总线电容最大为200 pF。这种通讯速度适用于需要超高速数据传输的设备,如图像处理器、高速网络接口等。

高速模式

原理上讲,使用上拉电阻来设置逻辑1,会限制总线的最大传输速度。而速度是限制总线应用的因素之一。这也说明为什么要引入高速模式(3.4Mbps)。在发起一次高速模式传输前,主设备必须先在低速的模式下(例如快速模式)发出特定的“High Speed Master”信号。为缩短信号的周期和提高总线速度,高速模式必须使用额外的I/O缓冲区。另外,总线仲裁在高速模式下可屏蔽掉。

异步IIC有了解吗?

异步IIC是指同步串行通信的I2C(Inter-Integrated Circuit)协议的一种变体。在标准的I2C协议中,通信是同步的,而异步IIC则是一种基于异步通信方式的I2C协议。

在异步IIC中,时钟信号不是由主设备(Master)提供,而是由从设备(Slave)生成。这意味着从设备负责控制时钟,并在数据线(SDA)上发送数据。主设备则负责检测从设备发送的时钟信号和数据,并对数据进行解析。

异步IIC协议的工作原理如下:

从设备在数据线上生成时钟信号,并将数据发送到数据线上。
主设备检测数据线上的时钟信号,并在时钟信号的边沿进行数据采样。
主设备根据采样到的数据进行处理,如读取数据或向从设备发送命令。
主设备和从设备之间通过数据线进行双向通信。
异步IIC协议相对于标准的同步I2C协议,在实现上更为简单,因为主设备不需要提供时钟信号。然而,由于时钟信号是由从设备生成的,因此在异步IIC中可能存在一些时钟同步的挑战,例如时钟偏移和数据速率的不一致。因此,异步IIC通常用于一些特定的应用场景,而不是广泛应用于各种设备之间的通信。

SMBUS协议

SMBus: System Management Bus,系统管理总线。
SMBus是基于I2C协议的,SMBus要求更严格,SMBus是I2C协议的子集。

2. 应用编程

I2C-Tools的访问I2C设备的2种方式

官方留的应用层直接控制的接口

image-20230320155618252

I2C-Tools可以通过SMBus来访问I2C设备,也可以使用一般的I2C协议来访问I2C设备。
使用一句话概括I2C传输:APP通过I2C Controller与I2C Device传输数据。
在APP里,有这几个问题:

  • 怎么指定I2C控制器?
    • i2c-dev.c提供为每个I2C控制器(I2C Bus、I2C Adapter)都生成一个设备节点:/dev/i2c-0、/dev/i2c-1等待
    • open某个/dev/i2c-X节点,就是去访问该I2C控制器下的设备
  • 怎么指定I2C设备?
    • 通过ioctl指定I2C设备的地址
    • ioctl(file, I2C_SLAVE, address)
      • 如果该设备已经有了对应的设备驱动程序,则返回失败
    • ioctl(file, I2C_SLAVE_FORCE, address)
      • 如果该设备已经有了对应的设备驱动程序
      • 但是还是想通过i2c-dev驱动来访问它
      • 则使用这个ioctl来指定I2C设备地址
  • 怎么传输数据?
    • 两种方式
    • 一般的I2C方式:ioctl(file, I2C_RDWR, &rdwr)
    • SMBus方式:ioctl(file, I2C_SMBUS, &args)

image-20230321210637473image-20230321210602989

使用GPIO模拟I2C的驱动程序

3. 内核驱动

3.1 通用驱动i2c-dev分析

看看官方留的应用层直接控制的接口对应的驱动

i2c-dev.c注册过程分析

就是字符设备那一套

image-20230321210700597

image-20230321210717574

file_operations函数分析

image-20230321211156574

image-20230321210850934

i2cdev_ioctl: I2C_SLAVE/I2C_SLAVE_FORCE

用于传地址

image-20230321210905782

i2cdev_ioctl: I2C_RDWR

读写方式1

image-20230321210923606

i2cdev_ioctl: I2C_SMBUS

读写方式2

image-20230321211003507

3.2 编写设备驱动之i2c_driver

image-20230321213743295

3.3 编写设备驱动之i2c_client

问题:Linux IIC子系统设备树中没有写那个IIC adapter,驱动怎么对应上呢❓

四种方法

1.在用户态生成

示例:

// 在I2C BUS0下创建i2c_client
# echo ap3216c 0x1e > /sys/bus/i2c/devices/i2c-0/new_device

// 删除i2c_client
# echo 0x1e > /sys/bus/i2c/devices/i2c-0/delete_device
  • 1
  • 2
  • 3
  • 4
  • 5

2.编写代码

  • i2c_new_device:会创建i2c_client,即使该设备并不存在

      static struct i2c_board_info sfe4001_hwmon_info = {
    	  I2C_BOARD_INFO("max6647", 0x4e),
      };
    
      int sfe4001_init(struct efx_nic *efx)
      {
          (...)
          efx->board_info.hwmon_client =i2c_new_device(&efx->i2c_adap, &sfe4001_hwmon_info);
          (...)
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  • i2c_new_probed_device

    • 它成功的话,会创建i2c_client,并且表示这个设备肯定存在

    • I2C设备的地址可能发生变化,比如AT24C02的引脚A2A1A0电平不一样时,设备地址就不一样

    • 可以罗列出可能的地址

    • i2c_new_probed_device使用这些地址判断设备是否存在

      static const unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END };
    
      static int usb_hcd_nxp_probe(struct platform_device *pdev)
      {
    	(...)
    	struct i2c_adapter *i2c_adap;
    	struct i2c_board_info i2c_info;
    
    	(...)
    	i2c_adap = i2c_get_adapter(2);
    	memset(&i2c_info, 0, sizeof(struct i2c_board_info));
    	strscpy(i2c_info.type, "isp1301_nxp", sizeof(i2c_info.type));
    	isp1301_i2c_client = i2c_new_probed_device(i2c_adap, &i2c_info,
    						   normal_i2c, NULL);
    	i2c_put_adapter(i2c_adap);
    	(...)
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
  • i2c_register_board_info

    • 内核没有EXPORT_SYMBOL(i2c_register_board_info)
    • 使用这个函数的驱动必须编进内核里去

3.使用设备树生成

在某个I2C控制器的节点下,添加如下代码:

		ap3216c@1e {
			compatible = "lite-on,ap3216c";
			reg = <0x1e>;
		};
  • 1
  • 2
  • 3
  • 4

**4.(不推荐):由i2c_driver.detect函数来判断是否有对应的I2C设备并生成i2c_client **

3.4 I2C_Adapter驱动框架讲解与编写

它知道怎么发数据,不知道要发什么数据。要发的数据通过i2c_driver经ic_core过来

关心第几条IIC总线nr,IIC 算法i2c_algorithm,master_xfer函数

一般来说芯片厂商都写好了

image-20230321221335124

image-20230321221313008

master_xfer:这是最重要的函数,它实现了一般的I2C传输,用来传输一个或多个i2c_msg

编写i2c_adapter驱动程序

设备树

在设备树里构造I2C Bus节点:

	i2c-bus-virtual {
		 compatible = "100ask,i2c-bus-virtual";
	};
  • 1
  • 2
  • 3

platform_driver

分配、设置、注册platform_driver结构体。

核心是probe函数,它要做这几件事:

  • 根据设备树信息设置硬件(引脚、时钟等)
  • 分配、设置、注册i2c_apdater

i2c_apdater

i2c_apdater核心是master_xfer函数,它的实现取决于硬件,大概代码如下:

static int xxx_master_xfer(struct i2c_adapter *adapter,
						struct i2c_msg *msgs, int num)
{
    for (i = 0; i < num; i++) {
        struct i2c_msg *msg = msgs[i];
        {
        	// 1. 发出S信号: 设置寄存器发出S信号
            CTLREG = S;
            
            // 2. 根据Flag发出设备地址和R/W位: 把这8位数据写入某个DATAREG即可发出信号
            //    判断是否有ACK
            
            if (!ACK)
                return ERROR;
            else {
	            // 3. read / write
	            if (read) {
                    STATUS = XXX; // 这决定读到一个数据后是否发出ACK给对方
                    val = DATAREG; // 这会发起I2C读操作
                } else if(write) {
                    DATAREG = val; // 这会发起I2C写操作
                    val = STATUS;  // 判断是否收到ACK
                    if (!ACK)
                        return ERROR;
                }                
            }
            // 4. 发出P信号
            CTLREG = P;
        }
    }    
    return i;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

4. 总体框架

4.1 I2C驱动框架

image-20221003211520001

image-20230321212743832

I2C Core就是I2C核心层,它的作用:

  • 提供统一的访问函数,比如i2c_transfer、i2c_smbus_xfer等
  • 实现I2C总线-设备-驱动模型,管理:I2C设备(i2c_client)、I2C设备驱动(i2c_driver)、I2C控制器(i2c_adapter)

4.2 IIC子系统的重要结构体

先有个整体框架

APP通过I2C Controller与I2C Device传输数据

APP通过i2c_adapter与i2c_client传输i2c_msg

  • 使用i2c_adapter表示一个I2C BUS,或称为I2C Controller

  • 里面有2个重要的成员:

    • nr:第几个I2C BUS(I2C Controller)

    • i2c_algorithm,里面有该I2C BUS的传输函数,用来收发I2C数据

怎么表示I2C Device

  • 一个I2C Device,一定有设备地址
  • 它连接在哪个I2C Controller上,即对应的i2c_adapter是什么
  • 使i2c_client来表示一个I2C Device

怎么表示要传输的数据

  • 在上面的i2c_algorithm结构体中可以看到要传输的数据被称为:i2c_msg

内核函数i2c_transfer

  • i2c_msg里含有addr,所以这个函数里不需要i2c_client

4.3 I2C总线-设备-驱动模型

image-20230321212944879

i2c_driver

知道发什么数据,不知道怎么发数据。如打开哪个设备、读写哪个寄存器

i2c_driver表明能支持哪些设备:

  • 使用of_match_table来判断
    • 设备树中,某个I2C控制器节点下可以创建I2C设备的节点
      • 如果I2C设备节点的compatible属性跟of_match_table的某项兼容,则匹配成功
    • i2c_client.name跟某个of_match_table[i].compatible值相同,则匹配成功
  • 使用id_table来判断
    • i2c_client.name跟某个id_table[i].name值相同,则匹配成功

i2c_client

主要提供IIC设备名字和地址,以及设备挂载的i2c_adapter让驱动能对应上

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/205843
推荐阅读
相关标签
  

闽ICP备14008679号