Contents
第十七章 IIC通信
本章代码,查看我的github网站:https://github.com/weiqi7777/s5pv210
1、IIC的总线空闲状态、起始位、结束位
IIC总线上有n(n>=1)个主设备,m(m>=1)个从设备。IIC总线上有2种状态;空闲态(所有从设备都未和主设备通信,此时总线空闲)和忙态(其中一个从设备在和主设备通信,此时总线被这一对占用,其他设备不能占用总线)。
整个通信分为一个周期一个周期的,两个相邻的通信周期是空闲态。每一个通信周期由一个起始位开始,一个结束位结束,中间是本周期的通信数据。
下图是起始位和结束位。
起始位:起始位是一个时间段,在这段时间内总线状态变化情况是:SCL线维持高电平,同时SDA发生一个从高到低的下降沿。
结束位:结束位也是一个时间段,在这段时间内总线状态变化情况是:SCL维持高电平,同时SDA发生一个从低到高的上升沿。
2、IIC数据传输格式(数据位和ACK)
每一个通信周期的发起和结束都是由主设备来做的,从设备只有被动的响应主设备,没法自己自发的去做任何事情。
主设备在每个周期会先发8位的从设备地址(其实8位中只有7位是从设备地址,还有1位表示主设备要写入还是读取)到总线(主设备是以广播的形式发送,只要总线上的所有从设备都能收到这个信息),然后总线上的每个从设备都能收到这个地址,并且收到地址后,和自己的地址比较,看是否相等。如果相等,说明主设备本次通信就是和自己,如果不相等,说明不是和自己通信,忽略后续接收的所有数据。
主设备发送一段数据后,从设备(接收方)需要回应一个ACK。这个响应本身只有1个bit位,不能携带有效信息,只能表示要么收到数据,即有效响应;要么表示未收到响应,无效响应。
在某一个通信时刻,主设备和从设备只能有一个在发(占用总线,向总线写),另一个在收(释放总线,从总线读)。如果在某个时间主设备和从设备都试图向总线写那就会出问题,通信就要出问题。
上图是IIC控制器能产生的四种时序。其中灰色的A,表示需要响应,非灰色的A,表示不需要响应。在真正和外部芯片使用IIC通信时,要将上述的四种时序进行组合实现读写。
对于1,将要写的地址写入I2CDS寄存器,将I2CSTAT写入0xf0,主发送模式。控制器会自动的产生开始信号,并将数据发送出去,接收响应信号。
对于2,将要写的地址写入I2CDS寄存器,将I2CSTAT写入0xb0,主接收模式。控制器会自动的产生开始信号,并将数据发送出去,接收响应信号。
对于3,将要写的数据写入I2CDS寄存器,将I2CCON的第4位中断标志位清零,控制器会自动将数据发送出去,接收响应信号。
对于4,直接去读I2CCON的第4位中断标志位,为1,说明数据接收完毕,控制器将接收到的数据放入到寄存器I2CDS中,并产生响应信号。
3、数据在总线上的传输协议
IIC通信时基本的数据单位也是以字节为单位,每次传输的有效数据都是1个字节(8位)。
起始位后的8个clk都是主设备在发送(主设备掌控总线),此时从设备只能读取总线,通过读总线来得知主设备发给从设备的信息;到了第9周期,从设备需要发送ACK给主设备,此时主设备要释放总线,以接收从设备响应。从设备将SDA线拉低,表示响应。如果从设备没有拉低SDA线,发ACK。主设备就应该认为刚刚发送的数据不正确。
4、IIC的时钟
IIC的时钟来源于PCLK_PSYS(65M),经过了两级分频。在I2CCON寄存器中,第6位,第一级分频,得到中间时钟I2CCLK,然后在根据[3:0]的值,进行第二级分频。得到TX时钟。
5、S5PV210的IIC控制器
通信双方本质上是通过时序在工作,但是时序会比较复杂,不利于soc软件去实现,于是, soc内部内置了硬件的IIC控制器来产生通信时序。这样编程时,只需要向控制器的寄存器中写入相应值即可。控制器会产生适当的时序在通信线上和对方通信。
IIC控制器有4个IIC接口。
结构框图:
IIC控制器使用的是PCLK_PSYS时钟。经过一个4-bit的预分频器得到IIC通信的CLK时钟。通信中这个CLK会通过SCL传给从设备。
IIC总线控制逻辑(代表是I2CCON,I2CSTAT两个寄存器),主要负责产生I2C通信时序,实际编程中要发起始位、停止位、接收ACK等都是通过这两个寄存器实现。
移位寄存器(shift registe),实现将一个字节数据按位从SDA发送出去,或者从SDA按位接收一个字节数据。
地址寄存器+比较器,如果IIC控制器作为从设备的时候,需要一个地址来实现与主设备的通信。210可以通过这个地址寄存器来设置地址。
5.1、I2CCON寄存器
第七位:acknowledge generation, IIC控制器产生响应信号。当在发送的时候,IIC控制器不会控制SDA产生响应。,在读取的时候,主设备如果要继续读取数据,应该在读完一个数据后,发送ACK响应,如果不准备读取数据了,应该发送NACK响应,以提示从设备准备接收响应信号。所以在读操作最后一个数据之前,要将这一位置1,表示发送ACK,否则将这一位置0,表示发送NACK。
第六位: 设置IIC时钟的第一级分频。
第五位: 设置IIC中断使能/禁止功能,推荐将该位置1,即使不使用I2C中断功能。因为为0,I2CCON[4]会工作不正确。
第四位: IIC中断挂起标志位,为1表示IIC中断产生,此时SCL为低电平,IIC通信停止。写入0,清除中断标志位,重启IIC通信。
第三到0位: 设置预分频值。
Note中提到:
中断产生,Interrupt pending flag置1:
-
当1个字节传输或者接收完成,并且响应也处理完成,会产生中断
-
或者是IIC当做从设备,接收到地址和自己的地址匹配时,也会置1
-
总线仲裁错误
5.2、IICSTAT寄存器
第7、第6位:模式选择。选择当前IIC通信时以什么模式。有四种模式:从接收模式,从发送模式,主发送模式,主接收模式
第5位:忙信号状态。读的话,0忙,1不忙。写的话,写0表示发停止位、写1表示发开始位,然后再I2CDS中的数据会自动的传输出去。
第4位:数据输出使能、禁止。IIC通信的时候要使能,不传输的时候可以禁止。
第3位:仲裁状态标志。 0总线仲裁成功,1总线仲裁不成功。
第2位:作为从设备,判断外部发的地址是否和自己的地址匹配,1表示匹配。
第1位:接收的地址为0判断,为1,表示接收的地址是0.
第0位:上一次接收数据状态,其实就是判断接收到的响应是什么。为0,表示接收到响应,为1表示没有接收到响应。
5.3、I2CADDA
设置I2C控制器作为从设备时的地址
I2CDS: I2C上传输的数据,需要向外发送数据时,将数据写入到该寄存器中。当从外读取数据时,读取该寄存器。
6、210上gsensor传感器
九鼎开发板上的gsensor的原理图。使用IIC进行通信。 加速度传感器。
前面的芯片是一个电源芯片,通过PWMTOUT3来控制电源输出是否时能。通过控制PWMTOUT3,就可以实现gsensor芯片的上电和断电,从而节省功耗。
Gsensor的SDA和SCL使用的是s5pv210的I2C端口0。
编程时,要在gsensor_init函数中要去初始化相应的GPIO,要把相应的GPIO设置为正确的模式和输入输出值。
一般传感器的接口有两种:模拟接口和数字接口。模拟接口是用接口电平变化来作为输出(如模拟接口的压力传感器,在压力不同时输出电平在0~3.3V范围内变化,每一个电压对应一个压力),soc需要用AD接口来对接这种传感器对它输出的数据进行AD转换,准换得到数字电压值,再用数字电压值去校准得到压力值;数字接口是后来发展出来的,数据接口的sensor是在模拟接口的sensor基础上,内部集成了AD,直接(通过一定的总线接口协议,一般是IIC)输出数字值的的参数,外部soc直接通过总线接口,读取传感器输出的参数即可(如gsensor,电容触摸屏IC)。
该传感器的地址。数据手册KXTE9-2050 Specifications Rev 3的第8页,有说明该传感器的IIC地址是0001111。
IIC通信格式
S: 开始位
Sr: 重新发起开始为
P: 停止位
SAD + W: 地址加写操作
SAD + R: 地址加读操作
ACK: 从设备给主设备的响应
NACK: 主设备给从设备的响应。
DATA: 数据
通信速率,IIC最高400hz。
7、IIC总线的通信流程
7.1、210的主发送流程图
作为主设备发送的流程图。
在操作之前,会有一定的要求。
7.2、210的主接收流程图
作为主设备接收的流程图。
7.3、IIC读写一个字节实例代码
7.3.1、写一个字节
对于写,网上的参考代码:
自己写的代码,加入了超时判断,通过函数的返回值,可以知道是成功还是超时
写一个字节
int gsensor_write_byte(uint8_t add, uint8_t data) { int timeout =TIMEOUT_VALUE; volatile int i; int res=0; //发设备地址 I2CDS = SLAVE_ADDRESS_W; I2CSTAT = 0xf0; #ifdef DETECT_TIMEOUT while( (I2CCON&0x10) == 0){ //发设备地址响应超时判断,超时返回1 if(timeout-- == 0) { res = 1; return res; } delay_us(10); } #else while( (I2CCON&0x10) == 0); #endif if(I2CSTAT&0x01) { //如果设备地址(写)无响应,置为标志 res |= (1<<7 | 1<<3); } // printf("dev(write) I2CSTAT: 0x%x\r\n",I2CSTAT); //发数据地址 I2CDS = add; I2CCON &= ~(1<<4); //清除中断,重新发起写操作,写地址 #ifdef DETECT_TIMEOUT timeout =TIMEOUT_VALUE; while( (I2CCON&0x10) == 0){ //发数据地址响应超时判断,超时返回2 if(timeout-- == 0) { res = 2; return res; } delay_us(10); } #else while( (I2CCON&0x10) == 0); #endif if(I2CSTAT&0x01 ) { //如果数据地址无响应,置为标志 res |= (1<<7 | 1<< 5); } // printf("add I2CSTAT: 0x%x\r\n",I2CSTAT); //发送数据 I2CDS = data; I2CCON &= ~(1<<4); //清除中断,重新发起写操作,写数据 #ifdef DETECT_TIMEOUT timeout =TIMEOUT_VALUE; while( (I2CCON&0x10) == 0){ //发数据响应超时判断,超时返回3 if(timeout-- == 0) { res = 3; return res; } delay_us(10); } #else while( (I2CCON&0x10) == 0); #endif if(I2CSTAT&0x01 ) {//如果写数据无响应,置为标志 res |= (1<<7 | 1<< 6); } // printf("data(write) I2CSTAT: 0x%x\r\n",I2CSTAT); //发停止位 I2CSTAT = 0xd0; #ifdef DETECT_TIMEOUT timeout = TIMEOUT_VALUE; while( (I2CSTAT&0x20) == 0){ //判断IIC BUS不忙超时判断,超时返回4 if(timeout-- == 0) { res = 4; return res; } delay_us(10); } #else while( (I2CSTAT&0x20) == 0); #endif I2CCON = IIC_CON_VALUE; //重新设置I2CCON,为下一次做准备 for(i=0; i<50;i++); //延时,以保证两次IIC操作间隔不太快 return res; }
使用逻辑分析仪抓的波形。
第一个绿色部分,是发送的起始位,然后发送7位从设备地址和写使能,然后读取设备响应,再发送需要写入的地址,再接收读取设备响应。接着发送写的数据,写完之后,再接收响应,写完之后,发送结束位,表示写结束。
7.3.2、读一个字节
过程:
网上的代码如下:
自己写的代码,对于读一个字节
int gsensor_read_byte(uint8_t add, uint8_t *data) { int timeout =TIMEOUT_VALUE; int res=0; volatile int i; //发送设备地址(写) I2CDS = SLAVE_ADDRESS_W; I2CSTAT = 0xf0; #ifdef DETECT_TIMEOUT while( (I2CCON&0x10) == 0){ //发设备地址(写)响应超时判断,超时返回1 if(timeout-- == 0) { res = 1; return res;; } delay_us(10); } #else while( (I2CCON&0x10) == 0); #endif if(I2CSTAT&0x01) { //如果设备地址(写)无响应,置为标志 res |= (1<<7 | 1<<3); } // printf("dev(write) I2CSTAT: 0x%x\r\n",I2CSTAT); //发送读数据地址 timeout =TIMEOUT_VALUE; I2CDS= add; I2CCON &= ~(1<<4); #ifdef DETECT_TIMEOUT while( (I2CCON&0x10) == 0){ //发读数据地址响应超时判断,超时返回2 if(timeout-- == 0) { res = 2; return res;; } delay_us(10); } #else while( (I2CCON&0x10) == 0); #endif if(I2CSTAT&0x01 ) { //如果数据地址无响应,置为标志 res |= (1<<7 | 1<< 5); } // printf("add I2CSTAT: 0x%x\r\n",I2CSTAT); //重新产生开始信号,并重新发送设备地址(读) timeout =TIMEOUT_VALUE; I2CDS = SLAVE_ADDRESS_R; I2CSTAT = 0xb0; I2CCON &= ~(1<<4); #ifdef DETECT_TIMEOUT while( (I2CCON&0x10) == 0){ //发设备地址(读)响应超时判断,超时返回3 if(timeout-- == 0) { res = 3; return res;; } delay_us(10); } #else while( (I2CCON&0x10) == 0); #endif if(I2CSTAT&0x01) { //如果设备地址(读)无响应,置为标志 res |= (1<<7 | 1<<4); } // printf("dev(read) I2CSTAT: 0x%x\r\n",I2CSTAT); //读取数据,读数据时不产生ACK信号 timeout =TIMEOUT_VALUE; I2CCON &= ~(1<<7 | 1<<4); //清中断标志位,关闭ACK应答 #ifdef DETECT_TIMEOUT while( (I2CCON&0x10) == 0){ //读数据响应超时判断,超时返回4 if(timeout-- == 0) { res = 4; return res;; } delay_us(10); } #else while( (I2CCON&0x10) == 0); #endif if( !(I2CSTAT&0x01) ) { //如果读数据有响应,置为标志 res |= (1<<7 | 1<< 6); } // printf("data(read) I2CSTAT: 0x%x\r\n",I2CSTAT); *data = I2CDS; //发停止位 I2CSTAT = 0x90; timeout = TIMEOUT_VALUE; #ifdef DETECT_TIMEOUT while( (I2CSTAT&0x20) == 0){ //判断IIC BUS不忙超时判断,超时返回5 if(timeout-- == 0) { res = 5; return res;; } delay_us(10); } #else while( (I2CSTAT&0x20) == 0); #endif I2CCON = IIC_CON_VALUE; //重新设置I2CCON,为下一次做准备 for(i=0; i<50;i++); //延时,以保证两次IIC操作间隔不太快 return res; }
使用逻辑分析仪抓的波形。
第一个绿色部分,是发送的起始位,然后发送7位从设备地址和写使能,然后读取设备响应,再发送需要读取的地址,再接收读取设备响应。第二个绿色部分,重新发送起始位,然后发送7位从设备地址和读使能,接着读取设备响应,之后,就读取数据了,因为只读取一个数据,所需不需要发送ack,直接发送NAK。最后一个红色,表示结束位。
8、调试过程中,出现的问题
在IIC调试中,出现以下情况,SCL出现8个周期后就一直为高电平了。要等自己设置的超时时间后,才回归正常。
通过代码测试,发现将IIC的管脚的上拉使能,可以有效减小上面出现的情况。不过在最开始IIC数据传输的时候,也是有可能发生的。但是时间越久,发生的几率就越低了。
隔一段时间,就发现很正常了。
对于IIC器件,不能一直读取,否则会出现响应一直是NANK。