【STC8A8K64S4A12开发板】—片外存储器E2PROM讲解

版权声明:本文为博主原创文章,转载请附上原文出处链接。


前言

今天介绍下STC8A8K64S4A12系列单片机外部E2PROM存储器原理和I2C总线的原理及工作模式,掌握STC8A8K64S4A12系列单片机I2C外设相关的寄存器配置及程序设计。


一、硬件设计

1.E2PROM存储器介绍

EEPROM(全称是Electrically Erasable Programmable Read-Only Memory)带电可擦除可编程只读存储器,该类存储器是用户可更改的只读存储器(ROM),其可通过高于普通电压的作用来擦除和重编程(重写)。EEPROM(常写成E2PROM)是一种特殊形式的闪存,其应用通常是个人电脑中的电压来擦写和重编程。

E2PROM存储器可分为片内E2PROM和片外E2PROM,片外E2PROM和单片机之间通过各种通信接口连接,而最为常见的通信接口即是I2C接口。一般情况下,E2PROM可写入或擦除的次数是30~100万次,而读取次数是没有限制的。

24C02芯片是一种常用的基于I2C通信协议的E2PROM元件,例如ATMEL公司的AT24C02、CATALYST公司的CAT24C02和ST公司的ST24C02等芯片。24C02芯片的存储空间是256字节(2048位),当然如果需要更大存储空间的存储器可以选择其他型号的,比如24C04芯片(512字节)、24C08芯片(1024字节)和24C16芯片(2048字节)。

24Cxx系列引脚定义是一致的,这方便用户在项目设计时,如遇到选择的芯片的存储空间不够时,可在该系列中选择存储空间更大的芯片直接替换,而无需改动硬件部分。

在这里插入图片描述

图1:24C02芯片引脚

STC8A8K64S4A12开发板上设计了可供用户使用的AT24C02存储器芯片,其中原理图部分及硬件实物部分如下。

在这里插入图片描述

图2:开发板外扩E2PROM存储器

2.开发板DAC硬件电路介绍

I2C总线(即IIC总线)是集成电路总线(Inter-Integrated Circuit)的缩写,是一种简单、双向二线制同步串行总线。该I2C总线是1982年由荷兰的Philips公司为了解决电视机内的CPU和外围芯片之间连接而开发的,可以说电视机是最早的嵌入式系统之一。

I2C总线是一个真正的多主机总线,如果两个或多个主机同时初始化数据传输,可以通过冲突检测和仲裁防止数据破坏,每个连接到总线上的器件都有唯一的地址,任何器件既可以作为主机也可以作为从机,但同一时刻只允许有一个主机。

2.1.主要特征

典型的I2C应用原理如下图所示,I2C总线通信仅需两根信号线,可以连接多个设备,从设备都有唯一的地址,主设备通过从设备的地址和不同的从设备通信。

在这里插入图片描述

图3:典型I2C总线结构
  1. I2C总线硬件结构简单,仅需一根时钟线(SCL)、一根数据线(SDA)和两个上拉电阻即可实现通信。I2C总线的SCL和SDA均为开漏结构,开漏结构的电路只能输出“逻辑0”,无法输出“逻辑1”,因此SCL和SDA需要连接上拉电阻。上拉电阻的阻值影响传输速率,阻值越大,由于RC影响,会带来上升时间的增大,传输的速率慢,阻值小,传输的速率快,但是会增加电流的消耗,一般情况下,我们会选择4.7K左右的阻值,在从机数量少,信号线短的情况下,可以适当增加阻值,如使用10K的阻值。
  2. I2C总线中的从设备必须有自己的地址,并且该地址在其所处的I2C总线中唯一,主设备通过此唯一的地址即可和该从设备进行数据传输。
  3. I2C总线支持多主机,但是同一时刻只允许有一个主机。I2C总线中存在多个主机时,为了避免冲突,I2C总线通过总线仲裁决定由哪一个主机控制总线。
  4. I2C总线只能传输8位的数据,数据速率在标准模式下可达100Kbit/s,快速模式下可达400Kbit/s,高速模式下可达3.4Mbit/s,另外一些变种实现了低速模式(10Kbps)和快速+模式(1Mbps)。
  5. 同时连接到同一个I2C总线上的设备数量受总线最大电容(400pF)的限制。
  6. I2C总线电流消耗很低,抗干扰强,适合应用于低功耗的场合。

2.2.I2C地址

I2C总线中的设备必须要有唯一的地址,这意味着如果在总线中接入两个相同的设备,该设备必须有配置地址的功能,这也是我们经常用的I2C接口的设备会有几个引脚用来配置地址的原因。

对于I2C地址,我们经常看到有的I2C接口设备在规格书中描述的是7位地址,而有的I2C接口设备在规格书中描述的是8位地址,他们有什么区别?(I2C也有10位地址,但用的较少,这里不做介绍,本文档中的内容不涉及到10位地址)。

7位地址和8位地址如下图所示,他们结构上是一样的,都是由7个地址位加一个用来表示读写的位组成,只是描述上有所区别。

  • 规格书中描述I2C地址是7位地址的设备:给出的是7个地址位加R/W位,最低位(R/W位)为0时表示为写地址,最低位为1时为读地址。如果把0和1分别带入R/W位,得到的地址就和8位地址一样了。
  • 规格书中描述I2C地址是8位地址的设备:直接给出写地址和读地址,也就是最低位(R/W位)为0时的地址和最低位为1时的地址。

在这里插入图片描述

图4:I2C地址

☆注:PCF8563时钟芯片手册上给出其I2C从机地址是8位,读地址为0xA3,写地址为0xA2。

在这里插入图片描述

图5:PCF8563芯片手册描述

由此可见,所谓的7位地址和8位地址实际上都是7位地址加上最低位的读写位,本质上是一样的,只是各个I2C接口设备的描述方式不一样。

I2C保留了如下表所示的两组I2C地址,这些地址用于特殊用途。

表1:保留地址
序号从机地址R/W位描述
10000 0000广播呼叫地址。
20000 0001起始字节。
30000 001XCBUS 地址。
40000 010X保留给不同的总线格式。
50000 011X保留到将来使用。
60000 1XXXHs 模式主机码。
71111 1XXX保留到将来使用。
81111 0XXX10 位从机寻址。

2.3.I2C数据传输

■ 起始和停止条件(START and STOP conditions)

所有的I2C事务都是以START开始、STOP结束,起始和停止条件总是由主机产生,如下图所示,当SCL为高电平时,SDA从高电平向低电平转换表示起始条件,当SCL是高电平时,SDA由低电平向高电平转换表示停止条件。如果总线中存在多个主机,先将SDA拉低的主机获得总线控制权。

在这里插入图片描述

图6:起始和停止条件

■ 字节格式(Byte format)

I2C总线发送到SDA上的数据必须为8位,即一次传输一个字节,每次传输可以发送的字节数量不受限制。每个字节后必须跟一个响应位,首先传输的是数据的最高位MSB,如果从机要完成一些其他功能后,例如一个内部中断服务程序才能接收或发送下一个完整的数据字节,那么从机可以将时钟线SCL保持为低电平强制主机进入等待状态,当从机准备好接收下一个字节数据并释放时钟线SCL后数据传输继续。

在这里插入图片描述

图7:I2C总线数据传输

2.4.ACK和NACK

每个字节后会跟随一个ACK信号。接收者通过ACK位告知发送者已经成功接收一字节数据并准备好接收下一字节数据。所有的时钟脉冲包括ACK信号的时钟脉冲都是由主机产生的。

  1. ACK信号:发送者发送完8位数据后,在ACK时钟脉冲期间释放SDA线,接收者可以将SDA拉低并在时钟信号为高时保持低电平,这样就产生了ACK信号,从而使得主机知道从机已成功接收数据并且准备好了接收下一数据。
  2. NACK信号:当SDA在第9个时钟脉冲的时候保持高电平,定义为NACK信号。这时,主机要么产生STOP条件来放弃这次传输,要么重复START条件来启动一个新的传输。

下面的5种情况会导致产生NACK信号:

  1. 发送方寻址的接收方在总线上不存在,因此总线上没有设备应答。
  2. 接收方正在处理一些实时的功能,尚未准备好与主机通信,因此接收方不能执行收发。
  3. 在传输期间,接收方收到不能识别的数据或者命令。
  4. 在传输期间,接收方无法接收更多的数据字节。
  5. 主-接收器要通知从-发送器传输的结束。

2.5.从机地址和R/W位

I2C数据传输如下图所示,在起始条件(S)后,发送从机地址,从机地址是7位,从机地址后紧跟着的第8位是读写位(R/W),读写位为0表示写,读写位为1表示读。数据传输一般由主机产生的停止位P 终止,但是,如果主机仍希望在总线上通信,他可以产生重复起始条件 S和寻址另一个从机而不是首先产生一个停止条件,在这种传输中可能有不同的读写格式结合。

在这里插入图片描述

图8:I2C总线传输时序

可能的数据传输格式有:

  1. 主机发送器发送到从机接收器,传输的方向不会改变,接收器应答每一个字节,如下图所示。

在这里插入图片描述

图9:主机发送器7位地址寻址从机接收器(传输方向不改变)
  1. 在第一个字节后,主机立即读从机,在第一次应答后,主机发送器变成主机接收器,从机接收器变成从机发送器。第一次应答仍由从机生成,主机生成后续应答。之前发送了一个非应答(A)的主机产生STOP条件。
    在这里插入图片描述
图10:主机发送第一个字节后立即读取从机
  1. 复合格式,如下图所示。传输改变方向的时侯,起始条件和从机地址都会被重复,但R/W位取反。如果主接收器发送重复START条件,他会在重复START条件之前发送一个非应答(A)。

在这里插入图片描述

图11:复合格式

3.STC8A8K64S4A12系列单片机I2C介绍

STC8A8K64S4A12系列单片机片内集成了1个I2C串行总线控制器,与标准I2C总线一样,STC8A8K64S4A12系列单片机的I2C总线支持两种操作模式:主模式和从模式。

在这里插入图片描述

图12:I2C总线工作模式

但需要知道STC8A8K64S4A12系列单片机的I2C协议与标准I2C协议相比较,有以下2种机制被忽略。

  1. 发送起始信号(START)后不进行仲裁。
  2. 时钟信号(SCL)停留在低电平时不进行超时检测。

STC8A8K64S4A12系列单片机的每一组I2C都有2个IO引脚供选择使用,如下表所示。.

表2:单片机I2C外设引脚分配

在这里插入图片描述
☆注:独立GPIO表示开发板没有其他的电路使用这个GPIO,非独立GPIO说明开发板有其他电路用到了该GPIO。须知同一时刻只能使能一组IO口作为I2C使用。

STC8A8K64S4A12系列单片机I2C使用哪一组IO口由P_SW2外设端口切换寄存器2的B4、B5位决定,如下图所示。

在这里插入图片描述

图13:外设端口切换寄存器2

☆注:一般P_SW2寄存器B4、B5位默认是0,即如果没有对P_SW2寄存器进行操作,则默认选择的I2C是P1.4、P1.5这一组。

二、软件设计

1.I2C相关寄存器汇集

STC8A8K64S4A12系列单片机使用I2C外设时会用到10个寄存器,如下表所示:

表3:STC8A8K64S4A12系列I2C使用寄存器汇总

在这里插入图片描述

2.寄存器解析

2.1.I2C配置寄存器I2CCFG

I2C配置寄存器控制I2C外设使能选择、工作模式、总线速度等,详见下图。

在这里插入图片描述

图14:I2C配置寄存器I2CCFG

2.2.I2C主机控制寄存器I2CMSCR

I2CMSCR主机控制寄存器的配置是针对I2C已配置为主机模式才有意义的,该寄存器的B7位用来控制主机模式的中断是否打开,B0~B3位为主机模式下的命令,此部分是重点,详见下图。

在这里插入图片描述

图15:I2C主机控制寄存器I2CMSCR

2.3.I2C主机辅助控制寄存器I2CMSAUX

当配置I2C总线为主机模式时,虽然可以按照I2C标准协议,控制I2CMSCR寄存器发送主机命令,但很多场合还是希望可以简化这个控制流程。I2CMSAUX寄存器的B0位可以帮助用户实现I2C自动发送数据并接收ACK信号。

在这里插入图片描述

图16:I2C主机辅助控制寄存器I2CMSAUX

2.4.I2C主机状态寄存器I2CMSST

I2C主机状态寄存器B7位是可读位,用来读取I2C控制器是否处于忙状态(BUSY),I2CMSST寄存器B6位是I2C控制器执行I2C主机命令后的中断标志位,需软件清零。

在这里插入图片描述

图17:I2C主机状态寄存器I2CMSST

2.5.I2C从机控制寄存器I2CSLCR

I2CSLCR从机控制寄存器可控制处于从机模式下的I2C设备是否使能接收START信号、接收STOP信号、接收1字节数据、发送1字节数据等的中断功能,详见下图。

在这里插入图片描述

图18:I2C从机控制寄存器I2CSLCR

2.6.I2C从机状态寄存器I2CSLST

I2C从机控制寄存器使能了接收START信号、接收STOP信号、接收1字节数据、发送1字节数据等的中断功能后,I2C从机状态寄存器I2CSLST不仅可读取I2C控制器是否处于忙状态(BUSY),还可以对接收START信号、接收STOP信号、接收1字节数据、发送1字节数据等的中断标志位进行读取,但这些位需软件清零。

在这里插入图片描述

图19:I2C从机状态寄存器I2CSLST

2.7.I2C从机地址寄存器I2CSLADR

I2C从机地址寄存器B1~B7位是从机设备地址位,STC8A8K64S4A12系列单片机的I2C外设又配置有B0位(MA位),用来设置是否忽略对从机设备地址的匹配。

在这里插入图片描述

图20:I2C从机地址寄存器I2CSLADR

☆注:前面有介绍I2C设备的地址是7位,此处I2C从机设备地址即是7位。

3.I2C配置步骤

针对STC8A8K64S4A12系列单片机I2C,软件的配置过程如下:

在这里插入图片描述

图21:I2C中断方式和非中断方式软件配置步骤

4.外部EEPROM存储器读写单字节实验(模拟I2C)

☆注:本节的实验源码是在“实验2-8-1:串口1收发实验(P3.0和P3.1)”的基础上修改。本节对应的实验源码是:“实验2-14-1:外部EEPROM存储器读写单字节实验(模拟I2C)”。

4.1.工程需要用到的c文件

本例需要用到的c文件如下表所示,工程需要添加下表中的c文件。

表4:实验需要用到的c文件
序号文件名后缀功能描述
1uart.c包含与用户uart有关的用户自定义函数。
2i2c.c包含与用户i2c通信有关的用户自定义函数。
3at24cxx.c操作AT24C系列存储器相关的用户自定义函数。
4delay.c包含用户自定义延时函数。

4.2.头文件引用和路径设置

■ 需要引用的头文件

#include "delay.h"  
#include "at24cxx.h"  
#include "uart.h"  

■ 需要包含的头文件路径

本例需要包含的头文件路径如下表:

表5:头文件包含路径
序号路径描述
1…\ Sourceuart.h、i2c.h、at24cxx.h和delay.h头文件在该路径,所以要包含。
2…\UseSTC8.h头文件在该路径,所以要包含。

MDK中点击魔术棒,打开工程配置窗口,按照下图所示添加头文件包含路径。

在这里插入图片描述

图22:添加头文件包含路径

4.3.编写代码

首先,在i2c.c文件中编写模拟i2c总线通信的基本函数,如下表所示。

表6:模拟i2c总线通信的相关函数汇集

在这里插入图片描述
关于每个模拟i2c总线通信的相关函数,下面给出详细代码。

程序清单:模拟I2C总线产生启动信号

   /*************************************************************************** 
 * 描  述 : 模拟I2C总线产生启动信号 
 * 入  参 : 无 
 * 返回值 : FALSE 报错  TRUE 启动成功 
 **************************************************************************/  
bit I2C_Start(void)  
{  
    SDA = 1;                     //I2C数据线置高电平  
    SCL = 1;                     //I2C时钟线置高电平  
    Delay10us();  
    if(!SDA)  return FALSE;      //判断SDA线为低电平则总线忙,退出   
    SDA = 0;             //判断SDA线为高电平则控制SDA为低,即SDA出现一个下降沿表示启动I2C  
    Delay10us();  
    if(SDA) return FALSE;          //判断SDA线为高电平则总线出错,退出  
    SDA = 0;                     //判断SDA线为低电平则依然拉低SDA  
    Delay10us();  
    return TRUE;  
} 

程序清单:模拟I2C总线产生停止信号

 /*************************************************************************** 
 * 描  述 : 模拟I2C总线产生停止信号 
 * 入  参 : 无 
 * 返回值 : 无 
 **************************************************************************/  
void I2C_Stop(void)  
{  
    SDA = 0;                    //I2C数据线置低电平  
    Delay10us();  
    SCL = 1;                    //I2C时钟线置高电平  
    Delay10us();  
    SDA = 1;                    //当SCL线为高电平时则SDA出现一个上升沿表示停止I2C  
    Delay10us();  
} 

程序清单:模拟I2C总线发送ACK命令

   /*************************************************************************** 
 * 描  述 : 模拟I2C总线发送ACK命令 
 * 入  参 : 无 
 * 返回值 : 无 
 **************************************************************************/  
void I2C_Ack(void)  
{     
    SCL = 0;                    //I2C时钟线置低电平  
    Delay10us();  
    SDA = 0;                    //I2C数据线置低电平  
    Delay10us();  
    SCL = 1;                    //I2C时钟线置高电平  
    Delay10us();  
    SCL = 0;                    //I2C时钟线置低电平,完成了在SCL线上产生一个时钟,此时SDA为低电平  
    Delay10us();  
} 

程序清单:模拟I2C总线发送NACK应答命令

     /*************************************************************************** 
 * 描  述 : 模拟I2C总线发送NACK应答命令 
 * 入  参 : 无 
 * 返回值 : 无 
 **************************************************************************/  
void I2C_NoAck(void)  
{     
    SCL = 0;                    //I2C时钟线置低电平  
    Delay10us();  
    SDA = 1;                    //I2C数据线置高电平  
    Delay10us();  
    SCL = 1;                    //I2C时钟线置高电平  
    Delay10us();  
    SCL = 0;                    //I2C时钟线置低电平,完成了在SCL线上产生一个时钟,此时SDA为高电平  
    Delay10us();  
} 

程序清单:模拟I2C总线接收ACK命令

   /*************************************************************************** 
 * 描  述 : 模拟I2C总线接收ACK命令 
 * 入  参 : 无 
 * 返回值 : FALSE 无ACK ; TRUE  有ACK 
 **************************************************************************/  
bit I2C_Check_ACK(void)          
{  
    SCL = 0;                    //I2C时钟线置低电平  
    Delay10us();  
    SDA = 1;                    //I2C数据线置高电平  
    Delay10us();  
    SCL = 1;                    //I2C时钟线置高电平  
    Delay10us();  
    if(SDA)                      //判断SDA线为高电平  
    {  
      SCL = 0;                 //I2C时钟线置低电平,完成了在SCL线上产生一个时钟,此时SDA为高电平  
      return FALSE;  
    }  
    SCL = 0;           //I2C时钟线置低电平,完成了在SCL线上产生一个时钟,此时SDA为低电平  
    return TRUE;  
} 

程序清单:模拟I2C总线发送数据

  /*************************************************************************** 
 * 描  述 : 模拟I2C总线发送数据(数据从高位到低位) 
 * 入  参 : 无 
 * 返回值 : FALSE 无ACK ; TRUE  有ACK 
 **************************************************************************/  
void I2C_SendByte(uint8 SendByte)   
{  
    uint8 i=8;  
    while(i--)  
    {  
            SCL = 0;  
            Delay10us();  
            if(SendByte&0x80)  
            SDA = 1;    
            else   
            SDA = 0;     
            SendByte<<=1;  
            Delay10us();  
            SCL = 1;  
            Delay10us();  
    }  
    SCL = 0;  
} 

程序清单:模拟I2C总线接收数据

   /*************************************************************************** 
 * 描  述 : 模拟I2C总线接收数据 
 * 入  参 : 无 
 * 返回值 : I2C总线返回的数据 
 **************************************************************************/  
uint8 I2C_ReceiveByte(void)    
{   
    uint8 i=8;  
    uint8 ReceiveByte=0;  
  
    SDA = 1;                  
    while(i--)  
    {  
      ReceiveByte<<=1;        
      SCL = 0;  
      Delay10us();  
        SCL = 1;  
      Delay10us();    
      if(SDA)  
      {  
        ReceiveByte|=0x01;  
      }  
    }  
    SCL = 0;  
    return ReceiveByte;  
}  

然后,在at24cxx.c文件中编写对E2PROM存储器的基本操作函数,如下表所示。

序号函数名功能描述
1AT24CXX_RcvOneByte从E2PROM指定地址读取单字节数据。
2AT24CXX_SendOneByte向E2PROM指定地址存入单字节数据。
3AT24CXX_SendLenByte向E2PROM指定地址存入多字节数据。
4AT24CXX_RcvLenByte从E2PROM指定地址读取多字节数据。
5AT24CXX_EraseOneByte擦除E2PROM指定地址单字节数据。
6AT24CXX_EraseAll擦除整个E2PROM芯片。

关于每个操作外部E2PROM相关用户函数,下面给出详细代码。

程序清单:从指定地址读取单字节数据函数

   /*********************************************************************************** 
 * 描  述 : 从芯片AT24CXX指定地址读取单字节数据 
 * 入  参 : 开始读数据的地址 
 * 返回值 : 读到的数据 
 ***********************************************************************************/  
uint8 AT24CXX_RcvOneByte(uint16 Addr)  
{                     
    uint8 temp=0;                                                                                  
   I2C_Start();                                     //启动总线  
      
    if(E2PROM_TYPE > AT24C16)  
    {  
        I2C_SendByte(SLAW);                              //发送写命令  
        I2C_Check_ACK();                                   //等待应答  
        I2C_SendByte(Addr>>8);                           //发送高地址  
        I2C_Check_ACK();                                   //等待应答  
    }else I2C_SendByte(SLAW+((Addr/256)<<1));          //发送器件地址,写数据  
  
    I2C_Check_ACK();                                      //等待应答  
   I2C_SendByte(Addr%256);                           //发送低地址  
    I2C_Check_ACK();                                    //等待应答      
    I2C_Start();                                            //启动总线    
    I2C_SendByte(SLAR);                               //设置为读模式                   
    I2C_Check_ACK();                                    //等待应答  
   temp=I2C_ReceiveByte();                           //读字节        
   I2C_Stop();                                            //结束总线      
    return temp;  
} 

程序清单:向指定地址存入单字节数据函数

  /*********************************************************************************** 
 * 描  述 : 向芯片AT24CXX指定地址写入单字节数据 
 * 入  参 : Addr:写入的目的起始地址  Data:要写入的数据  
 * 返回值 : 无 
 ***********************************************************************************/  
void AT24CXX_SendOneByte(uint16 Addr,uint8 Data)  
{                                                                                              
  I2C_Start();                                     //启动总线     
    if(E2PROM_TYPE > AT24C16)  
    {  
      I2C_SendByte(SLAW);                              //发送写命令  
        I2C_Check_ACK();                                   //等待应答  
        I2C_SendByte(Addr>>8);                           //发送高地址  
    }else I2C_SendByte(SLAW+((Addr/256)<<1));          //发送器件地址,写数据  
      
    I2C_Check_ACK();                                     //等待应答  
   I2C_SendByte(Addr%256);                            //发送低地址  
    I2C_Check_ACK();                                     //等待应答                                                      
    I2C_SendByte(Data);                                //发送字节数据                      
    I2C_Check_ACK();                                       //等待应答    
      
   I2C_Stop();                                      //结束总线  
    delay_ms(10);                                      //该延时保证连续发送字节的稳定性  
      
} 

程序清单:向指定地址存入多字节数据函数

   /*********************************************************************************** 
 * 描  述 : 向芯片AT24CXX里面的指定地址开始写入长度为Len的数据 
 * 入  参 : Addr:写入的目的起始地址  Data:要写入的数据  Len:要写入数据的长度   
 * 返回值 : 无 
 ***********************************************************************************/  
void AT24CXX_SendLenByte(uint16 Addr,uint8 *Data,uint16 Len)  
{     
    while(Len--)  
    {  
        AT24CXX_SendOneByte(Addr,*Data);  
      Addr++;  
      Data++;  
    }                                                     
}

程序清单:从指定地址读取多字节数据函数

  /*********************************************************************************** 
 * 描  述 : 从芯片AT24CXX里面的指定地址开始读出长度为Len的数据 
 * 入  参 :  Addr:读出的目的起始地址  Data:读出的数据  Len:要读出数据的长度 
 * 返回值 : 无 
 ***********************************************************************************/  
void AT24CXX_RcvLenByte(uint16 Addr,uint8 *Data,uint16 Len)  
{     
    while(Len)  
    {  
        *Data++ = AT24CXX_RcvOneByte(Addr++);  
      Len--;  
    }                                                 
} 

程序清单:从指定地址擦除单字节数据函数

   /*********************************************************************************** 
 * 描  述 : 擦除芯片AT24CXX指定地址数据 
 * 入  参 : Addr:要擦除的目的地址 
 * 返回值 : 无 
 ***********************************************************************************/  
void AT24CXX_EraseOneByte(uint16 Addr)  
{     
    AT24CXX_SendOneByte(Addr,0xFF);                                                   
} 

程序清单:擦除整片数据函数

   /************************************************************************************ 
功能描述:擦除整个芯片AT24CXX(即芯片存储单元数据都是0xFF) 
入口参数:无 
返回值:无 
备注:   不同的存储芯片,存储空间不同,AT24CXX_SIZE数值不同。  
*************************************************************************************/  
void AT24CXX_EraseAll(void)  
{     
  uint16 i;  
      
    // 填充缓冲区  
    for (i = 0; i < AT24CXX_SIZE; i++)  
    {  
        AT24CXX_SendOneByte(i,0xFF);  
    }  
    
} 

最后,在主函数中对串口1进行初始化,并通过串口1发送不同的命令实现对单片机片外E2PROM的单字节读、写及擦除等操作。

代码清单:主函数

int main(void)  
   
uint8   Temp;  
   
P3M1 &= 0xFE;   P3M0 &= 0xFE;                     //设置P3.0为准双向口  
P3M1 &= 0xFD;   P3M0 |= 0x02;                     //设置P3.1为推挽输出  
  
Uart1_Init();                                 //串口1初始化    
EA = 1;                                       //使能总中断  
 delay_ms(10);                                 //初始化后延时  
  
 while(1)  
 {         
    if(WriteFLAG)                                //写模式  
     {                
          WriteFLAG=0;                              //写标志变量清零,发送一次  
          AT24CXX_SendOneByte(0x0010,0x33);           //在地址0x0010位置写入1个字节数据0x33  
       SendDataByUart1(0x33);                    //串口1发送数据0x33表示写操作完成  
     }  
     if(ReadFLAG)                                 //读模式  
     {  
          ReadFLAG=0;                               //读标志变量清零,发送一次  
          Temp=AT24CXX_RcvOneByte(0x0010);              //在地址0x0010位置处读取1个字符  
       SendDataByUart1(Temp);                    //串口1发送读取的字符  
     }    
     if(ClearFLAG)                                //清除模式  
     {  
          ClearFLAG=0;                              //清除标志变量清零,发送一次  
          AT24CXX_EraseOneByte(0x0010);             //擦除地址0x0010位置处数据  
       SendDataByUart1(0x00);                    //串口1发送数据0x00表示擦除完成  
     }     
}

4.4.硬件连接

在这里插入图片描述

图23:开发板连接图

5.外部EEPROM存储器读写多字节实验(模拟I2C)

5.1.编写代码

首先,在at24cxx.c文件中编写模拟I2C方式的读写字节函数和对E2PROM存储器的基本操作函数。请参考“实验2-14-1:外部EEPROM存储器读写单字节实验(模拟I2C)”部分。

然后,在主函数中对串口1进行初始化,并通过串口1发送不同的命令实现对单片机片外E2PROM的多字节读、写及擦除等操作。

代码清单:主函数

 int main(void)  
{   
    P3M1 &= 0xFE;   P3M0 &= 0xFE;                     //设置P3.0为准双向口  
    P3M1 &= 0xFD;   P3M0 |= 0x02;                     //设置P3.1为推挽输出  
      
    Uart1_Init();                                 //串口1初始化    
    EA = 1;                                       //使能总中断  
   delay_ms(10);                                 //初始化后延时  
  
  while(1)  
  {            
     if(WriteFLAG)                                //写模式  
         {                
              WriteFLAG=0;                              //写标志变量清零,发送一次  
              AT24CXX_SendLenByte(0x0010,scan,E2PROM_Length);  //向E2PROM起始地址0x0010中连续写入scan数组中的E2PROM_Length个字节数据  
        SendDataByUart1(0x33);                           //串口1发送数据0x33表示写操作完成  
         }  
         if(ReadFLAG)                                 //读模式  
         {  
              ReadFLAG=0;                               //读标志变量清零,发送一次  
              AT24CXX_RcvLenByte(0x0010,buffer,E2PROM_Length);   //从E2PROM地址0x0010开始连续读取E2PROM_Length字节数据并存入到buffer数组中  
           SendStringByUart1_n(buffer,E2PROM_Length);         //串口1发送数组buffer中的值(即读取的多字节数据)  
         }    
         if(ClearFLAG)                                //清除模式  
         {  
              ClearFLAG=0;                              //清除标志变量清零,发送一次  
              AT24CXX_EraseAll();                       //擦除整片,需要一定的耗时  
           SendDataByUart1(0x00);                    //串口1发送数据0x00表示擦除完成  
         }     
    }  
} 

5.2.硬件连接

在这里插入图片描述

图24:开发板连接图

6.外部EEPROM存储器读写单字节实验(硬件I2C)

6.1.工程需要用到的c文件

本例需要用到的c文件如下表所示,工程需要添加下表中的c文件。

表8:实验需要用到的c文件
序号文件名后缀功能描述
1uart.c包含与用户uart有关的用户自定义函数
2i2c.c包含与用户i2c通信有关的用户自定义函数
3at24cxx.c操作AT24C系列存储器相关的用户自定义函数
4delay.c包含用户自定义延时函数

6.2.头文件引用和路径设置

■ 需要引用的头文件

#include "delay.h"  
#include "at24cxx.h"  
#include "uart.h"  

■ 需要包含的头文件路径

本例需要包含的头文件路径如下表:

表9:头文件包含路径
序号路径描述
1…\ Sourceuart.h、i2c.h、at24cxx.h和delay.h头文件在该路径,所以要包含
2…\UserSTC8.h头文件在该路径,所以要包含

MDK中点击魔术棒,打开工程配置窗口,按照下图所示添加头文件包含路径。

在这里插入图片描述

图25:添加头文件包含路径

6.3.编写代码

首先,在i2c.c文件中对硬件I2C进行初始化,并编写硬件I2C总线通信的基本函数,如下表所示。

表8:实验需要用到的c文件

在这里插入图片描述

关于每个硬件i2c总线通信的相关函数,下面给出详细代码。

程序清单:硬件I2C初始化函数

 /*************************************************************************** 
 * 描  述 : I2C初始化函数 
 * 入  参 : 无 
 * 返回值 : 无 
 **************************************************************************/  
void I2C_init(void)  
{  
    P_SW2 |= 0x80;               //将EAXFR位置1,以访问在XDATA区域的扩展SFR  
    P_SW2 &= 0xCF;               //将I2C_S[1:0]置10,以选择I2C硬件功能脚为P7.6  P7.7  
    P_SW2 |= 0x20;               //将I2C_S[1:0]置10,以选择I2C硬件功能脚为P7.6  P7.7  
      
    I2CCFG=0xE0;               //使能I2C主机模式,I2C总线速度为等待65个时钟数  
    I2CMSST=0x00;              //清零I2C主机状态寄存器各标志位  
      
//  P_SW2 &= 0x7F;               //将EAXFR位置0,恢复访问XRAM     
}

程序清单:硬件I2C总线等待中断标志位函数

   /*************************************************************************** 
 * 描  述 : I2C总线等待中断标志位 
 * 入  参 : 无 
 * 返回值 : 无 
 **************************************************************************/  
void    I2C_Wait(void)    
{  
    while(!(I2CMSST&0x40));    //等待I2C主机状态寄存器的中断标志位置1  
    I2CMSST &= 0xBF;             //主机模式中断标志位软件清零  
} 

程序清单:硬件I2C总线产生启动信号函数

   /*************************************************************************** 
 * 描  述 : I2C总线产生启动信号 
 * 入  参 : 无 
 * 返回值 : 无 
 **************************************************************************/  
void I2C_Start(void)            
{  
    I2CMSCR=0x01;              //关闭主机模式中断并发送主机命令:0001(起始命令)  
    I2C_Wait();                //等待中断标志位置1  
} 

程序清单:硬件I2C总线产生停止信号函数

 /*************************************************************************** 
 * 描  述 : I2C总线产生停止信号 
 * 入  参 : 无 
 * 返回值 : 无 
 **************************************************************************/  
void I2C_Stop(void)                   
{  
    I2CMSCR=0x06;              //关闭主机模式中断并发送主机命令:0110(发送STOP命令)  
    I2C_Wait();                //等待中断标志位置1  
}  

程序清单:硬件I2C总线发送ACK命令函数

   /*************************************************************************** 
 * 描  述 : I2C总线发送ACK命令 
 * 入  参 : 无 
 * 返回值 : 无 
 **************************************************************************/  
void I2C_SendACK(void)          
{  
    I2CMSST=0x00;                //设置ACK信号  
    I2CMSCR=0x05;              //关闭主机模式中断并发送主机命令:0101(发送ACK命令)  
    I2C_Wait();                //等待中断标志位置1  
}

程序清单:硬件I2C总线发送NACK应答命令函数

   /*************************************************************************** 
 * 描  述 : I2C总线发送NACK应答命令 
 * 入  参 : 无 
 * 返回值 : 无 
 **************************************************************************/  
void I2C_SendNAK(void)            
{  
    I2CMSST=0x01;                  //设置NACK信号  
    I2CMSCR=0x05;                  //关闭主机模式中断并发送主机命令:0101(发送ACK命令)  
    I2C_Wait();                  //等待中断标志位置1  
} 

程序清单:硬件I2C总线接收ACK命令函数

   /*************************************************************************** 
 * 描  述 : I2C总线接收ACK命令 
 * 入  参 : 无 
 * 返回值 : 无 
 **************************************************************************/  
void I2C_RecvACK(void)          
{  
    I2CMSCR=0x03;                //关闭主机模式中断并发送主机命令:0011(接收ACK命令)  
    I2C_Wait();                //等待中断标志位置1  
} 

程序清单:硬件I2C总线发送数据函数

   /*************************************************************************** 
 * 描  述 : I2C总线发送数据 
 * 入  参 : 无 
 * 返回值 : 无 
 **************************************************************************/  
void I2C_SendData(uint8 dat)          
{  
    I2CTXD=dat;                    //写数据到数据缓冲区  
    I2CMSCR=0x02;                  //关闭主机模式中断并发送主机命令:0010(发送数据命令)  
    I2C_Wait();                //等待中断标志位置1  
}  

程序清单:硬件I2C总线接收数据函数

   /*************************************************************************** 
 * 描  述 : I2C总线接收数据 
 * 入  参 : 无 
 * 返回值 : I2C总线返回的数据 
 **************************************************************************/  
uint8 I2C_RecvData(void)              
{  
    I2CMSCR=0x04;              //关闭主机模式中断并发送主机命令:0100(接收数据命令)  
    I2C_Wait();                //等待中断标志位置1  
    return  I2CRXD;  
}  

然后,在at24cxx.c文件中编写对E2PROM存储器的基本操作函数,如下表所示。

表11:E2PROM相关用户函数汇集

在这里插入图片描述
关于每个操作外部E2PROM相关用户函数,下面给出详细代码。

程序清单:从指定地址读取单字节数据函数

   /*********************************************************************************** 
 * 描  述 : 在芯片AT24CXX指定地址读出一个数据 
 * 入  参 : 开始读数据的地址 
 * 返回值 : 读到的数据 
 ***********************************************************************************/  
uint8 AT24CXX_RcvOneByte(uint16 Addr)  
{                     
    uint8 temp=0;                                                                                  
   I2C_Start();                                     //启动总线  
      
    if(E2PROM_TYPE > AT24C16)  
    {  
        I2C_SendData(SLAW);                              //发送写命令  
        I2C_RecvACK();                                   //等待应答  
        I2C_SendData(Addr>>8);                           //发送高地址  
        I2C_RecvACK();                                   //等待应答  
    }else I2C_SendData(SLAW+((Addr/256)<<1));          //发送器件地址,写数据  
  
    I2C_RecvACK();                                    //等待应答  
   I2C_SendData(Addr%256);                           //发送低地址  
    I2C_RecvACK();                                    //等待应答      
    I2C_Start();                                      //启动总线    
    I2C_SendData(SLAR);                               //设置为读模式                   
    I2C_RecvACK();                                    //等待应答  
   temp=I2C_RecvData();                              //读字节        
   I2C_Stop();                                       //结束总线      
    return temp;  
}  

程序清单:向指定地址存入单字节数据函数

  /*********************************************************************************** 
 * 描  述 : 在芯片AT24CXX指定地址写入一个数据 
 * 入  参 : Addr:写入的目的起始地址  Data:要写入的数据  
 * 返回值 : 无 
 ***********************************************************************************/  
void AT24CXX_SendOneByte(uint16 Addr,uint8 Data)  
{                                                                                              
    I2C_Start();                                     //启动总线       
    if(E2PROM_TYPE > AT24C16)  
    {  
        I2C_SendData(SLAW);                              //发送写命令  
        I2C_RecvACK();                                   //等待应答  
        I2C_SendData(Addr>>8);                           //发送高地址  
        I2C_RecvACK();                                   //等待应答  
    }else I2C_SendData(SLAW+((Addr/256)<<1));          //发送器件地址,写数据  
      
    I2C_RecvACK();                                     //等待应答  
   I2C_SendData(Addr%256);                            //发送低地址  
    I2C_RecvACK();                                     //等待应答                                                    
    I2C_SendData(Data);                                //发送字节数据                      
    I2C_RecvACK();                                       //等待应答    
      
   I2C_Stop();                                      //结束总线  
    delay_ms(10);                                      //该延时保证连续发送字节的稳定性  
      
}  

程序清单:向指定地址存入多字节数据函数

  /*********************************************************************************** 
 * 描  述 : 向芯片AT24CXX里面的指定地址开始写入长度为Len的数据 
 * 入  参 : Addr:写入的目的起始地址  Data:要写入的数据  Len:要写入数据的长度 
 * 返回值 : 无 
 ***********************************************************************************/  
void AT24CXX_SendLenByte(uint16 Addr,uint8 *Data,uint16 Len)  
{     
    while(Len--)  
    {  
        AT24CXX_SendOneByte(Addr,*Data);  
    Addr++;  
    Data++;  
    }                                                     
}  

程序清单:从指定地址读取多字节数据函数

   /*********************************************************************************** 
 * 描  述 : 从芯片AT24CXX里面的指定地址开始读出长度为Len的数据 
 * 入  参 :  Addr:读出的目的起始地址  Data:读出的数据  Len:要读出数据的长度 
 * 返回值 : 无 
 ***********************************************************************************/  
void AT24CXX_RcvLenByte(uint16 Addr,uint8 *Data,uint16 Len)  
{     
    while(Len)  
    {  
        *Data++ = AT24CXX_RcvOneByte(Addr++);  
      Len--;  
    }                                                 
}  

程序清单:从指定地址擦除单字节数据函数

   /*********************************************************************************** 
 * 描  述 : 擦除芯片AT24CXX指定地址数据 
 * 入  参 : Addr:要擦除的目的地址 
 * 返回值 : 无 
 ***********************************************************************************/  
void AT24CXX_EraseOneByte(uint16 Addr)  
{     
    AT24CXX_SendOneByte(Addr,0xFF);                                         
} 

程序清单:擦除整片数据函数

   /*********************************************************************************** 
功能描述:擦除整个芯片AT24CXX(即芯片存储单元数据都是0xFF) 
入口参数:无 
返回值:无 
备注:   不同的存储芯片,存储空间不同,AT24CXX_SIZE数值不同。  
************************************************************************************/  
void AT24CXX_EraseAll(void)  
{     
  uint16  i;  
      
    // 填充缓冲区  
    for (i = 0; i < AT24CXX_SIZE; i++)  
    {  
        AT24CXX_SendOneByte(i,0xFF);  
    }  
    
}

最后,在主函数中对串口1和I2C进行初始化,并通过串口1发送不同的命令实现对单片机片外E2PROM的单字节读、写及擦除等操作。

代码清单:主函数

  int main(void)  
{   
    uint8   Temp;  
    
    P3M1 &= 0xFE;   P3M0 &= 0xFE;                     //设置P3.0为准双向口  
    P3M1 &= 0xFD;   P3M0 |= 0x02;                     //设置P3.1为推挽输出  
      
    I2C_init();                                   //IIC初始化  
    Uart1_Init();                                 //串口1初始化    
    EA = 1;                                       //使能总中断  
  delay_ms(10);                                 //初始化后延时  
  
  while(1)  
  {            
     if(WriteFLAG)                                //写模式  
         {                
              WriteFLAG=0;                              //写标志变量清零,发送一次  
              AT24CXX_SendOneByte(0x0010,0x33);           //在地址0x0010位置写入1个字节数据0x33  
        SendDataByUart1(0x33);                    //串口1发送数据0x33表示写操作完成  
         }  
         if(ReadFLAG)                                 //读模式  
         {  
              ReadFLAG=0;                               //读标志变量清零,发送一次  
              Temp=AT24CXX_RcvOneByte(0x0010);              //在地址0x0010位置处读取1个字符  
        SendDataByUart1(Temp);                    //串口1发送读取的字符  
         }    
         if(ClearFLAG)                                //清除模式  
         {  
              ClearFLAG=0;                              //清除标志变量清零,发送一次  
              AT24CXX_EraseOneByte(0x0010);             //擦除地址0x0010位置处数据  
        SendDataByUart1(0x00);                    //串口1发送数据0x00表示擦除完成  
         }     
    }  
}

6.4.硬件连接

在这里插入图片描述

图26:开发板连接图

7.外部EEPROM存储器读写多字节实验(硬件I2C)

7.1.编写代码

首先,在at24cxx.c文件中编写硬件I2C方式的读写字节函数和对E2PROM存储器的基本操作函数。请参考“实验2-14-3:外部EEPROM存储器读写单字节实验(硬件I2C)”部分。

然后,在主函数中对串口1和I2C进行初始化,并通过串口1发送不同的命令实现对单片机片外EEPROM的多字节读、写及擦除等操作。

代码清单:主函数

  int main(void)  
   
P3M1 &= 0xFE;   P3M0 &= 0xFE;                     //设置P3.0为准双向口  
P3M1 &= 0xFD;   P3M0 |= 0x02;                     //设置P3.1为推挽输出  
  
I2C_init();                                   //IIC初始化  
Uart1_Init();                                 //串口1初始化    
EA = 1;                                       //使能总中断  
 delay_ms(10);                                 //初始化后延时  
  
 while(1)  
 {         
    if(WriteFLAG)                                //写模式  
     {                
          WriteFLAG=0;                              //写标志变量清零,发送一次  
          AT24CXX_SendLenByte(0x0010,scan,E2PROM_Length);  //向E2PROM起始地址0x0010中连续写入scan数组中的E2PROM_Length个字节数据  
       SendDataByUart1(0x33);                           //串口1发送数据0x33表示写操作完成  
     }  
     if(ReadFLAG)                                 //读模式  
     {  
          ReadFLAG=0;                               //读标志变量清零,发送一次  
          AT24CXX_RcvLenByte(0x0010,buffer,E2PROM_Length);   //从E2PROM地址0x0010开始连续读取E2PROM_Length字节数据并存入到buffer数组中  
       SendStringByUart1_n(buffer,E2PROM_Length);         //串口1发送数组buffer中的值(即读取的多字节数据)  
     }    
     if(ClearFLAG)                                //清除模式  
     {  
          ClearFLAG=0;                              //清除标志变量清零,发送一次  
          AT24CXX_EraseAll();                       //擦除整片,需要一定的耗时  
       SendDataByUart1(0x00);                    //串口1发送数据0x00表示擦除完成  
     }     
}  

7.2.硬件连接

在这里插入图片描述

图27:开发板连接图

总结

以上是今天要讲的内容,希望对大家有帮助,如果有啥不明白的,欢迎讨论哦!

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

电子友人张

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值