Dotcpp  >  编程教程  >  IIC通信  >  EEPROM简单使用

EEPROM简单使用

点击打开在线编译器,边学边练

我们这一讲简要讲解如何往EEPROM的地址0x55写入一个数据,然后读出这个数据的内容。本讲代码围绕的是宋老师的lesson14_2例程代码讲解。


1.写入一个字节内容

如果要在EEPROM的某个地址里写入内容,那么步骤的实现归为:

起始信号→找到这个器件是否存在(寻址),发送的字节最低位要为0意为要往这个器件写内容→选择EEPROM的哪个地址去写→写入8位的数据→停止信号。

宋老师写的“void E2WriteByte(unsigned char addr, unsigned char dat)”函数里面,上一讲都讲解过里面的函数了,写入一个字节内容的讲解我们就介绍完了。


2.读出一个字节内容

在“unsigned char E2ReadByte(unsigned char addr)”中前面三个函数与“void E2WriteByte(unsigned char addr, unsigned char dat)”都是一样的操作步骤,选定好要读出哪个地址的内容,然后还需再重新发送起始信号,接着是把寻址的字节最低位设置为1意为要读出EEPROM的某个地址里面的内容,因为只读一个字节,所以单片机在接收完EEPROM发送回来的数据(这个数据就是当初写进去的数据)之后,不产生拉低应答(是单片机不产生应答,不是说EEPROM不产生应答),这样EEPROM就不会再发送数据回来了,达到了只读一个字节的功能。

所谓单片机读EEPROM的数据出来,其实就是EEPROM在SDA线上不停地拉高拉低变化,而单片机就是不断地判断第一位是0或者1,第二位是0或者1······,这个细节过程大家有能力的话可以一步步去解读宋老师写的“unsigned char I2CReadNAK()”和“unsigned char I2CReadACK()”,其实这两个函数只有一处不同,那就是“I2C_SDA = 1;”和“I2C_SDA = 0;”是否产生应答。如果大家一句句地很难理解这个函数,那么不妨直接省去理解,拿来运用就可以了。


3.独立代码

在main.c里复制以下代码,编译下载进开发板,我们做的实验就是先在EEPROM的0x55的地址里写入数据71(也就是字符‘G’的ASCII码值),然后再去读出这个数据,将字符‘G’显示在液晶屏上。

之前的章节有提过为了兼容性我们不打算使用bit型的数据类型,所以修改了一下宋老师的“bit I2CWrite(unsigned char dat)”函数,改为unsigned char类型。

那么我们就不能像宋老师那样写为“return (~ack);”,而是写为“return (!ack);”。unsigned char类型下,ack如果是0,那么~ack就为0xFF;ack如果是1,~ack就为0xFE,所以“~ack”的书写方式会导致返回值不能限定在0或者1之间,也就会使代码功能失效。

而“!ack”的表达就是说,当ack等于0时,!ack只会等于1,不会等于0xFF这些。当ack不等于0时(比如“ack=0xFE;”这些),那么!ack只会等于0,这样保证了返回值只有0和1。

#include <reg52.h> 
#include <function.h>//详见第六章第8讲
#include <lcd.h>     //详见第十一章第3讲
#include <intrins.h>
 
#define I2CDelay()  {_nop_();_nop_();_nop_();_nop_();}
sbit I2C_SCL = P3^7;
sbit I2C_SDA = P3^6;
 
/* 产生总线起始信号 */
void I2CStart()
{
    I2C_SDA = 1; //首先确保SDA、SCL都是高电平
    I2C_SCL = 1;
    I2CDelay();
    I2C_SDA = 0; //先拉低SDA
    I2CDelay();
    I2C_SCL = 0; //再拉低SCL
}

/* 产生总线停止信号 */
void I2CStop()
{
    I2C_SCL = 0; //首先确保SDA、SCL都是低电平
    I2C_SDA = 0;
    I2CDelay();
    I2C_SCL = 1; //先拉高SCL
    I2CDelay();
    I2C_SDA = 1; //再拉高SDA
    I2CDelay();
}

/* I2C总线写操作,dat-待写入字节,返回值-从机应答位的值 */
u8 I2CWrite(unsigned char dat)
{
    u8 ack;                            //用于暂存应答位的值
    unsigned char mask;                //用于探测字节内某一位值的掩码变量
  
    for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
    {
        if ((mask&dat) == 0)  //该位的值输出到SDA上
            I2C_SDA = 0;
        else
            I2C_SDA = 1;
        I2CDelay();
        I2C_SCL = 1;          //拉高SCL
        I2CDelay();
        I2C_SCL = 0;          //再拉低SCL,完成一个位周期
    }
    I2C_SDA = 1;   //8位数据发送完后,主机释放SDA,以检测从机应答
    I2CDelay();
    I2C_SCL = 1;   //拉高SCL
    ack = I2C_SDA; //读取此时的SDA值,即为从机的应答值
    I2CDelay();
    I2C_SCL = 0;   //再拉低SCL完成应答位,并保持住总线
  
    return (!ack); //应答值取反以符合通常的逻辑:
                   //0=不存在或忙或写入失败,1=存在且空闲或写入成功
}

/* I2C总线读操作,并发送非应答信号,返回值-读到的字节 */
unsigned char I2CReadNAK()
{
    unsigned char mask;
    unsigned char dat;
  
    I2C_SDA = 1;                       //首先确保主机释放SDA
    for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
    {
        I2CDelay();
        I2C_SCL = 1;      //拉高SCL
        if(I2C_SDA == 0)  //读取SDA的值
            dat &= ~mask; //为0时,dat中对应位清零
        else
            dat |= mask;  //为1时,dat中对应位置1
        I2CDelay();
        I2C_SCL = 0;      //再拉低SCL,以使从机发送出下一位
    }
    I2C_SDA = 1;   //8位数据发送完后,拉高SDA,发送非应答信号
    I2CDelay();
    I2C_SCL = 1;   //拉高SCL
    I2CDelay();
    I2C_SCL = 0;   //再拉低SCL完成非应答位,并保持住总线
  
    return dat;
}
 
void main()
{  
    u8 str[2];
    InitLcd1602();//初始化液晶屏 
    while (1)
    {     
        I2CStart();
        I2CWrite(0x50<<1); //寻址器件,后续为写操作
        I2CWrite(0x55);    //写入要存储的地址为0x55
        I2CWrite('G');     //写入71这个数据,'G'的表达更加直观地表示要在液晶屏上显示的内容
        I2CStop();
    
        delay_ms(1000);    //等待一秒之后,准备读出这个数据
    
        I2CStart();
        I2CWrite(0x50<<1); //寻址器件,后续为写操作
        I2CWrite(0x55);    //选择要读出内容的地址为0x55
        I2CStart();        //发送重复启动信号
        I2CWrite((0x50<<1)|0x01); //寻址器件,后续为读操作
        str[0] = I2CReadNAK();    //读取这个器件0x55的地址里面的内容存放在数组的0号元素里
        I2CStop();
    
        LcdShowStr_len(7, 0, str, 1);//显示str[0]的内容
        while (1);//程序暂停运行
    }
}

为了更加直观了解这些过程,笔者并没有像宋老师那样给大家封装好写函数和读函数,这是为了大家不用在各个函数中跳来跳去地分析,在主函数里直接一步到位地讲解这些操作步骤,方便大家对这些细节的了解。


本文固定URL:https://www.dotcpp.com/course/392

C语言网提供由在职研发工程师或ACM蓝桥杯竞赛优秀选手录制的视频教程,并配有习题和答疑,点击了解:

一点编程也不会写的:零基础C语言学练课程

解决困扰你多年的C语言疑难杂症特性的C语言进阶课程

从零到写出一个爬虫的Python编程课程

只会语法写不出代码?手把手带你写100个编程真题的编程百练课程

信息学奥赛或C++选手的 必学C++课程

蓝桥杯ACM、信息学奥赛的必学课程:算法竞赛课入门课程

手把手讲解近五年真题的蓝桥杯辅导课程

单片机教程
第一章 单片机入门
第二章 LED及入门
第三章 蜂鸣器
第四章 数码管
第五章 独立按键
第六章 多文件编程
第七章 外部中断
第八章 定时器
第九章 舵机与超声波模块
第十章 串口通信
第十一章 1602液晶屏
第十二章 IIC通信
第十三章 红外遥控与温度传感器
第十四章 AD与DA
第十五章 混合例程
第十六章 完结
Dotcpp在线编译      (登录可减少运行等待时间)