zoukankan      html  css  js  c++  java
  • MCU 51-7 I2C Communication EEPROM

    学习通信协议: 买一块 逻辑分析仪,示波器(太贵,用公司或实验室)

    目前常用的微机与外设之间进行数据传输的串行总线主要有I2C总线、SPI总线和SCI总线。

    其中I2C总线以同步串行2线方式进行通信(一条时钟线,一条数据线)。

    SPI总线则以同步串行3线方式进行通信(一条时钟线,一条数据输入线,一条数据输出线)。 SCI总线是以异步方式进行通信(一条数据输入线,一条数据输出线)。

    1-wire,即单线总线,又叫单总线。

    IIC Communication

    I2C总线是PHLIPS公司推出的一种串行总线,它只有两根双向信号线。一根是数据线SDA(serial data I/O),另一根是时钟线SCL(serial clock)。

    如下图所示,IIC总线上可以挂多个器件,而每个器件都有唯一的地址,这样可以标识通信目标。数据的通信的方式采用主从方式,主机负责主动联系从机,而从机则被动回应数据。

    I2C总线通过上拉电阻接正电源。当总线空闲时,两根线均为高电平。连到总线上的任一器件输出的低电平,都将使总线的信号变低,即各器件的SDA及SCL都是线“与”关系。

    开发板上的 I2C总线以及总线上的设备AT24C02

    A0A1A2: 000 则从机AT24C02的地址:0x00

    AT24C02是一个2K位串行CMOS E2PROM, 内部含有256个字节,CATALYST公司的先进CMOS技术实质上减少了器件的功耗。AT24C02有一个8字节页写缓冲器。该器件通过IIC总线接口进行操作,有一个专门的写保护功能。

    EEPROM (Electrically Erasable Programmable read only memory)是指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。 EEPROM 可以在电脑上或专用设备上擦除已有信息,重新编程。一般用在即插即用。

     

    I2C总线传输协议

    数据位的有效性规定

    SCL为高电平期间,数据线上的数据必须保持稳定,只有SCL信号为低电平期间,SDA状态才允许变化。

    I2C的起始和终止信号

    SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。

    I2C字节的传送与应答

    每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。

    I2C字节的传送与应答

    每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。

    应答位的作用

    主机在发送数据时,每次发送一字节数据,都需要读取从机应答位,当从机空闲可以接收该字节数据时,从机会发出应答(一帧数据的第9位为“0”),当从机正忙于其他工作的处理来不及接收主机发送的数据时,从机会发出非应答(一帧数据的第9位为“1”)主机则应发出终止信号以结束数据的继续传送,主机通过从机发出的应答位来判断从机是否成功接收数据。

    当主机接收数据时,它收到最后一个数据字节后,必须向从机发出一个结束传送的信号。这个信号是由对从机的“非应答”来实现的。然后,从机释放SDA线,以允许主机产生终止信号。

    I2C写数据流程

    在起始信号后必须传送一个从机的地址(7位)我们开发板上的AT24C02地址为0xa0,第8位是数据的传送方向位(R/T),用“0”表示主机发送数据(T),“1”表示主机接收数据(R)。

    I2C读数据流程

    在读数据时也要先发送器件地址,读写方向为写,因为我们下一帧需要发送从AT24C02内那个单元开始读,之后需在发一次器件地址这个时候读写方向就为读了,接着我们就可以从总线上读取数据。

    软件模拟I2C通信时序

    I2C总线的数据传送有严格的时序要求。I2C总线的起始信号、终止信号、发送“0”及发送“1”的模拟时序 :

    # include <reg52.h>
    # include <intrins.h>
    
    #define uchar unsigned char
    #define uint unsigned  int
    #define AT24C02_ADDR  0xa0    //AT24C02地址
    
    /*I2C IO口定义*/
    sbit SDA = P2^0;
    sbit SCL = P2^1;
    
    /*5us延时*/
    void delay_5us()  
    {
        _nop_();
    }
    
    /*1Ms延时*/
    void delay(uint z)
    {
        uint x,y;
        for(x = z; x > 0; x--)
            for(y = 114; y > 0 ; y--);
    }
    
    /*I2C初始化*/
    void I2C_init()    
    {
        SDA = 1;
        _nop_();//1.08506us
        SCL = 1;
        _nop_();
    }
    
    /*I2C起始信号*/
    void I2C_Start()  
    {
        SCL = 1;
        _nop_();
        SDA = 1;
        delay_5us();
        SDA = 0;
        delay_5us();
    }
    
    /*I2C终止信号*/
    void I2C_Stop()
    {
        SDA = 0;
        _nop_();
        SCL = 1;
        delay_5us();
        SDA = 1;
        delay_5us();
    }
    
    /*主机发送应答*/
    void Master_ACK(bit i)        
    {
        SCL = 0; // 拉低时钟总线允许SDA数据总线上的数据变化
        _nop_(); // 让总线稳定
        if (i)     //如果i = 1 那么拉低数据总线 表示主机应答
        {
            SDA = 0;
        }
        else     
        {
            SDA = 1;     //发送非应答
        }
        _nop_();//让总线稳定
        SCL = 1;//拉高时钟总线 让从机从SDA线上读走 主机的应答信号
        delay_5us();
        SCL = 0;//拉低时钟总线, 占用总线继续通信
        _nop_();
        SDA = 1;//释放SDA数据总线。
        _nop_();
    }
    
    /*检测从机应答*/
    bit Test_ACK()
    {
        SCL = 1;//高电平期间才可以读取应答信号
        delay_5us();
        if (SDA)//非应答
        {
            SCL = 0;
            _nop_();
            I2C_Stop();//没有应答, 直接结束通信
            return(0);//跳出这个函数
        }
        else //应答
        {
            SCL = 0;
            _nop_();
            return(1);
        }
    }
    
    /*发送一个字节*/
    void I2C_send_byte(uchar byte)
    {
        uchar i;
        for(i = 0 ; i < 8 ; i++)
        {
            SCL = 0;//低电平时发送数据
            _nop_();
            if (byte & 0x80)//读取最高位数据
            {                
                SDA = 1;    //1
                _nop_();                   
            }                
            else
            {
                SDA = 0;//0
                _nop_();
            }
            SCL = 1; //拉高,开始取数
            _nop_();
            byte <<= 1;    // 0101 0100B 发送下一位 
        }
        SCL = 0;//占用总线,继续通信,以接受应答信号
        _nop_();
        SDA = 1;//释放SDA线,接受应答信号
        _nop_();
    }
    
     
    /*I2C 读一字节*/
    uchar I2C_read_byte()
    {
        uchar dat,i;
        SCL = 0;//先拉低,因为不知道当前电平状态
        _nop_();
        SDA = 1;//释放SDA线
        _nop_();
        for(i = 0 ; i < 8 ; i++)
        {
            SCL = 1;//高电平读取
            _nop_();
            if (SDA)                
            {
                 dat |= 0x01; //
            }
            else
            {
                dat &=  0xfe;    //1111 1110
            }
            _nop_();
            SCL = 0 ;//拉低,准备读取下一个数据
            _nop_();
            if(i < 7)
            {
                dat = dat << 1;    
            }
        }
        return(dat);
    }
    
    /*I2C发送数据*/
    bit I2C_TransmitData(uchar ADDR, DAT)
    {
        I2C_Start();
        I2C_send_byte(AT24C02_ADDR+0);
        if (!Test_ACK())
        {
            return(0);
        }
        I2C_send_byte(ADDR); 
        if (!Test_ACK())//无应答,返回
        {
            return(0);
        }
        I2C_send_byte(DAT);
        if (!Test_ACK())//无应答,返回
        {
            return(0);
        }
        I2C_Stop();
        return(1);    
    }
    
    /*I2C接收数据*/
    uchar I2C_ReceiveData(uchar ADDR)
    {
        uchar DAT;
        I2C_Start();
        I2C_send_byte(AT24C02_ADDR+0);
        if (!Test_ACK())
        {
            return(0);
        }
        I2C_send_byte(ADDR);
        Master_ACK(0);
        I2C_Start();
        I2C_send_byte(AT24C02_ADDR+1);
        if (!Test_ACK())
        {
            return(0);
        }
        DAT = I2C_read_byte();
        Master_ACK(0);
        I2C_Stop();
        return(DAT);    
    }
    
    void main()
    {
    bit ACK_flag =0; I2C_init();
    //I2C初始化
    I2C_Start();
    I2C_send_byte(AT24C02_ADDR+0);//发送从机地址
    if(!Test_ACK())
    {
    ACK_flag =1;//无应答
    }
    I2C_send_byte(8);//写地址
    if(!Test_ACK())
    {
    ACK_flag =1;
    }
    I2C_send_byte(0xfe);//在地址0x08写数据
    if(!Test_ACK())
    {
    ACK_flag =1;
    }
    I2C_Stop();//发完数据
    delay(5);//延时1ms,让从机缓一缓

    //开始将刚才写进去的数据读取出来
    I2C_Start();
    I2C_send_byte(AT24C02_ADDR+1);//发送地址
    if(!Test_ACK())
    {
    ACK_flag =1;
    }
    P1=I2C_read_byte();//主机读从机该寄存器地址的值,点亮LED1
    Master_ACK(0);//主机发送非应答,停止传输数据
    I2C_Stop();
    if(ACK_flag)
    {
    P1=0x00;
    }
         while(1);     
    }
     
    # include <reg52.h>
    # include <intrins.h>
    
    #define uchar unsigned char
    #define uint unsigned  int
    #define AT24C02_ADDR  0xa0    //AT24C02地址
    
    /*I2C IO口定义*/
    sbit SDA = P2^0;
    sbit SCL = P2^1;
    
    /*5us延时*/
    void delay_5us()  
    {
        _nop_();
    }
    
    /*1Ms延时*/
    void delay(uint z)
    {
        uint x,y;
        for(x = z; x > 0; x--)
            for(y = 114; y > 0 ; y--);
    }
    
    /*I2C初始化*/
    void I2C_init()    
    {
        SDA = 1;
        _nop_();//1.08506us
        SCL = 1;
        _nop_();
    }
    
    /*I2C起始信号*/
    void I2C_Start()  
    {
        SCL = 1;
        _nop_();
        SDA = 1;
        delay_5us();
        SDA = 0;
        delay_5us();
    }
    
    /*I2C终止信号*/
    void I2C_Stop()
    {
        SDA = 0;
        _nop_();
        SCL = 1;
        delay_5us();
        SDA = 1;
        delay_5us();
    }
    
    /*主机发送应答*/
    void Master_ACK(bit i)        
    {
        SCL = 0; // 拉低时钟总线允许SDA数据总线上的数据变化
        _nop_(); // 让总线稳定
        if (i)     //如果i = 1 那么拉低数据总线 表示主机应答
        {
            SDA = 0;
        }
        else     
        {
            SDA = 1;     //发送非应答
        }
        _nop_();//让总线稳定
        SCL = 1;//拉高时钟总线 让从机从SDA线上读走 主机的应答信号
        delay_5us();
        SCL = 0;//拉低时钟总线, 占用总线继续通信
        _nop_();
        SDA = 1;//释放SDA数据总线。
        _nop_();
    }
    
    /*检测从机应答*/
    bit Test_ACK()
    {
        SCL = 1;//高电平期间才可以读取应答信号
        delay_5us();
        if (SDA)//非应答
        {
            SCL = 0;
            _nop_();
            I2C_Stop();//没有应答, 直接结束通信
            return(0);
        }
        else //应答
        {
            SCL = 0;
            _nop_();
            return(1);
        }
    }
    
    /*发送一个字节*/
    void I2C_send_byte(uchar byte)
    {
        uchar i;
        for(i = 0 ; i < 8 ; i++)
        {
            SCL = 0;//低电平时发送数据
            _nop_();
            if (byte & 0x80)//读取最高位数据
            {                
                SDA = 1;    //1
                _nop_();                   
            }                
            else
            {
                SDA = 0;//0
                _nop_();
            }
            SCL = 1; //拉高,开始取数
            _nop_();
            byte <<= 1;    // 0101 0100B 发送下一位 
        }
        SCL = 0;//占用总线,继续通信
        _nop_();
        SDA = 1;//释放SDA线
        _nop_();
    }
    
    
    /*I2C 读一字节*/
    uchar I2C_read_byte()
    {
        uchar dat,i;
        SCL = 0;//先拉低,因为不知道当前电平状态
        _nop_();
        SDA = 1;//释放SDA线
        _nop_();
        for(i = 0 ; i < 8 ; i++)
        {
            SCL = 1;//高电平读取
            _nop_();
            if (SDA)                
            {
                 dat |= 0x01; //
            }
            else
            {
                dat &=  0xfe;    //1111 1110
            }
            _nop_();
            SCL = 0 ;//拉低,准备读取下一个数据
            _nop_();
            if(i < 7)
            {
                dat = dat << 1;    
            }
        }
        return(dat);
    }
    
    /*I2C发送数据*/
    bit I2C_TransmitData(uchar ADDR, DAT)
    {
        I2C_Start();
        I2C_send_byte(AT24C02_ADDR+0);
        if (!Test_ACK())
        {
            return(0);
        }
        I2C_send_byte(ADDR); 
        if (!Test_ACK())//无应答,返回
        {
            return(0);
        }
        I2C_send_byte(DAT);
        if (!Test_ACK())//无应答,返回
        {
            return(0);
        }
        I2C_Stop();
        return(1);    
    }
    
    /*I2C接收数据*/
    uchar I2C_ReceiveData(uchar ADDR)
    {
        uchar DAT;
        I2C_Start();
        I2C_send_byte(AT24C02_ADDR+0);
        if (!Test_ACK())
        {
            return(0);
        }
        I2C_send_byte(ADDR);
        Master_ACK(0);
        I2C_Start();
        I2C_send_byte(AT24C02_ADDR+1);
        if (!Test_ACK())
        {
            return(0);
        }
        DAT = I2C_read_byte();
        Master_ACK(0);
        I2C_Stop();
        return(DAT);    
    }
    
    void main()
    {
        I2C_init();//I2C初始化
        if(!I2C_TransmitData(255,0xf0));    //往AT24C02第255个单元中写入数据0XF0
        {
            P1 = 0;
        }
        delay(5);//一定延时一定时间后再读取数据
        /**/
        P1 = I2C_ReceiveData(255);//从AT24C02第255个单元中读取数据
        while(1);     
    }

    往QX-MCS51开发板的AT24C02内任意一个单元写数据,开发板上电后首先读取此单元的原有的数据,然后赋给程序中的计数变量,计数变量再以5秒的速度+1并且写入AT24C02,当计数大于99时清零再写,用数码管显示。

    # include <reg52.h>
    # include <intrins.h>
    
    #define uchar unsigned char
    #define uint unsigned  int
    #define AT24C02_ADDR  0xa0
    
    sbit LED1 = P1^0;
    sbit SDA = P2^0;
    sbit SCL = P2^1;
    sbit we  = P2^7;
    sbit du  = P2^6;
    
    uchar EEPROM_DATA;    //存放从AT24C02单元读取到的数据
    uchar code leddata[]={ 
     
                    0x3F,  //"0"
                    0x06,  //"1"
                    0x5B,  //"2"
                    0x4F,  //"3"
                    0x66,  //"4"
                    0x6D,  //"5"
                    0x7D,  //"6"
                    0x07,  //"7"
                    0x7F,  //"8"
                    0x6F,  //"9"
                    0x77,  //"A"
                    0x7C,  //"B"
                    0x39,  //"C"
                    0x5E,  //"D"
                    0x79,  //"E"
                    0x71,  //"F"
                    0x76,  //"H"
                    0x38,  //"L"
                    0x37,  //"n"
                    0x3E,  //"u"
                    0x73,  //"P"
                    0x5C,  //"o"
                    0x40,  //"-"
                    0x00,  //熄灭
                    0x00  //自定义
     
                             };
    void delay_5us()  //5us
    {
        _nop_();
    }
    
    /*1毫秒延时函数*/
    void delay(uint z)    
    {
        uint x,y;
        for(x = z; x > 0; x--)
            for(y = 114; y > 0 ; y--);
    }
    
    void display(uchar i)  //数码管显示函数
    {
        uchar shi,ge;
        shi = i / 10;     //求模
        ge  = i % 10;     //求余
        
        P0 = 0xff;         //清除段码
        we = 1;
        P0 = 0xfe;         //点亮第一位数码管
        we = 0;
    
        du = 1;
        P0 = leddata[shi];
        du = 0;
        delay(1);
    
        P0 = 0xff;         //清除段码
        we = 1;
        P0 = 0xfd;        //点亮第二位数码管
        we = 0;
    
        du = 1;
        P0 = leddata[ge];
        du = 0;
        delay(1);    
    }
    
    void I2C_init()    //I2C初始化
    {
        SDA = 1;
        _nop_();
        SCL = 1;
        _nop_();
    }
    
    /*定时器0初始化*/    
    void Timer0_init()
    {
        
        TMOD |= 0x01;          //定时器T0 16为计数工作模式
        TH0 = 0x4b;
        TL0 = 0xfe;         //T0 定时50ms
        ET0 = 1;             //T0中断
        TR0 = 1;            //启动T0
        EA = 1;                //开总中断
    }
    
    void I2C_Start()  //I2C起始信号
    {
        SCL = 1;
        _nop_();
        SDA = 1;
        delay_5us();
        SDA = 0;
        delay_5us();
    }
    
    void I2C_Stop()
    {
        SDA = 0;
        _nop_();
        SCL = 1;
        delay_5us();
        SDA = 1;
        delay_5us();
    }
    
    void Master_ACK(bit i)        // 主机发送应答
    {
        SCL = 0; // 拉低时钟总线允许SDA数据总线上的数据变化
        _nop_(); // 让总线稳定
        if (i)     //如果i = 1 那么拉低数据总线 表示主机应答
        {
            SDA = 0;
        }
        else     
        {
            SDA = 1;     //发送非应答
        }
        _nop_();//让总线稳定
        SCL = 1;//拉高时钟总线 让从机从SDA线上读走 主机的应答信号
        _nop_();
        SCL = 0;//拉低时钟总线, 占用总线继续通信
        _nop_();
        SDA = 1;//释放SDA数据总线。
        _nop_();
    }
    
    bit Test_ACK()     // 检测从机应答
    {
        SCL = 1;//时钟总线为高电平期间可以读取从机应答信号
        delay_5us();
        if (SDA)
        {
            SCL = 0;
            I2C_Stop();
            return(0);
        }
        else
        {
            SCL = 0;
            return(1);
        }
    }
    
    void I2C_send_byte(uchar byte)     //发送一个字节
    {
        uchar i;
        for(i = 0 ; i < 8 ; i++)
        {
            SCL = 0;
            _nop_();
            if (byte & 0x80)    //
            {
                SDA = 1;
                _nop_();
            }
            else
            {
                SDA = 0;
                _nop_();
            }
            SCL = 1;
            _nop_();
            byte <<= 1;
        }
        SCL = 0;
        _nop_();
        SDA = 1;
        _nop_();    
    }
    
    uchar I2C_read_byte()  //读一个字节
    {
        uchar i, dat;
        SCL = 0 ;
        _nop_();
        SDA = 1;
        _nop_();
        for(i = 0 ; i < 8 ; i++)
        {
            SCL = 1;
            _nop_();
            dat <<= 1;      
            if (SDA)
            {
                dat |= 0x01;  
            }
            _nop_();
            SCL = 0;
            _nop_();
        }
        return(dat);
    }
    
    bit I2C_WriteData(uchar ADDR,uchar DAT)     //I2C写数据
    {
        I2C_Start();
        I2C_send_byte(AT24C02_ADDR+0);
        if (!Test_ACK())
        {
            return(0);    
        }
        I2C_send_byte(ADDR);
        if (!Test_ACK())
        {
            return(0);    
        }
        I2C_send_byte(DAT);
        if (!Test_ACK())
        {
            return(0);    
        }
        I2C_Stop();
        return(1);    
    }
    
    uchar I2C_ReadData(uchar ADDR)     //I2C读数据
    {
        uchar dat;
        I2C_Start();
        I2C_send_byte(AT24C02_ADDR+0);
        if (!Test_ACK())
        {
            return(0);    
        }
        I2C_send_byte(ADDR);
        if (!Test_ACK())
        {
            return(0);    
        }
        Master_ACK(0);
        I2C_Start();
        I2C_send_byte(AT24C02_ADDR+1);
        if (!Test_ACK())
        {
            return(0);    
        }
        dat = I2C_read_byte();
        Master_ACK(0);
        I2C_Stop();
        return(dat);
    }
    
    void main()
    {
        I2C_init();
        Timer0_init();
        EEPROM_DATA = I2C_ReadData(255);    //上电后首先读出24C02第255单元的值
        while(1)
        {
    
            display(EEPROM_DATA);//数码管显示
                
        }
    }
    
    /*定时器0中断服务程序*/
    void timer0() interrupt 1    //T0内部查询顺序1
    {
        uchar i;      
        TH0 = 0x4b;
        TL0 = 0xfe;         //T0 定时50ms
        i++;
        if (i == 100)      //5秒时间到
        {
            i = 0;          //计数清零
            if (EEPROM_DATA < 99) //判断待写数据值
            {
                EEPROM_DATA++;
            }
            else
            {
                EEPROM_DATA = 0;    
            }
            if(!I2C_WriteData(255,EEPROM_DATA))    //写入待写数据并判断是否成功写入
            {
                LED1 = 0;//如果写失败  让LED1小灯点亮
            }
            else
            {
                LED1 = 1;    
            }        
        }
    }      
  • 相关阅读:
    23
    关系数据库范式
    组合
    排列
    bfs_迷宫求最短路径
    dfs-求连通块
    dfs_部分和问题
    线程
    http://m.blog.csdn.net/article/details?id=51699295
    jquery 页面多个倒计时
  • 原文地址:https://www.cnblogs.com/darren-pty/p/13288680.html
Copyright © 2011-2022 走看看