Dotcpp  >  编程教程  >    >  IIC&EEPROM合成文件

IIC&EEPROM合成文件

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

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的相关函数和EEPROM的相关函数合成一个单独文件,大家新建两个文件“iic_eeprom.c”和“iic_eeprom.h”。


2.iic_eeprom.c的代码

#include <reg52.h>
#include <iic_eeprom.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_eeprom.h的代码

#ifndef __IIC_EEPROM_H__
#define __IIC_EEPROM_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 I2CWrite(unsigned char dat)”函数里我们没有用bit数据类型,改为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。

“unsigned char I2CReadACK()”和“unsigned char I2CReadNAK()”这里我们合成了一个函数为“unsigned char I2CReadNAK_OR_ACK(unsigned char nak_or_ack)”,利用参数的传递决定是否产生应答。

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


5.main.c测试代码

#include <reg52.h>
#include <function.h>
#include <lcd.h>
#include <iic_eeprom.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);
}

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

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

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

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

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

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

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

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

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

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