我们这一讲简要讲解如何往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);//程序暂停运行
    }
}

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

点赞(0)

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

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

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

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

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

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

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

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

Dotcpp在线编译      (登录可减少运行等待时间)