1.浅释E2Write函数

宋老师的例程lesson14_3和lesson14_4里的“E2Write(unsigned char *buf, unsigned char addr, unsigned char len)”书写内容是不一样的,lesson14_4的E2Write函数比lesson14_3的E2Write函数运行高效,lesson14_3的E2Write函数是每写入一个字节就要经历起始信号,停止信号,写入下一个字节又要把这些步骤经历一遍。而lesson14_4的E2Write函数支持EEPROM页写入(参考《手把手教你学51单片机》文档14.3.3节),所以在建立起文件时我们选用lesson14_4的E2Write函数。

大家新建两个文件“iic.c”和“iic.h”,我们把IIC的相关函数和EEPROM的相关函数都合成在“iic.c”这个单独文件里。

2.iic.c的代码

#include <reg52.h>
#include <iic.h>
#include <intrins.h>
 
#define I2CDelay()  {_nop_();_nop_();_nop_();_nop_();}
 
/* 产生总线起始信号 */
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-待写入字节,返回值-从机应答位的值 */
unsigned char I2CWrite(unsigned char dat)
{
    unsigned char 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_OR_ACK(unsigned char nak_or_ack)
{
    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 = nak_or_ack; //8位数据发送完后,传入的参数NAK_OR_ACK决定是否应答,为1不应答,为0应答
    I2CDelay();
    I2C_SCL = 1;   //拉高SCL
    I2CDelay();
    I2C_SCL = 0;   //再拉低SCL完成非应答位,并保持住总线
  
    return dat;
}

/* E2读取函数,buf-数据接收指针,addr-E2中的起始地址,len-读取长度 */
void E2Read(unsigned char *buf, unsigned char addr, unsigned char len)
{
    do {                               //用寻址操作查询当前是否可进行读写操作
           I2CStart();
           if (I2CWrite(0x50<<1))      //应答则跳出循环,非应答则进行下一次查询
           {
               break;
           }
           I2CStop();
    } while(1);
    I2CWrite(addr);                    //写入起始地址
    I2CStart();                        //发送重复启动信号
    I2CWrite((0x50<<1)|0x01);          //寻址器件,后续为读操作
    while (len > 1)                    //连续读取len-1个字节
    {
        *buf++ = I2CReadNAK_OR_ACK(0); //最后字节之前为读取操作+应答
        len--;
    }
    *buf = I2CReadNAK_OR_ACK(1);       //最后一个字节为读取操作+非应答
    I2CStop();
}

/* E2写入函数,buf-源数据指针,addr-E2中的起始地址,len-写入长度 */
void E2Write(unsigned char *buf, unsigned char addr, unsigned char len)
{
    while (len > 0)
    {
        //等待上次写入操作完成
        do {                          //用寻址操作查询当前是否可进行读写操作
               I2CStart();
               if (I2CWrite(0x50<<1)) //应答则跳出循环,非应答则进行下一次查询
               {
                   break;
               }
               I2CStop();
        } while(1);
        //按页写模式连续写入字节
        I2CWrite(addr);           //写入起始地址
        while (len > 0)
        {
            I2CWrite(*buf++);     //写入一个字节数据
            len--;                //待写入长度计数递减
            addr++;               //E2地址递增
            if ((addr&0x07) == 0) //检查地址是否到达页边界,24C02每页8字节,
            {                     //所以检测低3位是否为零即可
                break;            //到达页边界时,跳出循环,结束本次写操作
            }
        }
        I2CStop();
    }
}


3.iic.h的代码

#ifndef __IIC_H__
#define __IIC_H__
 
sbit I2C_SCL = P3^7;
sbit I2C_SDA = P3^6;
void I2CStart();//产生总线起始信号
void I2CStop(); //产生总线停止信号
unsigned char I2CWrite(unsigned char dat);//I2C总线写操作,dat-待写入字节,返回值-从机应答位的值
unsigned char I2CReadNAK_OR_ACK(unsigned char nak_or_ack);//I2C总线读操作,并发送应答或者非应答信号,返回值-读到的字节
void E2Read(unsigned char *buf, unsigned char addr, unsigned char len); //E2读取函数,buf-数据接收指针,addr-E2中的起始地址,len-读取长度
void E2Write(unsigned char *buf, unsigned char addr, unsigned char len);//E2写入函数,buf-源数据指针,addr-E2中的起始地址,len-写入长度
 
#endif


4.部分代码的修改

“unsigned char I2CReadACK()”和“unsigned char I2CReadNAK()”这里我们合成了一个函数为

“unsigned char I2CReadNAK_OR_ACK(unsigned char nak_or_ack)”,利用参数的传递决定是否产生应答。

那么在main.c中,几乎也是只需要出现EEPROM的写函数“E2Write()”和读函数“E2Read()”而已了。

记得把“iic”添加到工程文件中

iic7


5.main.c测试代码

#include <reg52.h>
#include <function.h>//详见第六章第8讲
#include <lcd.h>     //详见第十一章第3讲
#include <iic.h>
 
void main()
{
    unsigned char buf[]={"We can learn SCM well!"};//我们可以学好单片机
    unsigned char str[sizeof(buf)];                //数组长度与buf的一样
  
    InitLcd1602();                //初始化液晶
    E2Write(buf,0x8E,sizeof(buf));//把buf数组里面的内容在EEPROM中从地址0x8E开始写,直到把数组里的内容全部写完进去,在EEPROM中保存起来
  
    delay_ms(1000);               //过1秒之后再读出里面的内容显示在液晶屏上
  
    E2Read(str,0x8E,sizeof(buf)); //用另一个数组存取从EEPROM中读出的内容
    LcdShowStr_len(0, 0,str, 16);
    LcdShowStr(0, 1, str+16+1);
    while(1);
}
点赞(0)

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

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

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

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

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

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

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

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

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