- 近期需要用到iic协议,想起来以前自己做了这个笔记,因此拿出来贴到博客上。
一、I2C的协议层
- 学习IIC之前,首先要对整个IIC协议有所了解,以下是从各网站上收集到的知识点。
1.1、IIC数据的有效性
- 在SCL为高电平周期内,SDA线上的数据必须保持稳定,数据线仅可以在时钟SCL为低电平时改变。
1.2、起始和结束条件
- 起始条件:当SCL为高电平的时候,SDA线上由高到低的跳变被定义为起始条件。
- 结束条件:当SCL为高电平的时候,SDA线上由低到高的跳变被定义为停止条件。
- 要注意起始和终止信号都是由主机发出的,连接到I2C总线上的器件,若具有I2C总线的硬件接口,则很容易检测到起始和终止信号。
- 总线在起始条件之后,视为忙状态,在停止条件之后被视为空闲状态,对起始条件和结束条件的描述如下图。
1.3、应答
- 每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,从机应答主机所需要的时钟仍是主机提供的,应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期。
- 应答ACK:当第九个SCL时,SDA为低电平为应答。
- 非应答NACK:当第九个SCL时,SDA为高电平为非应答。
1.4、数据帧格式
- I2C总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。 在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/T),用“0”表示主机发送数据(T),“1”表示主机接收数据(R)。
- 每次数据传送总是由主机产生的终止信号结束。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。
二、R-car I2C主模式相关寄存器
- 在网上居然没找到这芯片的芯片手册,为安全起见,我将寄存器地址相关信息抹去。
2.1、主机模式控制寄存器(ICMCR)
2.1.1、MDBS
- 主机模式的数据缓冲区选择
- 该位用于选择数据缓冲区模式。选择本模块支持的suffbuffer模式。
- 当该位设置为1时,选择单缓冲模式。
- 当接收数据寄存器获取数据包直到MDR标志清零为止时,SCL将保持低电平。
- 0:禁止设置
- 1:单缓冲模式
2.1.2、FSCL
- 强制SCL
- 该位控制I2C_SCL引脚的状态(读取反映I2C_SCL引脚的当前状态)。
- 当OBPC位被置1时,FSCL位可以直接控制总线上的SCL线。在读周期中,该位的电平将根据I2C_SCL上的电平而改变,因为它直接反映I2C_SCL上的电平。
2.1.3、FSDA
- 强制SCL
- 该位控制I2C_SDA引脚的状态(读数反映了I2C总线上的忙状态电平)。
- 当OBPC位置1时,该位直接控制总线上的SDA线。
- 在读周期中,该位上的电平将显示I2C总线的忙状态(1为忙,0为不忙)。
2.1.4、OBPC
- 覆盖总线引脚控制
- 当该位设置为1时,该寄存器中的FSDA和FSCL位直接控制SDA和SCL。此模式仅用于测试目的。
2.1.5、MIE
- 主接口使能
- 当该位设置为1时,主接口被使能。
- 当该接口在总线仲裁中(MAL)失败时,该位自动清零。
- 如果0未被写入该位,则在I2C总线上检测到STOP条件时该位的值将恢复为1,并重试数据传输。
- 关于总线仲裁失败的状况,请参阅ICMSR中MAL位的描述。
2.1.6、TSBS
- 开始字节传输启用
- 当这是1,如果主机发出START或重复START条件,它继续通过发送起始字节(01H)
起始字节用于与具有I2C总线接口的较慢的微控制器进行连接。
2.1.7、FSB
- 总线强制停止位
- 当该位设置为1时,主机在当前传输结束时在总线上发送STOP位。
- 如果ESG也被设置,主机立即成为START条件,并开始发送新的数据包,如果没有设置ESG,则主机进入空闲状态。
2.1.8、ESG
- 启用开始生成
- 当该位设置为1时,主机开始传输数据包。
- 如果在总线空闲时ESG被置位,则主机在总线上发送START位,然后发送从机地址。
- 如果主机传送数据时ESG被设置,则在数据字节传输结束时,主机在发送从机地址之前传输重复START条件。
- 在传输数据包时,当从站地址已被发送时,软件必须重置该位,否则在每次发送完成后都会发送重复的START条件。
2.2、主机状态寄存器ICMSR
2.2.1、MNR
- 主机接收数据NACK非应答
- 当该消息设置为1时,该位表示主机的NACK响应(SDA线在总线上的确认周期为高电平,即非应答)到地址或数据传输。
2.2.2、MAL
- 主机仲裁失败
- 在多主系统中,当该位设置为1时,这表明主机已经失去了对总线上其他主机的仲裁。此时,MIE被复位,主接口被禁用。
2.2.3、MST
- 主机停止位
- 当该位设置为1时,表示主机在总线上发送了STOP条件,即停止位。
- 主机停止位的发送可以由两种方式产生:由控制寄存器中强制停止位的设置,或者在接受从机数据期间,接收到从机发送了NACK。
2.2.4、MDE
- 主机数据为空
- 在字节数据传输开始时,发送数据寄存器的内容被加载到准备好在总线上发送的移位寄存器中。当该位设置为1时,它指示移位 寄存器已经加载,因此发送数据寄存器已准备好接受进一步的数据。
- 在主机传输期间,传送从机地址addr后MAT位和MDE位同时被置1。在这种情况下,需要在ICMCR的ESG位清零后清除MDT和MAT位,清零将重启数据传输。
2.2.5、MDT
- 主机数据传输完成
- 数据已经发送到总线上的从机。在最后一个数据位之后,SCL的下降沿之后,此状态位将被设置为1。
2.2.6、MDR
- 主机数据接收
- 数据从总线接收并在接收数据寄存器中。在最后一个数据位之后,该状态位在SCL的下降沿之后被置为1,在单缓冲模式下,从接收数据寄存器读取数据后,该状态位必须复位。
- 当MDBS设置为1时,SCL将从接收数据寄存器获取数据包的时间保持为低电平,直到MDR标志被清零。
- 在主机接受数据模式期间,传送从机地址addr后MAT位和MDR位同时被置1,在这种情况下,在清除ICMCR的ESG位后,必须将MDR和MAT位清零,然后才能进行数据的接收。
2.2.7、MAT
- 主机发送从机地址已经完成
- 主机已经发送从站地址的数据包。该位在从机地址数据包后的ACK位的SCL地址下降后的之后被置为1。
- 这些位都能被读写,但写操作时只能做清零处理,如果置1的时候,则会被忽略。
2.3、时钟控制寄存器
三、时序图分析
- 相关时序图来源于对XV4000系列的数字陀螺仪的数据采集,主要原因是因为板子上接的iic设备只有这款硬件上比较方便检测(方便接示波器进行分析)。在kernel中,在sys目录下
- 通过对技术手册的阅读,了解以下以下信息。
3.1、相关技术手册的信息
- 1、XV4000系列的陀螺仪通信频率为400Hz。
- 2、XV4000的slave addr的地址位0x68
- 3、XV4000系列的陀螺仪数据为16位,因此读取数据的需要读取两个8位数据。
- 4、XV4000主要输出两种数据,一个是温度,一个是陀螺仪的角度值。
3.2、读数据时序
- 以XV4000系列的陀螺仪读取其内部陀螺仪数据,通过示波器可以得出以下时序图。
四、读数据程序分析
4.1、Start分析
1、先进行写操作,设置从机地址到寄存器ICMAR中,ICMAR最低位为读写位,0为写,1为读。由XV4000数据手册可得知,从机地址为0x68。
writel((devno << 1) | 0, &base->icmar);
2、从机的寄存器地址写入icrxdtxd寄存器中。
writel(cmd, &base->icrxdtxd);
3、清空主机状态寄存器。
writel(0, &base->icmsr);
4、设置单缓冲模式,使能IIC功能,开始发送Start。(当主线为闲置状态时,MCR_ESG被置位的话,则主机在总线上发送START位)
writel(MCR_MDBS | MCR_MIE | MCR_ESG, &base->icmcr);
5、当主机传输期间,传送从机地址addr后MAT位和MDE位同时被置1。这时判断MAT和MDE是否被置位,如果被置位,则代表addr传输完成。
while ((readl(&base->icmsr) & (MSR_MAT | MSR_MDE))
!= (MSR_MAT | MSR_MDE))
6、传输完成后,需要对MCR_ESG进行清零,否则在每次发送完成后都会发送重复的START位。
writel(MCR_MDBS | MCR_MIE, &base->icmcr);
7、在清零ESG位之后,需要清除MDT和MAT位,以重启数据传输。
writel(~(MSR_MAT | MSR_MDE), &base->icmsr);
4.2、Salve addr发送分析
-
1、重启数据传输后,主机总线会发送从机的寄存器地址cmd到总线上,如上图所示,RA=0x25,对应XV4000的寄存器标,即表示将要操作的寄存器为Output angular rate,即读取陀螺仪的角速度。
-
2、当主机向总线发送完从机寄存器地址后,从机接到数据后,需要发送一个应答信号ACK,以表示接受到数据。
-
3、当该MDE设置为1时,它表示移位寄存器已经加载,发送数据寄存器已准备好接受进一步的数据。所以我们需要等待MDE被置为1,才能进行下一步操作。
while (!(readl(&base->icmsr) & MSR_MDE))
4.3、Restart发送分析
1、发送完从机寄存器地址,主机需要设置成读数据模式。这时候需要重新发送起始位,将从机地址0x68写入寄存器ICMAR中,并设置为读模式
writel((devno << 1) | 1, &base->icmar);
2、设置单缓冲模式,使能IIC功能,开始发送Start。
writel(MCR_MDBS | MCR_MIE | MCR_ESG, &base->icmcr);
3、清空主机状态寄存器。
writel(0, &base->icmsr);
4、等待从机地址传输完成
while ((readl(&base->icmsr) & (MSR_MAT | MSR_MDR))
!= (MSR_MAT | MSR_MDR))
5、当主机向总线发送完从机寄存器地址后,从机接到数据后,需要发送一个应答信号ACK,以表示接受到数据。
6、接下来从机接收到主机发送的读命令后,就要开始向主机发送数据包了,这时候需要清除ESG,防止重复发送start。
writel(MCR_MDBS | MCR_MIE, &base->icmcr);
7、清除MAT和 MDR,让总线重新启动
writel(~(MSR_MAT | MSR_MDR), &base->icmsr);
4.4、数据接收
1、总线重启后,从机控制SDA线向主机发送数据,这时候等待MDR被置为1,代表控制器已经将总线上的数据读取完毕
while (!(readl(&base->icmsr) & MSR_MDR))
2、读取icrxdtxd寄存器中的数据。
buf[num] = (u8)readl(&base->icrxdtxd);
3、如果需要连续从从机中读取数据的话,在CLK第九个周期的时候,主机应该要响应Ack,继续保持通信状态,如果仅读取一位数据的话,主机就可以直接不响应Ack,这样主机发送stop,停止本次通信。
4.5、Stop
1、 writel(MCR_MDBS | MCR_MIE | MCR_FSB, &base->icmcr);
4.6、IIC数据读取
/* clear ESG */
/* clear MSR_MAT */
while(num < size-1)
{
/* clear MSR_MDR to start SCLclk*/
writel(~MSR_MDR, &base->icmsr);
{
if(i > IRQ_WAIT)
{
i=0;
printf("IRQ_WAIT3 Timeout!%d\n",devno);
return -1;
}
i+=1;
udelay(10);
}
/* get receive data */
num++;
}
/* prepare stop condition */
writel(MCR_MDBS | MCR_MIE | MCR_FSB, &base->icmcr);
/* start SCLclk */
writel(~MSR_MDR, &base->icmsr);
while (!(readl(&base->icmsr) & MSR_MDR))
{
if(i > IRQ_WAIT)
{
i=0;
printf("IRQ_WAIT4 Timeout!%d\n",i);
return -1;
}
i+=1;
udelay(10);
}
/* get receive data */
buf[num++] = (u8)readl(&base->icrxdtxd);
/* start SCLclk */
writel(~MSR_MDR, &base->icmsr);
while (!(readl(&base->icmsr) & MSR_MST))
{
if(i > IRQ_WAIT)
{
i=0;
printf("IRQ_WAIT5 Timeout!%d\n",i);
return -1;
}
i+=1;
udelay(10);
}
writel(0, &base->icmcr);
return num;
}
五、网上一些其他知识点
5.1、总线死锁原因分析
- I2C总线写操作过程中,主机在产生启动信号后控制SCL产生8个时钟脉冲,然后拉低SCL信号为低电平,在这个时候,从机输出应答信号,将SDA信号拉为低电平。如果这个时候主机异常复位,SCL就会被释放为高电平。此时,如果从机没有复位,就会继续I2C的应答,将SDA一直拉为低电平,直到SCL变为低电平,才会结束应答信号。而对于主机来说,复位后检测SCL和SDA信号,如果发现SDA信号为低电平,则会认为I2C总线被占用,会一直等待SCL和SDA信号变为高电平。这样,主机等待从机释放SDA信号,而同时从机又在等待主机将SCL信号拉低以释放应答信号,两者相互等待,I2C总线进人一种死锁状态。同样,当I2C进行读操作时,从机应答后输出数据,如果在这个时刻主机异常复位而此时从机输出的数据位正好为0,也会导致I2C总线进入死锁状态。
- 解决方案通常有如下几种:
- 1、将从机的电源设计为可控,当发生总线死锁的时将从机复位
- 2、可以在从机的程序中加入监测功能,如果总线长时间被拉低则释放对总线的控制
- 3、在主机中增加I2C总线恢复程序。每次主机复位后,如果检测到SDA被拉低,则控制SCL产生<=9个时钟脉冲(针对8位数据的情况),每发送一个时钟脉冲就检测SDA是否被释放,如果SDA已经被释放就再模拟产生一个停止信号,这样从机就可以完成被挂起的读写操作,从死锁状态中恢复过来。这种方法有一定的局限性,因为大部分主机的I2C模块由内置的硬件电路来实现,软件并不能够直接控制SCL信号模拟产生需要时钟脉冲