I²C接口学习总结

1、IIC总线概念:
   a、只有两条总线线路:一条串行数据线,一条串行时钟线。
   b、每个连接到总线的器件都可以使用软件根据它们的唯一的地址来识别。
   c、传输数据的设备间是简单的主从关系。
   d、主机可以用作主机发送器或主机接收器。
   e、它是一个真正的多主机总线,两个或多个主机同时发起数据传输时,可以通过冲突检测和仲裁来防止数据被破坏。
   f、串行的8位双向数据传输,位速率在标准模式下可达100Kbit/s,在快速模式下可达400Kbit/s,在高速模式下可以达到3.4Mbit/s。
   g、片上的滤波器可以增加抗干扰能力,保证数据的完整。
   h、连接到同一总线上的IIC数量只受到总线的最大电容400pF的限制。
2、IIC总线的信号类型:
   开始信号(S):SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
   结束信号(P):SCL为高电平时,SDA由低电平向高电平跳变,结束数据传送。
   响应信号(ACK):接收器在接收到8位数据后,在第9个时钟周期,拉低SDA电平。
3、IIC总线数据传输格式:
   1、发送到SDA上的每个字节必须是8位的,每次传输发送的字节数量不受限制。
   2、每个字节后必须跟一个响应位。
   3、a:主机先发出S信号,b:然后发出8位数据,这8位数据中前7位为从机的地址,第8位表示传世的方向(0,表示写操作,1,表示读操作)c:被选中的从机发出响应信号,紧接着传输一系列字节及其响应位。d:主机发出P信号结束本次传输。
4、S3C2440总线控制器:
   1、IICCON寄存器:控制是否发出ACK信号,设置发送器的时钟,开启IIC中断,并标识中断是否发生。
   2、IICSTAT寄存器:选择IIC接口的模式,发出S信号、P信号,使能接收/发送功能,并标识各种状态,比如仲裁是否成功?作为从机时是否被寻址?是否接收到0地址?是否接收到ACK信号?
   3、IICADD寄存器:
   4、IICDS寄存器:发送和接收的数据。
5、S3C2440主机发送器工作流程:
   1、开始
   2、配置主机发送器 IIC初始化函数程序实现
   3、将从机地址写入IICDS IICDS = slvAddr; slvAddr从设备地址
   4、在IICSTAT中写入0xF0,启动传输 IICSTAT = 0xf0;
   5、IICDS中的数据被发送出去 在S3C2440上,启动之后,只要IICDS里面有数据就会被自动发送出去
   6、在响应周期之后发生中断 在发送IICDS中数据完成之后,最后一位是ACK响应位自动生成发送出去,跳转到 中断函数,执行中断函数
   7、如果没有数据要发送,则跳至11,否则跳至8 判断目前工作是否完成,选择下一步
   8、将下一个要发送的数据写入IICDS IICDS = data; data为发送的数据
   9、恢复IIC传输 IICCON = 0xaf; 0xaf值为恢复IIC传输配置值
   10、IICDS中的数据被发送出去 在S3C2440上,启动之后,只要IICDS里面有数据就会被自动发送出去
   11、在IICSTAT中写入0xD0,停止传输 IICSTAT = 0xd0; 0xd0值为停止传输配置值
   12、清除中断 跳转到中断函数,执行停止中断,SRCPND = BIT_IIC;INTPND = BIT_IIC;
   13、等待P信号有效 延时等待,Delay(10000);函数执行
   14、结束
6、一:EEPROM芯片介绍

在这里分析AT24C02A/AT24C04A/AT24C08A,对于其他不同型号的EEPROM芯片要根据具体手册进行分析。他们的大小分别是2K(256*8)/4K(512*8)/8K(1024*8)因此可以看出实际大小是256/512/1024byte,。对于AT24C02A的三位地址线都是写死的,因为在进行读写操作时使用8位地址已经足够,所以三位地址线写死作为片选,对于AT24C08A的三位地址线第一位必须写死,后两位可以作为内部页地址。因为AT24C08A的大小超过了256byte,8为寻址,已经没法使用到芯片内所有的空间。因此对于后面两位也就可以由程序决定了。

写EEPROM有两种,(在写数据的时候,AMR9作为主设备,EEPROM是从设备)

第一种写byte方式:



写一个byte实际上需要发送三次数据。在这个过程中,主设备为发送状态。第一个数据——设备地址。第二数据——ARM9想写的EEPROM中的地址。第三个数据——想写入到EEPROM中的具体数据。最后停止。

第二种写页方式:



自我感觉其实写页与写byte应该是一致的,第一个数据——设备地址。第二数据——ARM9想写的EEPROM中的地址(但是这个地址是首地址。AT24C02一页是8byte,AT24C04/08一页是16byte。所以在写页的时候最多写一页的大小,如果写太多就会重新又从首地址开始,以前写的会被覆盖掉。)。第三、四、······数据——就是你想写入到EEPROM中的数据。最后停止

读EEPROM中的数据

第一种读当前地址数据



主设备仍然是ARM9,从设备是EEPROM,但是要注意主设备的状态,有时候会是发送状态,有时候会是接收状态

第一个数据——(主设备现在处于发生状态)发送从设备地址,并且把主设备配置为接收状态。

第二个数据——(主设备处于接收状态)ARM9接收数据,注意此时是NO ACK。再停止。(要在产生NO ACK后在读取数据这时数据会是稳定的。网上有问为什么在读IIC最后需要读两次,我自己实验了,只需要最后一次就行,)

第二种随机读数据方式



第一个数据——(主设备处于发生状态),发送一个从设备地址。第一个设备地址是用来从设备匹配的,也在文档中被称为a “dummy” byte write sequence

第二个数据——(主设备处于发生状态),发送一个想读取数据在EEPROM中的地址。

第三个数据——(主设备处于发生状态),发送一个从设备地址。这是特定要求这样发送的。。(在这里主设备会被配置为接收状态),这此发送设备地址是用来同时调整主设备状态的。

第四个数据——(主设备处于接收状态)需要读的数据。也是一个NO ACK,与读当前地址类似。最后再停止。

第三种读序列地址



与读当前数据有些类似。

第一个数据——(主设备处于发送状态),发出设备地址,并配置主设备为接收状态。为后面接收数据准备

第二、三···个数据——(主设备处于接收状态),前面每个数据都会发送ACK,最后一个数据是一个NO ACK。

再停止。

以上这些,主要要注意主设备状态的调整,以及为NO ACK时的处理,后面有事例程序,能够比较清楚的看到怎么进行处理的。


二、S3C2440中对于IIC需要配置的寄存器

GPECON,主要是把这个GPIO配置为IIC模式。

IICCON:其中[0]---[3]与[6]共同决定IIC总线的时钟频率。

[4]是一个中断标志位,我们如果没有用中断方式的话,应该可以通过查询这一位进行。(我用的中断,没有具体自己实践)

[5]IIC中断使能。[7]是否发送ACK。这一位在后面读数据的时候,要注意进行改变。

IICSTAT:这个寄存器主要是一些标志为,不需要配置,主要要配置的是这几位。

[4]使能IIC数据线的,使其能够发送数据。

[5]启动和停止IIC,1启动。0停止。

[6-7]是配置AMR9的状态的,一般CPU是一个主设备的角色。只有在两块CPU进行相互通信的时候,可能把他配置成为一个从设备的状态。所以在我们实验中,ARM9全部都是处于主设备的角色。

IICADD是CPU做从设备的时候,给他配置的从设备地址,这里可以不用配置。

IICDS:数据移位寄存器。发送数据就是把数据发到这个寄存器。接收数据就是从这个寄存器中去取数据。

如果使用中断当然还得配置INTMSK,打开IIC中断。



三:IIC成功读写EEPROM的程序

首先要对程序有几点说明:

1:ACK必须是volatile类型,因为在中断中改变了值,不然值被保存在缓存中了,最后检测时,不能真正读到其值。
2:IIC的中断总是在ACK周期内,产生的,我没有贴出操作流程图,ARM9文档中IIC这章已经清楚给出。所以在有ACK的那些数据发送与接收都可以用中断操作,但是从读数据的后接收数据来看,由于是NO ACK,所以就没有用中断操作了,而且自己进行了一个延时。在读数据。

void Test_Iic(void)
{
    unsigned int i,j,save_E,save_PE;
    static U8 data[256];

    Uart_Printf("\nIIC Test(Interrupt) using AT24C02\n");

    save_E = rGPECON;
    save_PE = rGPEUP;
        IIC_Init(); //初始化IIC必须的一些寄存器
    Uart_Printf("Write test data into AT24C02\n");

    for(i=0;i<48;i++)
        Wr24C080(0xa0,i,i); //slvaddr, addr, data
                                                                                                        
           
    for(i=0;i<48;i++)
        data[i] = 0;

    Uart_Printf("Read test data from AT24C02\n");
    
    for(i=0;i<48;i++)
        Rd24C080(0xa0,i,&(data[i]));

        //Line changed 0 ~ f
    for(i=0;i<3;i++)
    {
        for(j=0;j<16;j++)
            Uart_Printf("%2x ",data[i*16+j]);
        Uart_Printf("\n");
    }
    rINTMSK |= BIT_IIC;
    rGPEUP = save_PE;
    rGPECON = save_E;
}


  
void IIC_Init(void)
{
        //配置GPE端口为IIC功能
        rGPECON &=~(0xF<<28);
        rGPECON |=(1<<31)|(1<<29);
//产生ACK,IIC中断使能,频率200KHz
        rIICCON = 0;
        rIICCON |=(7)|(1<<5)|(1<<7);
        //模式为主发送,使能Rx/Tx (不管是读还是写初始化都为主发送)
        rIICSTAT |=(3<<6)|(1<<4);
        rIICADD = 0x10;//从地址 表示2440作为从设备的时候的地址,
//在这里2440是作为一个主设备存在的,所以没有作用。
//EEPROM的标识符为1010
//控制字节,其中高四位为器件类型标识符,后三位作为片选
//最后一位决定读写,0是读,1是写。

//IIC传输中断开启
        rINTMOD=0x0;
        rINTMSK &=~BIT_IIC;
        pISR_IIC = (unsigned)IicInt;
}


  
//*************************[ Wr24C080 ]****************************
void Wr24C080(U32 slvAddr,U32 addr,U8 data)
{
    f_GetACK = 0;
        rIICDS = slvAddr; //发送第一个数据
        rIICSTAT = 0xf0;
        while(f_GetACK == 0); //等待发送结束
        f_GetACK = 0;
        rIICDS = addr; //发送第二个数据
        rIICCON = 0xaf;
        while(f_GetACK == 0); //等待发送结束
        f_GetACK = 0;
        rIICDS = data; //发送第三个数据
        rIICCON = 0xaf;
        while(f_GetACK ==0); //等待发送结束
        rIICSTAT = 0xd0; //停止IIC
        rIICCON = 0xaf;
        Delay(3);
}


  
void Rd24C080(U32 slvAddr,U32 addr,U8 *data)
{
    char cRecvByte;

        f_GetACK = 0;

        rIICDS = slvAddr; //发送第一个数据
        rIICSTAT = 0xf0;
        while(f_GetACK==0); //等待结束
        f_GetACK = 0;
        rIICDS = addr; //发送第二个数据
        rIICCON = 0xAF;
        while(f_GetACK==0); //等待结束
        f_GetACK = 0;
        rIICDS = slvAddr; //发送第三个数据
        rIICSTAT = 0xb0; //配置主设备状态为接收
        rIICCON = 0xaf;
        while(f_GetACK==0); //等待结束
        f_GetACK = 0;
        rIICCON = 0x2f; //NO ACK配置,在接收完数据不发送ACK信号
        Delay(2); //等待其稳定,延时不要求精确
        cRecvByte = rIICDS; //接收第四个数据
        rIICSTAT = 0x90; //停止IIC
        rIICCON = 0xaf;
        Delay(3);
        *data = cRecvByte;
}


  
void __irq IicInt(void)
{
        ClearPending(BIT_IIC);
        f_GetACK = 1;
}

 感悟:
      1、在嵌入式开发关于总线协议类的,需要先看总线协议的发送接收流程操作,再次是看主机(或者直接是CPU)的文档,了解其操作原理和流程,然后是看从机的文档,操作原理和流程,最后是开始用代码去对接。
      2、一定要注意使用结构体和关键字volatile,尽可能的分开每一细小功能为函数去写。
      3、必须清楚流程中的哪些动作是自动完成?哪些是需要用代码配置设置后完成?
      4、注意函数变量的使用类型。
      5、尽量使用多文件去声明变量和函数。
      6、一切和硬件相关的地址最好在.h文件中使用宏定义去定义新名称,一般采用大写字母。
      7、在RTC时钟设置时候,需要同时发送写入好多数据,可以用结构体、数组,自加或者自减去实现。在写EEPROM的时候,写入函数需要三个参数,需要多次发送数据,注意需要在发送完之后恢复ACK值,恢复IIC传输。
      8、在程序中使用标记ACK变量,比如:while(ACK == 0);当进入循环的时候,一直在循环,数据也就在发送过程中,直至发送完毕后,响应周期之后会发生中断,然后会跳到中断执行,将ACK标记成1,此时,循环停止。若不加循环等待数据传输是否完成,此时很快会执行下一句程序,可能数据还没有完全发送完毕。
      9、如果是一次写一页的时候,就需要多发送好几次,数据三···数据四···数据五···然后结束传输,因为IIC总线一次只传输8字节,这时候之前所输入的地址就是首地址。
      10、一次读一页的时候,用一个数组去接收,多读出几次,地址要自加,这样就能读出多个数据了。

posted on 2016-06-19 17:35  让编程成为一种习惯  阅读(2056)  评论(0编辑  收藏  举报