cortex-a8裸机系列:第十七章 IIC通信
0赞一、 IIC的总线空闲状态、起始位、结束位
IIC总线上有1个主设备,n(n>=1)个从设备。IIC总线上有2种状态;空闲态(所有从设备都未和主设备通信,此时总线空闲)和忙态(其中一个从设备在和主设备通信,此时总线被这一对占用,其他从设备不能工作)
整个通信分为一个周期一个周期的,两个相邻的通信周期是空闲态。每一个通信周期由一个起始位开始,一个结束位结束,中间是本周期的通信数据。
下图是起始位和结束位。
起始位:起始位是一个时间段,在这段时间内总线状态变化情况是:SCL线维持高电平,同时SDA发生一个从高到低的下降沿。
结束位:结束位也是一个时间段,在这段时间内总线状态变化情况是:SCL维持高电平,同时SDA发生一个从低到高的上升沿。
二、 IIC数据传输格式(数据位和ACK)
每一个通信周期的发起和结束都是由主设备来做的,从设备只有被动的响应主设备,没法自己自发的去做任何事情。
主设备在每个周期会先发8位的从设备地址(其实8位中只有7位是从设备地址,还有1位表示主设备要写入还是读取)到总线(主设备是以广播的形式发送,只要总线上的所有从设备都能收到这个信息),然后总线上的每个从设备都能收到这个地址,并且收到地址后,和自己的地址比较,看是否相等。如果相等,说明主设备本次通信就是和自己,如果不相等,说明不是和自己通信,忽略后续接收的所有数据。
主设备发送一段数据后,从设备(接收方)需要回应一个ACK。这个响应本身只有1个bit位,不能携带有效信息,只能表示要么收到数据,即有效响应;要么表示未收到响应,无效响应。
在某一个通信时刻,主设备和从设备只能有一个在发(占用总线,向总线写),另一个在收(释放总线,从总线读)。如果在某个时间主设备和从设备都试图向总线写那就出大问题了,通信就要出问题。
上图是IIC控制器能产生的四种时序。在真正和外部芯片使用IIC通信时,要将上述的四种时序进行组合实现读写。
对于1,将要写的地址写入I2CDS寄存器,将I2CSTAT写入0xf0,主发送模式。控制器会自动的产生开始信号,并将数据发送出去,接收响应信号。
对于2,将要写的地址写入I2CDS寄存器,将I2CSTAT写入0xb0,主接收模式。控制器会自动的产生开始信号,并将数据发送出去,接收响应信号。
对于3,将要写的数据写入I2CDS寄存器,将I2CCON的第4位中断标志位清零,控制器会自动将数据发送出去,接收响应信号。
对于4,直接去读I2CCON的第4位中断标志位,为1,说明数据接收完毕,控制器将接收到的数据放入到寄存器I2CDS中,并产生响应信号。
三、 数据在总线上的传输协议
IIC通信时基本的数据单位也是以字节为单位,每次传输的有效数据都是1个字节(8位)。
起始位后的8个clk都是主设备在发送(主设备掌控总线),此时从设备只能读取总线,通过读总线来得知主设备发给从设备的信息;到了第9周期,从设备需要发送ACK给主设备,此时主设备要释放总线,以接收从设备响应。从设备将SDA线拉低,表示响应。如果从设备没有拉低SDA线,发ACK。主设备就应该认为刚刚发送的数据不正确。
四、 IIC的时钟
IIC的时钟来源于PCLK_PSYS(65M),经过了两级分频。在I2CCON寄存器中,第6位,第一级分频,得到中间时钟I2CCLK,然后在根据[3:0]的值,进行第二级分频。得到TX时钟。
五、 S5PV210的IIC控制器
通信双方本质上是通过时序在工作,但是时序会比较复杂不利于soc软件完成,于是乎解决方案是soc内部内置了硬件的控制器来产生通信时序。这样编程时,只需要向控制器的寄存器中写入配置值即可。控制器会产生适当的时序在通信线上和对方通信。
IIC控制器有4个IIC接口。
结构框图
IIC控制器使用的是PCLK_PSYS时钟。经过一个4-bit的预分频器得到IIC通信的CLK时钟。通信中这个CLK会通过SCL传给从设备。
IIC总线控制逻辑(前台代表是I2CCON,I2CSTAT两个寄存器),主要负责产生I2C通信时序,实际编程中要发起起始位、停止位、接收ACK等都是通过这两个寄存器实现。
移位寄存器(shift registe,将寄存器中的字节数据,一位一位的发送出去),实现将数据按位从SDA发送出去。
地址寄存器+比较器,如果IIC控制器作为从设备的时候,需要一个地址来实现与主设备的通信。210可以通过这个地址寄存器来设置地址。
一、 I2CCON寄存器
第7位:acknowledge generation, IIC控制器产生响应信号。当在发送的时候,IIC控制器不会控制SDA产生响应。,在读取的时候,主设备如果要继续读取数据,应该在读完一个数据后,发送ACK响应,如果不准备读取数据了,应该发送NACK响应,以提示从设备准备接收响应信号。所以在读操作最后一个数据之前,要将这一位置0。
第六位: 设置IIC时钟的第一级分频
第五位: 设置IIC中断使能/禁止功能,推荐将该位置1,即使不使用I2C中断功能。因为为0,I2CCON[4]会工作不正确。
第四位: IIC中断挂起标志位,为1表示IIC中断产生,此时SCL为低电平,IIC通信停止。写入0,清除中断标志位,重启IIC通信。
第三到0位: 设置预分频值。
Note中提到:
中断产生,Interrupt pending flag置1:
l 当1个字节传输或者接收完成,并且响应也处理完成,会产生中断
l 或者是IIC当做从设备,接收到地址和自己的地址匹配时,也会置1。
l 总线仲裁错误。
二、 IICSTAT寄存器
第7、第6位:模式选择。选择当前IIC通信时以什么模式。有四种模式。
从接收模式,从发送模式,主发送模式,主接收模式
第5位:忙信号状态。读的话,0忙,1不忙。
写的话,写0表示发停止位、写1表示发开始位,然后再I2CDS中的数据会自动的传输出去。
第4位:数据输出使能、禁止。IIC通信的时候要使能,不传输的时候可以禁止。
第3位:仲裁状态标志。 0总线仲裁成功,1总线仲裁不成功。
第2位:作为从设备,判断外部发的地址是否和自己的地址匹配,1表示匹配。
第1位:接收的地址为0判断,为1,表示接收的地址是0.
第0位:上一次接收数据状态,其实就是判断接收到的响应是什么。为0,表示接收到响应,为1表示没有接收到响应。
三、 I2CADDA
设置I2C控制器作为从设备时的地址
I2CDS: I2C上传输的数据,需要向外发送数据时,将数据写入到该寄存器中。当从外读取数据时,读取该寄存器。
六、 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。
七、 IIC总线的通信流程
1. 210的主发送流程图
作为主设备发送的流程图。
在操作之前,会有一定的要求。
2. 210的主接收流程图
作为主设备接收的流程图。
3. IIC读写一个字节实例代码
1) 写一个字节
对于写,过程是
起始位 设备地址(写) 设备响应 数据地址 数据地址响应 数据 数据响应(从机发) 停止位
网上的代码就是
2) 读一个字节
过程
起始位 设备地址(写) 设备响应 数据地址 数据地址响应 重起始位 设备地址(读) 设备地址响应 数据 数据响应(主机发)
网上的代码如下:
自己写的代码,加入了超时判断,通过函数的返回值,可以知道是成功还是哪一个超时.:
写一个字节
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; |
使用逻辑分析仪抓的波形。
对于读一个字节
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; } |
使用逻辑分析仪抓的波形
八、 调试过程中,出现的问题:
在IIC调试中,出现以下情况,SCL出现8个周期后就一直为高电平了。要等自己设置的超时时间后,才回归正常。
通过代码测试,发现将IIC的管脚的上拉使能,可以有效减小上面出现的情况。不过在最开始IIC数据传输的时候,也是有可能发生的。但是时间越久,发生的几率就越低了。
隔一段时间,就发现很正常了。
对于IIC器件,不能一直读取,否则会出现响应一直是NANK。