CORTEX-A8裸机系列:第十七章 IIC通信

第十七章 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。

此条目发表在CORTEX-A8分类目录,贴了, , 标签。将固定链接加入收藏夹。

发表评论

电子邮件地址不会被公开。