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、信息学奥赛的必学课程:算法竞赛课入门课程
手把手讲解近五年真题的蓝桥杯辅导课程