51单片机——I2C——eeprom

本文围绕51单片机I2C通信的EEPROM(AT24C02)展开。介绍了EEPROM背景知识,包括与RAM、ROM区别,和flash的关系等;阐述了I2C通信原理、特征及底层时序;还说明了AT24C02的读写高层时序,如写时序、页写时序、立即读和选择性读地址时序等。

/****************    I2C通信之EEPROM(AT24C02)(掉电不丢失)    **********************/

/*

一. EEPROM及其背景知识

    1.1 ROM(只读存储器——硬盘), RAM(随机访问存储器——电脑内存), PROM(可重新编程的rom), EPROM(可擦除rom), EEPROM(电可擦除rom)

    1.2 为什么需要eeprom———— 单片机内部的rom只能在程序下载时进行擦除和改写,但是程序运行本身不能改写的。

    单片机内部的ram中的数据程序运行时可以改,但是掉电就丢失了。

   

    有时候我们有一些数据要存在系统中,要求掉电不丢失,而且程序还要能改。

    所以内部rom和ram都不行。   这个时候系统就需要一块eeprom。

    存储器分为两大类:RAM和ROM。

    RAM:全称“随机存取存储器”,也叫主存,是与CPU直接交换数据的内部存储器。它可以随时读写(刷新时除外),而且速度很快,

        通常作为操作系统或其他正在运行中的程序的临时数据存储介质.。RAM在计算机和数字系统中用来暂时存储程序、数据和中间结果。即内存条。

    ROM:全称“只读存储器”,只能读出事先所存数据。

    RAM与ROM的最大区别:在于数据的易失性,RAM一旦断电所存储的数据将随之丢失,ROM掉电数据不丢失。

   

    总结: RAM:运行内存存储 掉电丢失,可随机访问读写,速度快

           ROM: 掉电不丢失   只读存储不支持修改

           EEPROM:掉电不丢失且支持读写修改

       

    1.3 eeprom和flash的区别与联系

        (1)关系:FLASH属于EEPROM,EEPROM属于ROM,即ROM>EEPROM>FLASH。

        (2)EEPROM和FLASH:EEPROM存数据变量,数据存储器,可改变,flash存程序,程序存储器,不可改变(因为难度大)

        (3)FLASH分类:NOR FLASH 和NAND FLASH。 nor flash存储引导代码,用一个大容量的nand flash存放文件系统和内核,即固态

            EEPROM是按功能分类的一种存储设备类型;flash是存储设备生产的一种工艺。EEPROM就可能采用了flash工艺,常见的U盘也是采用flash工艺。

            这里的EEPROM芯片具体型号是AT24C02,采用I2C时序进行读写。

    1.4 eeprom存在系统中的2种形式:

        内置在单片机内部和外部扩展

    1.5 eeprom如何编程分为(1)I2C接口底层时序

                        (2)上层时序器件定义的寄存器读写时序


 

二. 原理图和数据手册

    接线:   SDA       SCL      GND    三线

    物理接口:SCL + SDA

    SCL(serial clock):时钟线,传输CLK信号,一般是I2C主设备向从设备提供时钟的通道。

    SDA(serial data):数据线,通信数据都通过SDA线传输。

    通信特征:串行、同步、非差分、低速率

        I2C属于串行通信,所有的数据以位为单位在SDA线上串行传输。  先传高位,与spi先传低位不同

        同步通信就是通信双方工作在同一个时钟下,一般是通信的A方通过一根CLK信号线传输A自己的时钟给B,B工作在A传输的时钟下。

        所以同步通信的显著特征就是:通信线中有CLK。

        非差分。因为I2C通信速率不高,而且通信双方距离很近,所以使用电平信号通信。

        低速率。I2C一般是用在同一个板子上的2个IC之间的通信,而且用来传输的数据量不大,

        所以本身通信速率很低(一般几百Kbps,不同的I2C芯片的通 信速率可能不同,具体在编程的时候要看自己所使用的

        设备允许的I2C通信最高速率,不能超过这个速率)

        注意:同一时间内,I2C的总线上只能传输一对设备的通信信息,所以同一时间只能有一个从设备和主设备通信,

        其他从设备处于“休眠”状态,不能出来捣乱,否则通信就乱套了(广播然后匹配的思路)。

        !!!关于I2C的上拉电阻:I2C协议规定,总线空闲时两根线都必须为高。!!!

三. I2C底层时序图和程序1

    eeprom的时序方式与ds1302spi时序类似,但eeprom的时序依照I2C方式进行

    I2C总结

        I2C总线是一种同步、半双工,带数据应答的二线制串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。

        工作原理:

        如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;

        如果主机要接收从器件的数据,首先由主器件寻址从器件,然后主机接收从器件发送的数据,最后由主机终止接收过程。

        在这种情况下.主机负责产生定时时钟和终止数据传送。

        注意: 寻址和数据与spi相反 要先从高位开始

    (1)主CPU和其附属芯片(I2C设备)之间最常用的接口,尤其是各种传感器,因此在物联网时代非常重要。

    (2)三根线:GND、SCL、SDA,串行通信(只有一根数据传输线),电平式通信(相对于差分信号)。

    (3)总线式结构,可以一对多,总线上可以挂上百个器件,用从地址来区分。

    (4)主从式,由主设备来发起通信及总线仲裁,从设备被动响应。

    (类似访谈节目主持人和嘉宾对话的方式,主持人具有对话发起的权力,嘉宾只能被动回复)

    (5)通信速率一般(kbps级别),不适合语音、视频等信息类型

    3.1 EEPROM编程思路主要包含下面两部分:

    (1)I2C接口底层时序。起始信号、停止信号、发送字节、读取字节。

    (2)EEPROM的寄存器读写时序。

    3.2 I2C从设备的地址

    (1)从设备的地址是由从设备自身定义的,不同的从器件的地址定义方式是不同的,要查具体的芯片数据手册来确定。

    (2)同一个I2C总线上只有一个主设备,但是从设备可以有多个。这多个从设备的从地址不能相同(硬件设计工程师必须保证这一点。

        因为从地址是不能通过软件设定的)

    (3)分析原理图和AT24C02的数据手册,得出: 从设备地址是:读地址:0xA1,写地址:0xA0.

   

四. I2C底层时序图和程序2

   4.1 起始信号和结束信号

    !!!! I2C时序: (1) scl=1时, sda由高电平到低电平(下降沿)为 时序开始

                      (2) 只有当scl=0时, sda是低电平或者是高电平才会被允许,即数据才允许被写入

                      (3) scl=1时, sda由低电平到高电平(上升沿)为 时序结束

                      I2C通信时的基本数据单位也是以字节为单位的,每次传输的有效数据都是1个字节(8位)。

                起始位及其后的8个clk都是主设备在发送(主设备掌控总线),此时从设备只能读取总线来得知主设备发给它的信息;

                然后到了第9周期,按照协议规定从设备需要发送ACK给主设备,所以此时主设备必须释放总线

                (也就是主设备把SDA总线置为高电平然后不要动),同时从设备试图拉低总线发出ACK。

                如果从设备拉低总线失败,或者从设备根本就没有拉低总线,则主设备看到的现象就是总线在第9周期仍然一直保持高,

                这对主设备来说,意味着我没收到ACK,主设备就认为刚才给从设备发送的8字节不对(接收失败)

   4.2 I2C发送一个字节

   4.3 应答位处理

   4.4 I2C接收一个字节

概念:释放总线   SDA=1就是释放总线; 在其他更高级的单片机(譬如STM32等)这里处理还会有点不一样

    // 程序设计编译


 

五.AT24C02的读写

1.24c02读写高层时序

    1.1 从器件的地址是由器件自身定义的,不同的从器件的地址定义方式是不同的,要查具体的芯片手册来确定

    1.2 同一个i2c网络中只有一个主设备,但是从设备可以有多个。这多个从设备的地址不能相同(硬件设计工程师必须保证这一点)

    1.3 从器件地址1010 +a2+a1+a0   其中a2,a1,a0 表示具体器件/外设的地址. rw=1读地址0xa1  rw=0写地址0xa0

    写时序:1.开始

            2.从器件地址+ack

            3.字节地址+ack

            4.数据1+ack

            5.结束

    写页写(seandbyte)时序:1.开始

                             2.从器件地址+ack

                             3.字节地址+ack

                             4.数据1+ack

                             5.数据2+ack

                             6.数据3+ack。。。。。。。等

                             7.结束

     立即读地址时序:1.开始

                     2.从器件地址

                     3.数据

                     4.结束

     选择性读地址时序:1.开始

                       2.(readbyte)从器件地址      +ack

                       3.(readbyte)字节地址   +ack

                       4.开始

                       5.(readbyte)从器件地址      +ack

                       6.数据       no ack

                       4.结束            

*/

  /************ I2C_EEPROM  ******************/



#include "reg51.h"



sbit SCL = P2^1;

sbit SDA = P2^0;



typedef unsigned char uchar;



void Delay10us(void)

{

    uchar a, b;

    for(b=1; b>0; b--)

    {

        for(a=1; a>0; a--);

    }

   

}

// I2C起始信号函数

void I2c_start()

{

    SDA = 1;

    Delay10us();

    SCL = 1;

    Delay10us();

    SDA = 0;

    Delay10us();

    SCL = 0;

    Delay10us();

}



// I2C终止信号函数

void I2c_stop()

{

    SDA = 0;

    Delay10us();

    SCL = 1;

    Delay10us();

    SDA = 1;

    Delay10us();

    SCL = 0;

    Delay10us();

}



// 通过I2c向从设备发送一个字节

//uchar I2c_Sendbyte(uchar addr)

//{

//    uchar i = 0;

//    for(i=0; i<8; i++)

//    {

//        SDA = addr >> 7;  // I2c总线高位优先

//        addr <<= 1;

//        Delay10us();

//        SCL = 1;

//        Delay10us();

//        SCL = 0;

//        Delay10us();

//    }

//  SDA = 1;      // 主设备释放SDA总线 给从设备去操作

//  Delay10us();  // 主设备释放SDA总线 给从设备去操作

//  SCL = 1;      // 主设备释放SDA总线 给从设备去操作   主设备第9个周期

//  if(SDA == 0)

//  {

//      SCL = 0;

//      Delay10us();

//      return 0; //return 0 表示成功  

//  }

//  SCL = 0;

//  Delay10us();

//  return 1;   //return1表示失败

//}

uchar I2c_Sendbyte(uchar addr, uchar ack)

{

    uchar i = 0, j = 0;

    for(i=0; i<8; i++)

    {

        SDA = addr >> 7;

        addr = addr << 1;

        Delay10us();

        SCL = 1;

        Delay10us();

        SCL = 0;

        Delay10us();

    }

    SDA = 1;        // 主设备释放SDA总线 给从设备去操作

    Delay10us();

    SCL = 1;        //  主设备第9个周期开始

    while(SDA && (ack == 1))    // 等待应答,等待从设备将SDA拉低   //逻辑与:都为真为真,有一假为假   逻辑或:有一真为真,

    {

        j++;   //    等待200的时间看sda能否拉低

        if(j > 200)

        {

            SCL = 0;

            Delay10us();

            return 1;    //return1表示失败

        }

    }

    SCL = 0;

    Delay10us();

    return 0;  //return 0 表示成功

}    

//  说明;uchar ack 是一个参数,是需要等它的响应。有应答ack=1,无应答ack=0

//          ack=1时需要等从设备响应。

//          ack=0时,说明不需要去等待从设备的响应

//         从    while(SDA = 0 && (ack == 1))可以看出来



//    ack总结: 正常情况下一个字节(8位数据)发送完后(这里说的发送是主设备向从设备通过i2c总线发送的数据),主设备有一个等待从设备反馈的动作,动作前提是SDA需要在这之前

//              拉高,让出SDA总线,等待从设备将SDA拉低,SDA=0说明从设备ack反馈了一个值,就说明从设备接收数据完毕。




// 读取从设备在i2c中的字节

uchar I2c_readbyte()

{

    uchar a = 0,dat = 0;

    SDA = 1;        // 主设备释放总线 (sendbyte和 readbyte都需要释放总线)      //起始和发送一个字节之后I2C_SCL都是0

    Delay10us();

    //SCL = 0;

    // 按道理这里应该有一个SCL = 0的

    for(a=0; a<8; a++)//接收8个字节

    {

        SCL = 1;        // 通知从设备我要开始读了,可以放1bit数据到SDA了

        Delay10us();

        dat <<= 1;          // 读取的时候是高位在前的

        dat |= SDA;

        Delay10us();

        SCL = 0;        // 拉低,为下一个bit的周期做准备

        Delay10us();

    }

    return dat;

}



/*  逻辑运算符:按位或|| 和 按位与 &&

                按位或 || : 表达式左右两边有一个为真,则表达式成立都为真

                按位与 %% : 表达式左右两边都为真,则表达式成立都为真

*/



/*  高层时序   */

// 以cpu为本身,向24c02写数据

void write_24C02(uchar addr, uchar dat)

{

    I2c_start();             // 开始

    I2c_Sendbyte(0xa0, 1);   // 从器件地址,+ 需要应答

    I2c_Sendbyte(addr, 1);   // 字节地址,  + 需要应答

    I2c_Sendbyte(dat, 1);    // 字节数据,  + 需要应答

    I2c_stop();              // 结束

}



////1. 以cpu为本身,立即 读24c02数据

//uchar read_24c02()

//{

//  uchar num = 0;

//  I2c_start();             // 开始

//  I2c_Sendbyte(0xa0, 1);   // 发送从器件地址,+ 需要应答

//  num = I2c_readbyte();    // 读取地址

//  return num;

//}                            




//2. 以cpu为本身,选择性 读24c02数据

uchar read_24c02(uchar addr)

{

    uchar num = 0;

    I2c_start();             // 开始

    I2c_Sendbyte(0xa0, 1);   // 发送从器件地址,+ 需要应答       0xa0 写   0xa1读

    I2c_Sendbyte(addr, 1);   // 发送字节地址,  + 需要应答

    I2c_start();             // 开始

    I2c_Sendbyte(0xa1, 1);   // 发送读从器件地址,+ 需要应答

    num = I2c_readbyte();    // 读取地址

    I2c_stop();

    return num;

}  




////3. 以cpu为本身,连续都 读24c02数据

                   




/*  串口函数    波特率4800  */



void uart_init(void)    // 串口初始化函数

{

    SCON = 0x50;

    PCON = 0X80;    //波特率加倍  2400mhz加倍到4800,相应的TH1\TL1的计算就要改变

                    //波特率计算公式为:2的smod次方/32*晶振频率*10 000 000/12/(256-TH1)=4800mhz

    TMOD = 0x20;

    TH1 = 243;

    TL1 = 243;



    TR1 = 1;

//  EA = 1;

//  ES = 1;

}



void uart_send_byte(uchar a)  // 串口发送函数

{

    SBUF = a;

    while (!TI);        

        TI = 0;

}




// 主函数主程序



void main()

{

    uchar i = 0;

    uchar addr = 0;

    uchar dat[8] ={1,2,3,4,5,6,7,8};

    uchar buf[8];



//  uart_init();

//  for (i=0; i<12; i++)

//  {                        // 串口调试程序没问题

//      uart_send_byte(i);

//  }

//  while(1);

    uart_init();     // uart初始化



    addr = 0;

    for(i=0; i<8; i++)     // 写入

    {

        write_24C02(addr, dat[i]);

        Delay10us();

        addr++;

    }

    addr = 0;

    for(i=0; i<8; i++)     // 读取

    {

        buf[i] = read_24c02(addr);

        Delay10us();

        addr++;

    }

    for(i=0; i<8; i++)     // urat 发送

    {

        uart_send_byte(buf[i]);

        Delay10us();    

    }      

}


 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值