22 IC总线AT24C02芯片应用

第8章 I2C总线AT24C02芯片应用

8.1I2C总线概述

  1. I2C总线介绍

    I2C总线是近年来微电子通讯控制领域广泛采用的一种新型总线标准,它是同步通信的一种特殊形式,具有接口线少、控制简单、器件封装形式小、通信速率较高等优点。在主从通信中,可以有多个I2C总线器件同时接到I2C总线上,所有与I2C兼容的器件都具有标准的接口,通过地址来识别通信对象,使它们可以由I2C总线互相直接通信。

    I2C总线有数据线SDA和时钟线SCL两条线构成通信线路,即可以发送数据,也可以接收数据。在CPU与被控IC(被控元器件)之间、IC与IC之间都可以进行双向传送,各种被控器件均并联在总线上,但每个器件都有唯一的地址。在信息传输过程中,I2C总线上并联的每个器件,计时被控器(或主控器),也是发送器(或接收器),这取决于它要完成的功能。CPU发出的控制信号分为地址码和数据码两部分:地址码用来选址,即接通需要控制的电路;数据码是通信的内容,这样各IC控制电路虽然挂在同一条总线上,却彼此独立。

  2. I2C总线硬件结构

    image-20200925205701941

    SCL是时钟线,SDA是数据线。

    总线上各器件采用漏极**开路结构(漏极开路(Open Drain)即高阻状态,适用于输入/输出,其可独立输入/输出低电平和高阻状态,若需要产生高电平,则需使用外部上拉电阻或使用如LCX245等电平转换芯片。)**结构与总线相连,因此SCL和SDA均需接上拉电阻。总线在空闲状态下均保持高电平,连到总线上的任一器件输出的低电平,都将使总线的信号变低。

    I2C总线支持多主和主从两种工作方式,通常为主从工作方式。**在主从工作方式中,系统中只有一个主器件(单片机),其他器件都是具有I2C总线的外围从器件。**在主从工作方式中,主器件启动数据的发送(发送启动信号),产生时钟信号,发出停止信号

  3. I2C总线通信格式

    下图为I2C总线上进行一次数据传输的通信格式。

    image-20200925210622942

  4. 数据位的有效性规定

    I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟信号为低电平期间,数据线上的高电平或低电平状态才允许变化。

    image-20200925210850286

  5. 总线空闲状态

    I2C总线的SDA和SCL两条线处于高电平的时候,规定为总线的空闲状态,也称为释放总线。同理,SDA和SCL哪条线处于高电平了,哪条线也就是被释放了。

  6. 发送启动信号

    再利用I2C总线进行一次数据传输时,先有主机发出启动信号,启动I2C总线。在SCL为高电平期间,SDA出现下降沿则为启动信号。此时具有I2C总线接口的从器件会检测到该信号

    image-20200925211054995

  7. 发送寻址信号

    主机发送启动信号后,在发出寻址信号。器件地址有7位和10位,这里介绍7位。寻址字节的位定义如下。

    image-20200925211327807

    寻址信号由一个字节构成,高7位为地址位,最低位为方向位,用以表明主机与从器件的数据传送方向。方向位为0时,表明主机接下来对从器件进行写操作;方向位为1,表明主机接下来对从器件进行读操作。

    主机发送地址时,总线上的每一个从机都将这7位地址码与自己的地址进行比较,如果相同,则认为自己正被主机寻址,根据方向位(R/W位)将自己确定为发送器或接收器。

    从机的地址码由固定部分和可编程部分组成,其中可编程部分决定了可接入总线该类器件的最大数目。如果一个从机的7位寻址位有4位是固定位,3位是可编程位,这是仅能寻址8个同样的器件,即可有8个同样的器件接入到该I2C总线系统中。

  8. 应答信号

    I2C总线协议规定,每传送一字节数据(含地址及命令字)(8个时钟周期)后,由接收器反馈一个应答信号(第9个时钟周期),已确定数据传送是否被对方受到,**应答信号由接收设备产生,在SCL信号为高电平期间,接收设备将SDA拉为低电平,也就是应答信号为低电平,表示数据传输正确,产生应答。应答信号为高电平时,规定为非应答信号,一般表示接收器没有成功接收该字节。**当主机作为接收设备时,主机对最后一字节不应答, 以向发送设备表示数据传送结束。

    无论是以上那种情况,都会终止数据传输,这时主机或是产生停止信号释放总线,或是产生重新开始信号开始一次新的通信。

    image-20200926103003869

  9. 数据传输

    主机发送寻址信号,并得到从器件应答后,便可进行数据传输,每次1位,每个位传输时间对应一个时钟周期,也就是一次时钟脉冲送一个位,那么送完一个字节要8个位,也就是8个时钟周期,那么在第9个时钟周期的时候释放数据线SDA,由接收器反馈一个应答信号,但每次传输都应在得到应答信号后再进行下一字节传送。

  10. 发送停止信号

    在全部数据传送完毕后,主机发送停止信号,即在SCL为高电平期间,SDA上产生一个上沿信号,停止时序图如下。

    image-20200926103611192

8.2单片机模拟I2C总线通信

市面上许多单片机是由I2C总线接口的,工作的时候总线的状态有硬件检测控制,不用人为干涉。但是我们51单片机是没有I2C总线接口的,但是我们可以通过软件模拟I2C总线的工作时序,从而达到控制I2C总线接口器件。

为了保证数据传送的可靠性,标准I2C总线的数据传送有严格的时序要求。单片机在模拟I2C总线通信时,需写出如下关键部分的程序:总线初始化、启动信号、应答信号、停止信号、写1字节、读1字节。

  1. 总线初始化

    void init()
    {
        SCL=1;
        delay();
        SDA=1;//将总线都拉高以释放总线
        delay();
    }
    
  2. 启动信号

    void start()
    {
        SDA=1;
        delay();
        SCL=1;
        delay();
        SDA=0;
        delay();
    }
    
  3. 应答信号

    void respons()
    {
        unsigned char i=0;
        SCL=1;
        delay();
        while((SDA==1)&&(i<256))
            i++;
        SCL=0;
        delay();
    }
    

    这里我们要注意一点,我们接收应答信号,是从器件将SDA拉低,所以我们这里是要接受到SDA拉低的信息。如果少了这个while函数,那么主机收不到应答信号时,就会一直停留在这里,我们让他一段时间接收不到应答信号后就自动退出。

  4. 停止信号

    void stop()
    {
        SDA=0;
        delay();
        SCL=1;
        delay();
        SDA=1;
        delay();
    }
    
  5. 写字节

    1. 主机在检测到总线为“空闲状态时”(即SDA、SCL均为高电平)时,发送一个启动信号,开始通信

    2. 主机接着发送一个寻址信号。该字节由7位外围器件地址和1位读写控制位R/W组成(此时R/W=0,表示主机对从机进行写操作)

    3. 相对应的从机收到寻址信号后向主机回馈应答信号 ACK(ACK=0),寻址信号也是1字节,所以也要反馈

    4. 主机收到从机的应答信号后开始发送第一个字节的数据

    5. 从机收到数据后返回一个应答信号 ACK

    6. 主机收到应答信号后再发送下一个数据字节

    7. 当主机发送最后一个数据字节并收到从机的 ACK 后,通过向从机发送一个停止信号P结束本次通信并释放总线。从机收到P信号后也退出与主机之间的通信

      image-20200927102843664

    void write_byte()
    {
        unsigned char 1,temp;
        temp=date;
        for(i=0;i<8;i++)
     {
         temp=temp<<1;
         SCL=0;//这里发送数据的时候,SCL得是低电平才行,因为SDA送数据是送0,1的,只有SCL=0,SDA的电平才能改变;其实看前面应答信号下的图,你会发现,在刚刚送进去一个位时,SCL永远是低电平,然后在送的中途,它又是高电平,然后下一个字节开始了,又变成低电平。这里置零准确来说是为了第一位数据的传送,后面的它自己会变的。
         delay();
         SDA=CY;//这里你就理解成,temp那8位,从最高位开始一个个进入CY,然后就送到SDA上发出去
         delay();
         SCL=1;
         delay();
     }
        SCL=0;
        delay();
        SDA=1;
        delay();//这个SCL=0;SDA=1目的是释放数据线,使SDA处于空闲状态,为下次数据线的传输数据做准备。
    }
    

    串行发送1字节时,需要把其中的8位一位位地发出去,“temp=temp<<1”表示将temp左移一位,最高位将移入PSW寄存器的CY为中,然后将CY赋给SDA进而在SCL的控制下发送出去。

    PSW寄存器

    PSW(程序状态标志寄存器)是一个8位寄存器,位于单片机的特殊功能寄存区,字节地址为D0H,用来存放运算结果的一些特征,如有无仅为,借位等。使用汇编语言是,PSW寄存器很有用,但在利用c语言编程时,编译器会自动控制该寄存器,很少有人人为操作它。

    image-20200926164220726

    • CY——进位标志位,表示运算是否有进位(或借位)。这里使用的使它的第二个功能,用于寄存器中的移位操作。
    • 其他的先不讲了吧,暂时用不上。

    移位操作

    1. 左移

      C51中的操作符号是“<<”,每执行一次左移指令,被操作的数(二进制)将最高位移入单片机PSW寄存器的CY位,CY位中原来的数丢弃,最低位补0,其他位依次向左移动一位。

      CY最高位最低位
      X(可1可0)01101011

      左移后变成

      CY最高位最低位
      011010110
    2. 右移

      这个和左移其实是同理的,只不过变成右移,然后将最低位放入CY中,然后最高位补0。

    3. 循环右移和循环左移

      这两个不同于上面两个操作,是真的循环,最高位去到最低位或者最低位去到最高位,就是一个循环的样子。循环右移在C51库中有现成函数 _ crol_,循环左移的话就是 _cror _。直接使用就好了。

      使用起来是x=_crol _(x,n);,这个表示的是将x个8位数据左移n位。

    4. 例子

      用循环右移库函数来实现流水灯

      #include <reg52.h>
      #include <intrins.h>//包含_crol_函数所在的头文件
      #define uint unsigned int
      #define uchar unsigned char
      void delayms(uint);
      uchar aa;
      void main()
      {
          aa=0xfe;
          while(1)
          {
              P1=aa;
              delayms(500);
              aa=_crol_(aa,1);
          }
      }
      void delayms(uint xms)
      {
          uint i,j;
          for(i=xms;i>0;i--)
              for(j=xms;j>0;j--);
      }
      
  6. 读1字节

    1. 主机发送启动信号后,接着发送命令字节(其中 R/W=1)

    2. 对应的从机收到地址字节后,返回一个应答信号并向主机发送数据

    3. 主机收到数据后向从机反馈一个应答信号

    4. 从机收到应答信号后再向主机发送下一个数据

    5. 当主机完成接收数据后,向从机发送一个“非应答信号(ACK=1)”,从机收到ACK=1 的非应答信号后便停止发送

    6. 主机发送非应答信号后,再发送一个停止信号,释放总线结束通信

      image-20200927102959415

    uchar read_byte()
    {
        unsigned char i,k;//这里定义变量,其实就是定义了单片机中的一个缓冲寄存器,在RAM中的寄存器,默认8位都是0.那么下面的解释的通了
        SCL=0;
        delay();
        SDA=1;//这里释放数据线,让他之后的电平由信号决定
        for(i=0;i<8;i--)
        {
            SCL=1;//这里将SCL拉高,上升沿相当于驱动力或者说是协议,唤醒对方处理,就是告诉被读器件要开始读了
            delay();
            k=(k<<1)|SDA;//由于k的8位全都是0,通过或运算就可以全部接收了
            SCL=0;//这里告诉它我读完了
            delay();
        }
        delay();
        return k;
    }
    

    串行接收1字节时需要将8位一位一位地接收,再组合成1字节。上面定义一个临时变量k,将k左移一位后于SDA进行或运算,一次吧8个独立的位放入以字节中来完成接收。

8.3E2PROM AT24C02与单片机的通信实例

E2PROM:电可擦可编程只读存储器

E2PROM拥有I2总线接口,ATMEL公司生产的AR24C系列E2PROM,主要型号有AT24C01/02/04/08/16,02对应的储存容量是256X8。采用这类芯片可以解决掉电数据保存问题,可对所存数据保存100年,并可多次擦写,擦写次数可达10W次以上。

1.AT24C02引脚配置与引脚功能

AT24C02芯片的常用封装形式有直插式和贴片式两种。如下图所示

image-20200927161453329

下图是它的引脚图

image-20200927161559412

各引脚功能如下:

  • 1、2、3引脚(A0、A1、A2)——可编程地址输入端
  • 4(GND)——电源接地端
  • 5(SDA)——串行数据输入/输出端
  • 6(SCL)——串行时钟输入端
  • 7(WP)——写保护输入端,用于硬件数据保护。其为低电平时,可以对整个存储器进行正常的读/写操作;为高电平时,存储器具有写保护功能,但读操作不受影响。
  • 8(VCC)——电源正端

2.存储结构与寻址

AT24C02的存储容量为2Kb,内部分成32页,每页8B,共256B(这里注意一下,存储容量为2Kb等于2048位,256B表示256个字节,小写b表示bit,位;大写B表示byte,字节),操作时有两种寻址方式:芯片寻址和片内子地址寻址

  1. 芯片寻址

    AT24C02的芯片地址为1010,期地址控制字格式为1010A2A1A0R/W,其中A2、A1、A0为可编程地址选择位。A2、A1、A0引脚接高、低电平后得到确定的3位编码,与1010形成7位编码,即该器件的地址码。R/W为芯片读写控制位,该位为0,表示对芯片进行写操作;该位为1,表示对芯片进行读操作。

  2. 片内子地址寻址

    芯片寻址可对内部256B中的任何一个进行读/写操作,其寻址范围为00~FF,共256个寻址单元

3.读/写操作时序

串行E2PROM一般有两种写入方式:字节写入方式、页写入方式。

页写入方式允许在一个写周期(10ms)左右对1字节到1页的若干字节进行编程写入,AT24C02的页面大小为8B。采用页写入方式可提高写入效率,但容易发生事故。AT24C系列芯片内地址在接收到每个数据字节后自动加1,故装载一页以内数据字节时,只用输入首地址。如果写到此页的最后1字节,主器件继续发送数据,数据将重新从该页的首地址写入,进而造成原来的数据丢失,这就是页地址空间的“上卷”现象。(这个就是说只要输入每页首地址,数据就会从首地址自动的一个个排下去,如果排满了,会从这一页的首位再开始接受,那么原来的首位就消失了,就是这个意思

解决“上卷”的方式:在第8个数据后将地址强制加1,或是将下一页的首地址重新赋给寄存器。

  1. 字节写入方式

    单片机在一次数据帧中只访问E2PROM一个单元。该方式下,单片机先发送启动信号,然后送1字节的控制字(1010A2A1A0R/W这个,声明控制这个芯片),再送1字节的存储器单元子地址(00~FF)。上述2个字节都得到E2PROM响应后,发送8位数据,最后再发送1位停止信号。

    发送流程如下

    image-20200927164737976

  2. 页写入方式

    单片机在一个数据写周期内,可以连续 访问1页(8个)E2PROM存储单元,单片机先发送启动信号,接着送1字节的控制字,再送1字节的存储器起始单元地址。上述字节都得到E2PROM应答后,就可以发送最多1页的数据,并顺序存放在以指定起始地址开始的相继单元,最后以停止信号结束。

    流程如下
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-REdplo3X-1601302118650)(C:/Users/Asus/AppData/Roaming/Typora/typora-user-images/image-20200927165030037.png)]

  3. 指定地址读操作

    读指定地址单元的数据。单片机在启动信号后先发送含有片选地址的写操作控制字,E2PROM应答后发送1字节的指定单元地址,再发送一个含有片选地址的读操作控制字。如果E2PROM做出应答,被访问单元的数据就会按SCL信号同步出现在串行数据/地址线SDA上。(这里简单来说,写入器件地址,然后写入器件内部地址,然后让给单片机一个读的指令,让单片机读取该地址器件内部的地址上的数据,就酱)

    这种读操作的数据帧格式如下

    image-20200927170735700

  4. 指定地址连续读

    该方式的读地址控制与前面指定地址读相同。单片机接收到每个字节数据后应做出应答,只要E2PROM检测到应答信号,其内部的地址寄存器就自动加1,指向下一个单元,并顺序将指向的单元的数据送到SDA串行数据线上。当需要结束读操作时,单片机接收到数据后在需要应答的时刻发送一个非应答信号,在发送一个停止信号即可。

4.TX-1C实验板与AT24C02连接

实例:利用定时器产生一个0~99秒变化的秒表,并且显示在数码管上,每过1秒,将这个变化的数写入板上AT23C02内部。当关闭实验板电源,并再次打开实验板电源时,单片机先从AT24C02中将原来写入的数读出来,接着此数继续变化并显示在数码管上

#include<reg52.h>
#define uchar unsigned char
#define uint unsigned int 
bit write=0;//定义了一个单个的二进制位,这个位的值初始是0,然后要么1要么0
sbit sda=P2^0;
sbit scl=P2^1;
sbit dula=P2^6;
sbit wela=P2^7;
uchar sec,tcnt;
uchar code table[]={0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 
                    0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71};
void delay(){;;}
void delay1ms(uint z)
{
    uint x,y;
    for(x=z;x>0;x--)
        for(y=110;y>0;y--);
}
void start()//启动信号
{
    sda=1;
    delay();
    scl=1;
    delay();
    sda=0;
    delay();
}
void stop()//停止信号
{
    sda=0;
    delay();
    scl=1;
    delay();
    sda=1;
    delay();
}
void respons()//应答信号
{
    unsigned char i;
    scl=1;
    delay();
    while((sda==1)&&(i<256))
    {
        i++;
    }
    scl=0;
    delay();
}
void init()//总线初始化
{
    scl=1;
    delay();
    sda=1;
    delay();
}
void write_byte(uchar date)//写操作
{
    unsigned char i,temp;
    temp=date;
    for(i=0;i<8;i++)
 {
     temp=temp<<1;
     scl=0;
     delay();
     sda=CY;
     delay();
     scl=1;
     delay();
 }
    scl=0;
    delay();
    sda=1;
    delay();
}
uchar read_byte()
{
    unsigned char i,k;
    scl=0;
    delay();
    sda=1;
    delay();
    for(i=0;i<8;i++)
    {
        scl=1;
        delay();
        k=(k<<1)|sda;
        scl=0;
        delay();
    }
    delay();
    return k;
}
void write_add(uchar address,uchar date)//字节写入格式格式
{
    start();
    write_byte(0xa0);//10100000
    respons();
    write_byte(address);
    respons();
    write_byte(date);
    respons();
    stop();
}
uchar read_add(uchar address)
{
    uchar date;
    start();
    write_byte(0xa0);
    respons();
    write_byte(address);
    respons();
    start();
    write_byte(0xa1);
    respons();
    date=read_byte();
    stop();
    return date;    
}
void display(uchar shi_c,uchar ge_c)//显示程序
{
    dula=0;
    P0=table[shi_c];//显示第一位
    dula=1;
    dula=0;
    wela=0;
    P0=0xfd;//11111101
    wela=1;
    wela=0;
    delay1ms(500);
    dula=0;
    P0=table[ge_c];//显示第二位
    dula=1;
    dula=0;
    wela=0;
    P0=0xfe;//11111110
    wela=1;
    wela=0;
    delay1ms(500);
}
void main()
{
    init();
    sec=read_add(2);//读出保存的数据赋予sec
    if (sec>100)//防止首次读取出现错误
    {
        sec=0;
    }
    TMOD=0x01;//定时器工作在方式1
    ET0=1;
    EA=1;
    TH0=(65536-50000)/256;
    TL0=(65536-50000)%256;//定时器0.05秒中断一次
    TR0=1;//启动定时器
    while(1)
    {
        display(sec/10,sec%10);
        if(write==1)//判断计时器是否计时1秒
        {
            write=0;//清0
            write_add(2,sec);//在24c02的地址2中写入数据sec
        }
    }
}
void t0() interrupt 1//定时器中断服务函数
{
    TH0=(65536-50000)/256;
    TL0=(65536-50000)%256;
    tcnt++;//每过50ms,tcnt加1
    if(tcnt==20)
    {
        tcnt=0;//重新再计
        sec++;
        write=1;//1秒写一次24c02
        if(sec==100)//定时100秒,再从零开始计时
        {
            sec=0;
        }
    }
}
  1. 这里面void delay(;;)是一个微秒级别的延时函数,用空语句来实现短时间延时。,在晶振为11.0592MHz时,该延时函数延时大概4~5μs,用来操作I2C总线。
  2. 在主程序的开始出先读取上次写入AT24C02中的数据,下面两句是为了防止第一次操作AT24C02时出现读取数据大于100的情况,导致数码管乱码。

版权声明:本文为qq_46440098原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。