EEPROM(AT24C02 I2C协议)入门: 嵌入式硬件

EEPROM是指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。
本文重点讲解AT24C02的应用(即如何编程实现读写功能,偏软件)

文章目录

  • EEPROM
  • EEPROM简介
  • EEPROM发展过程
  • EEPROM分类
  • AT24C02
  • AT24C02简介
  • AT24C02管脚定义及说明
  • I2C协议(针对AT24C02)
  • I2C总线概念
  • I2C总线术语
  • I2C起始和终止条件
  • I2C 传输数据(单个字节)
  • I2C总线时序
  • AT24C02特殊时序
  • I2C设备地址(7位)
  • 对AT24C02进行读写
  • 字节写
  • 页写
  • 当前地址读
  • 选择读(随机读)
  • 连续读
  • 51单片机控制AT24C02
  • I2C协议代码
  • Proteus小实验
  • EEPROM

    EEPROM简介

    EEPROM (Electrically Erasable Programmable read only memory)是指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。 EEPROM 可以在电脑上或专用设备上擦除已有信息,重新编程。一般用在即插即用。
    .
    EEPROM(带电可擦可编程只读存储器)是用户可更改的只读存储器(ROM),其可通过高于普通电压的作用来擦除和重编程(重写)。不像EPROM芯片,EEPROM不需从计算机中取出即可修改。在一个EEPROM中,当计算机在使用的时候可频繁地反复编程,因此EEPROM的寿命是一个很重要的设计考虑参数。EEPROM是一种特殊形式的闪存,其应用通常是个人电脑中的电压来擦写和重编程 。
    ——百度百科

    EEPROM发展过程

    以下内容均参考百度百科

    1. ROM(Read Only Memory,只读存储器)
      在微机的发展初期,BIOS都存放在ROM中。ROM内部的资料是在ROM的制造工序中,在工厂里用特殊的方法被烧录进去的,其中的内容只能读不能改,一旦烧录进去,用户只能验证写入的资料是否正确,不能再作任何修改。如果发现资料有任何错误,则只有舍弃不用,重新订做一份。ROM是在生产线上生产的,由于成本高,一般只用在大批量应用的场合。
    2. PROM(Programmable ROM,可编程ROM)
      由于ROM制造和升级的不便,后来人们发明了PROM)。最初从工厂中制作完成的PROM内部并没有资料,用户可以用专用的编程器将自己的资料写入,但是这种机会只有一次,一旦写入后也无法修改,若是出了错误,已写入的芯片只能报废。PROM的特性和ROM相同,但是其成本比ROM高,而且写入资料的速度比ROM的量产速度要慢,一般只适用于少量需求的场合或是ROM量产前的验证。
    3. EPROM(Erasable Programmable ROM,可擦除可编程ROM)
      EPROM芯片可重复擦除和写入,解决了PROM芯片只能写入一次的弊端。EPROM芯片有一个很明显的特征,在其正面的陶瓷封装上,开有一个玻璃窗口,透过该窗口,可以看到其内部的集成电路,紫外线透过该孔照射内部芯片就可以擦除其内的数据,完成芯片擦除的操作要用到EPROM擦除器。EPROM内资料的写入要用专用的编程器,并且往芯片中写内容时必须要加一定的编程电压(VPP=12~24V,随不同的芯片型号而定)。EPROM的型号是以27开头的,如27C020(8*256K)是一片2M Bits容量的EPROM芯片。EPROM芯片在写入资料后,还要以不透光的贴纸或胶布把窗口封住,以免受到周围的紫外线照射而使资料受损。
    4. EEPROM(Electrically Erasable Programmable ROM,电可擦除可编程ROM)
      由于EPROM操作的不便,后来出的主板上BIOS ROM芯片大部分都采用EEPROM。EEPROM的擦除不需要借助于其它设备,它是以电子信号来修改其内容的,而且是以Byte为最小修改单位,不必将资料全部洗掉才能写入,彻底摆脱了EPROM Eraser和编程器的束缚。EEPROM在写入数据时,仍要利用一定的编程电压,此时,只需用厂商提供的专用刷新程序就可以轻而易举地改写内容,所以,它属于双电压芯片。借助于EEPROM芯片的双电压特性,可以使BIOS具有良好的防毒功能,在升级时,把跳线开关打至“on”的位置,即给芯片加上相应的编程电压,就可以方便地升级;平时使用时,则把跳线开关打至“off”的位置,防止CIH类的病毒对BIOS芯片的非法修改。所以,仍有不少主板采用EEPROM作为BIOS芯片并作为自己主板的一大特色。

    EEPROM分类

    以下内容整理自网络,仅供参考

  • 生产商分类
    1. Atmel(AT,爱特梅尔,已被Microchip收购):AT24C系列、AT45DB系列等;
    2. Microchip(微芯科技):11AA02E系列、25AA02E系列、MC93C系列等;
    3. ST(意法半导体):M24C系列、M93C系列等;
    4. 聚辰半导体(国内):GT24C系列、GT93C系列等;

    还有很多EEPROM生成商,上面四个(实际上就3个)为全球供货占比前三的厂家。(2018年的数据:ST占37%,Microchip(Atmel)占23%,聚辰半导体占8.2%)

  • 通信协议
    1. I2C:I2C总线(Inter-Integrated Circuit,内部整合电路)是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。
    2. SPI:SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线。
    3. Microwire:Microwire串行接口是一种简单的四线串行接口,由串行数据输入(SI)、串行数据输出(SO)、串行移位时钟(SK)、芯片选择(CS)组成,可实现高速的串行数据通讯。它是SPI的精简接口,能满足通常外设的需求。

    还可以根据按存储空间大小串/并行等进行分类,这里不作详细介绍。

    AT24C02

    AT24C02简介

    本文将以AT24C02为例,介绍EEPROM的基本知识和使用方法。

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

    AT24C02管脚定义及说明

    管脚定义

    管脚功能对照表

    功能说明

    注:AT24C01/02/04/08/16 封装(管脚定义)相同。

    AT24C01/02/04/08/16 支持 I2C 总线数据传送协议 I2C 总线协议规定,任何将数据传送到总线的器件作为发送器。任何从总线接收数据的器件为接收器。数据传送是由产生串行时钟和所有起始停止信号的主器件控制的。主器件从器件都可以作为发送器或接收器,但由主器件控制传送数据(发送或接收)的模式,通过器件地址输入端 A0、A1 和 A2 可以实现将最多 8 个 24CW01 和 24CW02 器件,4 个242CW04 器件,2 个 24CW08 器件和 1 个 24WC16 器件连接到总线上。
    .
    ——AT24CXX中文手册

    管脚描述

    SCL 串行时钟
    AT24WC01/02/04/08/16 串行时钟输入管脚用于产生器件所有数据发送或接收的时钟,这是一个输入管脚。
    .
    SDA 串行数据/地址
    AT24WC01/02/04/08/16 双向串行数据/地址管脚用于器件所有数据的发送或接收,SDA 是一个开漏输出管脚,可与其它开漏输出或集电极开路输出进行线或(wire-OR)。
    .
    A0 A1 A2 器件地址输入端
    这些输入脚用于多个器件级联时设置器件地址,当这些脚悬空时默认值为 0(24WC01 除外)。
    当使用 24WC01 或 24WC02 时最大可级联 8 个器件。如果只有一个 24WC02 被总线寻址,这三个地址输入脚 (A0、A1、A2) 可悬空或连接到 Vss,如果只有一个 24WC01 被总线寻址,这三个地址输入脚 (A0、A1、A2)必须连接到 Vss。
    当使用 24WC04 时最多可连接 4 个器件,该器件仅使用 A1、A2 地址管脚。A0 管脚未用,可以连接到 Vss 或悬空。如果只有一个 24WC04 被总线寻址 A1 和 A2 地址管脚可悬空或连接到 Vss。
    当使用 24WC08 时最多可连接 2 个器件,且仅使用地址管脚 A2, A0、A1 管脚未用,可以连接到Vss 或悬空 。如果只有一个 24WC08 被总线寻址 A2 管脚可悬空或连接到 Vss。
    当使用 24WC16 时最多只可连接 1 个器件,所有地址管脚 A0、A1、A2 都未用 管脚可以连接到Vss 或悬空。
    .
    WP 写保护
    如果 WP 管脚连接到 Vcc 所有的内容都被写保护(只能读)。当 WP 管脚连接到 Vss 或悬空,允许器件进行正常的读/写操作。
    .
    ——AT24CXX中文手册

    I2C协议(针对AT24C02)

    I2C总线概念

    I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。
    主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件.在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下.主机负责产生定时时钟和终止数据传送。
    .
    ——百度百科

    I2C总线术语


    多主机、仲裁和同步本文不会涉及
    在AT24C02的使用中(假设主控芯片为单片机),单片机为主机、AT24C02为从机,如果单片机向AT24C02写数据,那么单片机是发送器,AT24C02是接收器,反之,如果单片机及从AT24C02读数据,那么单片机就是接收器,AT24C02为发送器。

    I2C起始和终止条件

    起始信号:当 SCL为高电平时,SDA 由高电平变成低电平。
    停止信号:当 SCL为高电平时,SDA 由低电平变成高电平。
    我们可以通过“起始信号为下降沿信号,停止信号为上升沿信号”来帮助记忆。

    要点

    1. 起始信号和停止信号均由主机发出。
    2. 总线在起始状态前是空闲状态(此时SDA和SCL都为高电平),进入起始条件后,总线处于忙状态。
    3. 只要主机不发送停止信号,总线将一直处于忙状态。

    I2C 传输数据(单个字节)

    字节格式
    发送到SDA线上的每个字节必须为8位,每次传输可以发送的字节数量不受限制。每个字节后必须跟一个响应位。首先传输的是数据的最高位(MSB),如果从机要完成一些其他功能后(例如一个内部中断服务程序)才能接收或发送下一个完整的数据字节,可以使时钟线SCL保持低电平,迫使主机进入等待状态,当从机准备好接收下一个数据字节并释放时钟线SCL后数据传输继续。
    应答响应
    数据传输必须带响应,相关的响应时钟脉冲由主机产生。在响应的时钟脉冲期间发送器释放SDA线(高)。
    在响应的时钟脉冲期间,接收器必须将SDA线拉低,使它在这个时钟脉冲的高电平期间保持稳定的低电平。
    .
    ——百度百科

    下图左半边1~8个时钟周期传输了1个字节的地址数据(寻址+读/写),在第9个时钟周期时,从机将SDA拉低(发出响应信号),继续第二个字节的传输。如果等了很长一段时间SDA都没有被拉低,说明从机处于忙状态或者无法与从机进行通信,这时主机需要发送停止信号结束通信(或发送起始信号重新建立通信)。

    第二个字节可能是主机写,也可能是主机读(由第一个字节的最后一位决定,后面具体介绍)。

    假如第二个字节是主机写数据到从机,那么当第二个字节传输完成,到达右边第9个时钟周期时,如果从机发送响应信号,说明该字节传输成功,此时主机可以选择再发送一个起始信号(也称重新开始Sr信号),继续传输数据;也可以发送一个停止信号,结束本次通信。如果等了很长一段时间SDA都没有被拉低,说明从机处于忙状态或者无法与从机进行通信,这时主机需要发送停止信号结束通信(或发送起始信号重新建立通信)。

    假如第二个字节是主机读数据,那么当第二个字节传输完成,到达右边第9个时钟周期时,主机可以发送一个非响应信号(保持SDA为高),告知从机数据传输结束,让从机释放数据线,允许主机产生一个停止信号重新起始信号。有些场合(如连续读),主机可以发送响应信号,继续数据传输。

    请添加图片描述

    数据位发送
    在I2C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。进行数据传送时,在SCL呈现高电平期间,SDA上的电平必须保持稳定,低电平为数据0,高电平为数据1。只有在SCL为低电平期间,才允许SDA上的电平改变状态。逻辑0的电平为低电压,而逻辑1则为高电平。

    I2C总线时序


    上图时序参照《AT24CXX手册》4.5V~5.5V的参数,见下表

    AT24CXX时序表:

    AT24C02特殊时序

    由于不同的I2C设备有自己的特性,有时仅仅使用标准I2C协议无法完成芯片的特有功能。比如温湿度传感器在每次发送读取温湿度数据的指令后还需要等待传感器处理完数据才能成功读取,AT24C02也有类似的需求——向AT24C02写数据时需要等待一个写周期

    此外,AT24C02还有上电时序:

    I2C设备地址(7位)

    AT24C02设备地址

    下图是AT24CXX的设备地址(第一行的为AT24C02,它的容量为2K),我们发现AT24CXX整个系列芯片的地址高四位都相同,都是1010,这四位是由生产商固化在芯片内部,无法改变。

    AT24C02地址的低三位(不包括读写位)对应芯片的三个引脚,也就是说这三位是可以人为设定的,23=8,所以一条I2C总线上可以挂载8个AT24C02。

    AT24C02的地址为7位二进制数,下图中最后一位是读写位(数据方向位),1 表示读数据,0 表示写数据。

    这样,7位设备地址加1位读写位,构成I2C的寻址数据。I2C 总线的寻址过程中,通常在起始条件后的第一个字节决定了主机选择哪一个从机,该字节的最后一位决定数据传输方向。

    对AT24C02进行读写

    AT24C02的存储空间为2K位(256字节),在对其进行写数据时,最小写入单位为字节(Byte),最大写入单位为页(Page),AT24C02页大小为 16 Byte。

    字节写

    在字节写模式下,主器件发送起始信号和从器件地址信息(R/W 位置零)给从器件,在从器件送回应答信号后,主器件发送 AT24WC01/02/04/08/16 的字节地址,主器件在收到从器件的应答信号后,再发送数据到被寻址的存储单元。AT24WC01/02/04/08/16 再次应答,并在主器件产生停止信号后开始内部数据的擦写,在内部擦写过程中,AT24WC01/02/04/08/16 不再应答主器件的任何请求。
    .
    ——AT24CXX中文手册

    页写

    用页写,AT24WC01 可一次写入 8 个字节数据,AT24WC02/04/08/16 可以一次写入 16 个字节的数据,页写操作的启动和字节写一样,不同在于传送了一字节数据后并不产生停止信号,主器件被允许发送 P(AT24WC01 P=7;AT24WC02/04/08/16 P=15)个额外的字节。每发送一个字节数据后 AT24WC01/02/04/08/16 产生一个应答位并将字节地址低位加 1,高位保持不变。
    .
    如果在发送停止信号之前主器件发送超过P+1个字节,地址计数器将自动翻转,先前写入的数据被覆盖。
    .
    接收到P+1字节数据和主器件发送的停止信号后,AT24CXXX启动内部写周期将数据写到数据区,所有接收的数据在一个写周期内写入AT24WC01/02/04/08/16。
    .
    ——AT24CXX中文手册

    当前地址读

    AT24WC01/02/04/08/16 的地址计数器内容为最后操作字节的地址加 1。也就是说 如果上次读/写的操作地址为 N,则立即读的地址从地址 N+1 开始。如果 N=E(这里对 24WC01 E=127;对 24WC02 E=255;对 24WC04 E=511;对 24WC08 E=1023;对 24WC16 E=2047)则计数器将翻转到 0 且继续输出数据。AT24WC01/02/04/08/16 接收到从器件地址信号后(R/W 位置 1),它首先发送一个应答信号,然后发送一个 8 位字节数据。主器件不需发送一个应答信号,但要产生一个停止信号。
    .
    ——AT24CXX中文手册

    选择读(随机读)

    选择性读操作允许主器件对寄存器的任意字节进行读操作,主器件首先通过发送起始信号、从器件地址和它想读取的字节数据的地址执行一个伪写操作。在 AT24WC01/02/04/08/16 应答之后,主器件重新发送起始信号和从器件地址,此时 R/W 位置 1,AT24WC01/02/04/08/16 响应并发送应答信号,然后输出所要求的一个 8 位字节数据,主器件不发送应答信号但产生一个停止信号。
    .
    ——AT24CXX中文手册

    连续读

    连续读操作可通过立即读或选择性读操作启动。在 AT24WC01/02/04/08/16 发送完一个 8 位字节数据后,主器件产生一个应答信号来响应,告知 AT24WC01/02/04/08/16 主器件要求更多的数据,对应每个主机产生的应答信号 AT24WC01/02/04/08/16 将发送一个 8 位数据字节。当主器件不发送应答信号而发送停止位时结束此操作。
    .
    从 AT24WC01/02/04/08/16 输出的数据按顺序由 N 到 N+1 输出。读操作时地址计数器在 AT24WC01/02/04/08/16 整个地址内增加,这样整个寄存器区域在可在一个读操作内全部读出。当读取的字节超过 E(对于 24WC01 E=127;对 24WC02 E=255; 对 24WC04 E=511;对 24WC08 E=1023;对 24WC16 E=2047)计数器将翻转到零并继续输出数据字节。
    .
    ——AT24CXX中文手册

    51单片机控制AT24C02

    I2C协议代码

    以下代码仅供参考,属于我练手的代码,并不能保证没有Bug

    大部分51单片机没有自带的I2C接口,所以这里使用纯软件代码实现I2C协议。

    51单片机型号——AT89C52RC

    延时函数
    模拟I2C协议,延时函数必不可少,由于I2C的延时都很短,直接用空语句实现延时。

    /******************************************************************************
     * @ 函数名  : Delay_10us
     * @ 功  能  : 10us粗略延时
     * @ 参  数  : 延时时间--单位10us
     * @ 返回值  : 无
     ******************************************************************************/
    void Delay_10us(unsigned int time)
    {
    	while(time--);
    }
    
    /******************************************************************************
     * @ 函数名  : I2c_Delay
     * @ 功  能  : I2C延时函数
     * @ 参  数  : 无
     * @ 返回值  : 无
     ******************************************************************************/
    void I2c_Delay()
    {
    	Delay_10us(1);
    }
     
    

    1. 起始信号

    /******************************************************************************
     * @ 函数名  : I2c_Start
     * @ 功  能  : I2C起始信号
     * @ 参  数  : 无
     * @ 返回值  : 无
     ******************************************************************************/
    void I2c_Start()
    {
    	sda = 1;
    	scl = 1;
    	I2c_Delay(); //起始信号建立时间
    	sda = 0;     //SDA拉低,下降沿
    	I2c_Delay(); //起始信号保持时间
    	scl = 0;
    }
    

    2. 停止信号

    /******************************************************************************
     * @ 函数名  : I2c_Stop
     * @ 功  能  : I2C停止信号
     * @ 参  数  : 无
     * @ 返回值  : 无
     ******************************************************************************/
    void I2c_Stop()
    {
    	scl = 0;
    	I2c_Delay(); //上一个时钟周期的低电平
    	sda = 0;
    	scl = 1;
    	I2c_Delay(); //停止信号建立时间
    	sda = 1;     //SDA拉高,上升沿
    	I2c_Delay(); //总线空闲时间保持
    }
    

    3. 应答信号

    /******************************************************************************
     * @ 函数名  : I2c_Ack
     * @ 功  能  : I2C应答信号
     * @ 参  数  : 无
     * @ 返回值  : 无
     ******************************************************************************/
    void I2c_Ack()
    {
    	scl = 0;
    	sda = 0;     //SDA拉低,发出应答信号
    	I2c_Delay();
    	scl = 1;
    	I2c_Delay(); 
    	scl = 0;
    }
    
    

    4. 非应答信号

    /******************************************************************************
     * @ 函数名  : I2c_No_Ack
     * @ 功  能  : I2C非应答信号
     * @ 参  数  : 无
     * @ 返回值  : 无
     ******************************************************************************/
    void I2c_No_Ack()
    {
    	scl = 0;
    	sda = 1;     //SDA拉高,发出非应答信号
    	I2c_Delay();
    	scl = 1;
    	I2c_Delay(); 
    	scl = 0;
    }
    

    5. 等待应答信号

    /******************************************************************************
     * @ 函数名  : I2c_Wait_Ack
     * @ 功  能  : I2C等待应答信号
     * @ 参  数  : 无
     * @ 返回值  : 1:接收到应答信号,0:接收到非应答信号
     ******************************************************************************/
    unsigned char I2c_Wait_Ack()
    {
    	unsigned char ack = 0;
    	sda = 1;
    	scl = 0;	    
    	I2c_Delay();
    	scl = 1;
    	I2c_Delay(); 
    	if(sda == 0) //检测数据线SDA是否被拉低
    		ack = 1;
    	else 
    		ack = 0;
    	scl = 0;
    	return ack;
    }
    
    

    6. I2C写字节

    /******************************************************************************
     * @ 函数名  : I2c_Write_Byte
     * @ 功  能  : I2C写字节
     * @ 参  数  : dat 要写入的字节数据
     * @ 返回值  : 无
     ******************************************************************************/
    void I2c_Write_Byte(unsigned char dat)
    {
    	unsigned char i = 0;
    	
    	
    	for(i = 0; i < 8; i++) //读取8位
    	{
    		scl = 0;	
    		I2c_Delay();
    		if(dat & 0x80)  //发送最高位
    			sda = 1;
    		else 
    			sda = 0;
    		scl = 1; 
    		I2c_Delay();		
    		dat <<= 1;  //左移1位
    	}
    	scl = 0;
    }
    

    7. I2C读字节

    /******************************************************************************
     * @ 函数名  : I2c_Read_Byte
     * @ 功  能  : I2C读字节
     * @ 参  数  : 无
     * @ 返回值  : 读取的字节数据
     ******************************************************************************/
    unsigned char I2c_Read_Byte()
    {
    	unsigned char dat = 0, i = 0;
        
    	for(i = 0; i < 8; i++) //读取8位
    	{
    		dat <<= 1;   //左移1位
    		scl = 0;	
    		I2c_Delay();
    		scl = 1;     //SCL高电平
    		I2c_Delay();
    		if(sda)      //读取SDA状态
    			dat |= 0x1;
    	}
    	scl = 0;
    	return dat;
    }
    

    8. AT24C02写字节
    设备地址为0xA0,说明AT24C02的3个地址引脚都接地(或悬空),7位地址为1010000。

    /******************************************************************************
     * @ 函数名  : At24c02_Write
     * @ 功  能  : AT24C02写字节
     * @ 参  数  : 
     *              addr 要写数据的地址(存储空间)
     *              dat  要写入的字节数据
     * @ 返回值  : 无
     ******************************************************************************/
    void At24c02_Write(unsigned char addr, unsigned char dat)
    {
    	I2c_Start();
    	I2c_Write_Byte(0xA0); //发送I2C设备地址
    	I2c_Wait_Ack();       //等待从机响应
    	I2c_Write_Byte(addr); //发送要写入的内存地址
    	I2c_Wait_Ack();
    	I2c_Write_Byte(dat);  //写入数据
    	I2c_Wait_Ack();
    	I2c_Stop();
    	Delay_10us(1000);     //写周期
    }
    

    8. AT24C02读字节
    设备地址为0xA0,说明AT24C02的3个地址引脚都接地(或悬空),7位地址为1010000。读数据时,读写位为1,所以8位的I2C地址(设备地址+读写位)为10100001,即0xA1。

    /******************************************************************************
     * @ 函数名  : At24c02_Read
     * @ 功  能  : AT24C02读字节
     * @ 参  数  : addr 要读数据的地址(存储空间)
     * @ 返回值  : 读取的字节数据
     ******************************************************************************/
    unsigned char At24c02_Read(unsigned char addr)
    {
    	unsigned char dat = 0, i = 0;
    	I2c_Start();
    	I2c_Write_Byte(0xA0); //发送I2C设备地址
    	I2c_Wait_Ack();       //等待从机响应
    	I2c_Write_Byte(addr); //发送要写入的内存地址
    	I2c_Wait_Ack(); 
    	I2c_Start();
    	I2c_Write_Byte(0xA1); //发送I2C设备地址(读数据)
    	I2c_Wait_Ack();       //等待从机响应
    	dat = I2c_Read_Byte(); //读取数据
    	I2c_Wait_Ack(); 
    	I2c_Stop();
    	return dat;
    }
    
    

    还有其他AT24C02的代码(页写、连续读),我这里就不写了。

    Proteus小实验

    这里用上面的代码仿真一个小实验,完整工程代码见文末

    实现功能:先向 AT24C02 的 0 ~ 7 号地址空间写入 8 ~ 1,再向 AT24C02 的 0 ~ 7 号地址空间写入 1 ~ 8。写入后立刻从 AT24C02 中读出,同时显示在数码管上。

    Proteus仿真

    24C02的 A0、A1、A2 都接地,所以它的I2C设备地址为1010000,写地址为10100000(0xA0),读地址为10100001(0xA1)

    请添加图片描述

    完整代码

    本人水平有限,仅供参考。

    #include <reg52.h>   //此文件中定义了单片机的一些特殊功能寄存器
    
    
    sbit scl = P2^1;
    sbit sda = P2^0;
    unsigned char code coding[16]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
    					0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//显示0-F的值
    
    					
    #define LED_TUBE  P3 //P3的8个IO端口对应数码管的8个信号引脚
    					
    					
    /******************************************************************************
     * @ 函数名  : Delay_10us
     * @ 功  能  : 10us粗略延时
     * @ 参  数  : 延时时间--单位10us
     * @ 返回值  : 无
     ******************************************************************************/
    void Delay_10us(unsigned int time)
    {
    	while(time--);
    }
    
    /******************************************************************************
     * @ 函数名  : I2c_Delay
     * @ 功  能  : I2C延时函数
     * @ 参  数  : 无
     * @ 返回值  : 无
     ******************************************************************************/
    void I2c_Delay()
    {
    	Delay_10us(1);
    }
    
    /******************************************************************************
     * @ 函数名  : I2c_Start
     * @ 功  能  : I2C起始信号
     * @ 参  数  : 无
     * @ 返回值  : 无
     ******************************************************************************/
    void I2c_Start()
    {
    	sda = 1;
    	scl = 1;
    	I2c_Delay(); //起始信号建立时间
    	sda = 0;     //SDA拉低,下降沿
    	I2c_Delay(); //起始信号保持时间
    	scl = 0;
    }
    
    /******************************************************************************
     * @ 函数名  : I2c_Stop
     * @ 功  能  : I2C停止信号
     * @ 参  数  : 无
     * @ 返回值  : 无
     ******************************************************************************/
    void I2c_Stop()
    {
    	scl = 0;
    	I2c_Delay(); //上一个时钟周期的低电平
    	sda = 0;
    	scl = 1;
    	I2c_Delay(); //停止信号建立时间
    	sda = 1;     //SDA拉高,上升沿
    	I2c_Delay(); //总线空闲时间保持
    }
    
    
    /******************************************************************************
     * @ 函数名  : I2c_Ack
     * @ 功  能  : I2C应答信号
     * @ 参  数  : 无
     * @ 返回值  : 无
     ******************************************************************************/
    void I2c_Ack()
    {
    	scl = 0;
    	sda = 0;     //SDA拉低,发出应答信号
    	I2c_Delay();
    	scl = 1;
    	I2c_Delay(); 
    	scl = 0;
    }
    
    
    /******************************************************************************
     * @ 函数名  : I2c_No_Ack
     * @ 功  能  : I2C非应答信号
     * @ 参  数  : 无
     * @ 返回值  : 无
     ******************************************************************************/
    void I2c_No_Ack()
    {
    	scl = 0;
    	sda = 1;     //SDA拉高,发出非应答信号
    	I2c_Delay();
    	scl = 1;
    	I2c_Delay(); 
    	scl = 0;
    }
    
    /******************************************************************************
     * @ 函数名  : I2c_Wait_Ack
     * @ 功  能  : I2C等待应答信号
     * @ 参  数  : 无
     * @ 返回值  : 1:接收到应答信号,0:接收到非应答信号
     ******************************************************************************/
    unsigned char I2c_Wait_Ack()
    {
    	unsigned char ack = 0;
    	sda = 1;
    	scl = 0;	    
    	I2c_Delay();
    	scl = 1;
    	I2c_Delay(); 
    	if(sda == 0) //检测数据线SDA是否被拉低
    		ack = 1;
    	else 
    		ack = 0;
    	scl = 0;
    	return ack;
    }
    
    
    
    /******************************************************************************
     * @ 函数名  : I2c_Write_Byte
     * @ 功  能  : I2C写字节
     * @ 参  数  : dat 要写入的字节数据
     * @ 返回值  : 无
     ******************************************************************************/
    void I2c_Write_Byte(unsigned char dat)
    {
    	unsigned char i = 0;
    	
    	
    	for(i = 0; i < 8; i++) //读取8位
    	{
    		scl = 0;	
    		I2c_Delay();
    		if(dat & 0x80)  //发送最高位
    			sda = 1;
    		else 
    			sda = 0;
    		scl = 1; 
    		I2c_Delay();		
    		dat <<= 1;  //左移1位
    	}
    	scl = 0;
    }
    
    
    /******************************************************************************
     * @ 函数名  : I2c_Read_Byte
     * @ 功  能  : I2C读字节
     * @ 参  数  : 无
     * @ 返回值  : 读取的字节数据
     ******************************************************************************/
    unsigned char I2c_Read_Byte()
    {
    	unsigned char dat = 0, i = 0;
        
    	for(i = 0; i < 8; i++) //读取8位
    	{
    		dat <<= 1;   //左移1位
    		scl = 0;	
    		I2c_Delay();
    		scl = 1;     //SCL高电平
    		I2c_Delay();
    		if(sda)      //读取SDA状态
    			dat |= 0x1;
    	}
    	scl = 0;
    	return dat;
    }
    
    /******************************************************************************
     * @ 函数名  : At24c02_Write
     * @ 功  能  : AT24C02写字节
     * @ 参  数  : 
     *              addr 要写数据的地址(存储空间)
     *              dat  要写入的字节数据
     * @ 返回值  : 无
     ******************************************************************************/
    void At24c02_Write(unsigned char addr, unsigned char dat)
    {
    	I2c_Start();
    	I2c_Write_Byte(0xA0); //发送I2C设备地址
    	I2c_Wait_Ack();       //等待从机响应
    	I2c_Write_Byte(addr); //发送要写入的内存地址
    	I2c_Wait_Ack();
    	I2c_Write_Byte(dat);  //写入数据
    	I2c_Wait_Ack();
    	I2c_Stop();
    	Delay_10us(1000);     //写周期
    }
    
    
    /******************************************************************************
     * @ 函数名  : At24c02_Read
     * @ 功  能  : AT24C02读字节
     * @ 参  数  : addr 要读数据的地址(存储空间)
     * @ 返回值  : 读取的字节数据
     ******************************************************************************/
    unsigned char At24c02_Read(unsigned char addr)
    {
    	unsigned char dat = 0, i = 0;
    	I2c_Start();
    	I2c_Write_Byte(0xA0); //发送I2C设备地址
    	I2c_Wait_Ack();       //等待从机响应
    	I2c_Write_Byte(addr); //发送要写入的内存地址
    	I2c_Wait_Ack(); 
    	I2c_Start();
    	I2c_Write_Byte(0xA1); //发送I2C设备地址(读数据)
    	I2c_Wait_Ack();       //等待从机响应
    	dat = I2c_Read_Byte(); //读取数据
    	I2c_Wait_Ack(); 
    	I2c_Stop();
    	return dat;
    }
    
    
    /******************************************************************************
     * @ 函数名  : main
     * @ 功  能  : 主函数
     * @ 参  数  : 无
     * @ 返回值  : 无
     ******************************************************************************/
    
    int main()
    {	
    	unsigned char i = 0;
    	Delay_10us(100);  //AT24C02上电时序
    	
    	while(1)
    	{
    		for(i = 0; i < 8; i++)
    		{
    			//向AT24C02前8个字节空间写8-i
    			At24c02_Write(i, 8 - i);
    			//读取该地址的数据,并显示到数码管
    			LED_TUBE = coding[At24c02_Read(i)];
    			//粗略延时500ms
    			Delay_10us(50000);	 	
    		}
    		for(i = 0; i < 8; i++)
    		{
    			//向AT24C02前8个字节空间写i+1
    			At24c02_Write(i, i + 1);
    			//读取该地址的数据,并显示到数码管
    			LED_TUBE = coding[At24c02_Read(i)];
    			//粗略延时500ms
    			Delay_10us(50000);			
    		}	
    	}
    }
    
    物联沃分享整理
    物联沃-IOTWORD物联网 » EEPROM(AT24C02 I2C协议)入门: 嵌入式硬件

    发表评论