I2C总线编程实例(k1-k4:写入、读取、加+、清零)【EEPROM-AT24C02】
(1)AT24C02是一种EEPROM元器件,是一种只读寄存器,断电保持,可保存数据100年,
是一种可擦除读写的芯片,相当于ROM硬盘,在下面实验中充当从机角色;
(2)51在下面实验中充当主机角色;
(3)在IIC总线标准协议上,进行51单片机(主机)和AT24C02(从机)的相互读写数据的操作。
小结:51单片机和各种EEPROM芯片之间可以通过IIC总线标准协议进行数据交互(通信)的。
实验:四个独立按键对应四个不同的功能,
k1:将数据写入单片机,断电保存
k2:读取上次保存的数据,断电后仍可读取上次保存的数据
k3:当前数据+1
k4:当前数据清零
------------------------------------------------------------- 采用多文件的框架模式 -------------------------------------------------------------
i2c.h:
/* 这个文件进行宏定义:定义I2C串行总线的相关数据端口、方法函数,以及定义一些使用频率较高的元素 */ #ifndef _I2C_H_ // 如果没有定义宏 #define _I2C_H_ // 定义一个宏 // 需要用到51单片机的管脚,所以需要引入库文件 #include <reg52.h> // 查单片机原理图可知(其中,SCL是时钟线,SDA是数据线) sbit SCL=P2^1; sbit SDA=P2^0; /* 相关函数 */ // I2C的起始信号函数 void I2cStart(); // I2C的终止信号函数 void I2cStop(); // I2C发送(写入)字节函数,成功返回1,失败返回0 unsigned char I2cSendByte(unsigned char dat); // I2C接收(读取)字节函数,返回读取的数据 unsigned char I2cReadByte(); // AT24C02芯片的写入数据函数 void At24c02Write(unsigned char addr, unsigned dat); // AT24C02芯片的读取数据函数,返回读取的数据 unsigned char At24c02Read(unsigned char addr); #endif // 结束
i2c.c:
/* 这个文件专门针对I2C模块的编程,其他模块可以新建另外一个文件 */ #include <i2c.h> // 引入I2C的库文件 /******************************************************************************* * 函数名 : Delay10us() * 函数功能 : 延时10us * 输入 : 无 * 输出 : 无 *******************************************************************************/ void Delay10us() //误差 0us { unsigned char a,b; for(b=1;b>0;b--) for(a=2;a>0;a--); } /******************************************************************************* * 函数名 : I2cStart() * 函数功能 : 起始信号:在SCL时钟信号在高电平期间SDA信号产生一个下降沿 * 输入 : 无 * 输出 : 无 * 备注 : 起始之后SDA和SCL都为0,表示总线被主机占用 *******************************************************************************/ void I2cStart() { // 根据各个单片机的时序图来写 SDA=1; Delay10us(); SCL=1; Delay10us(); // 建立时间是SDA保持时间>4.7us SDA=0; Delay10us(); // 保持时间是>4us SCL=0; Delay10us(); } /******************************************************************************* * 函数名 : I2cStop() * 函数功能 : 终止信号:在SCL时钟信号高电平期间SDA信号产生一个上升沿 * 输入 : 无 * 输出 : 无 * 备注 : 结束之后保持SDA和SCL都为1;表示总线处于空闲状态 *******************************************************************************/ void I2cStop() { // 根据各个单片机的时序图来写 SDA=0; Delay10us(); SCL=1; Delay10us(); // 建立时间是SDA保持时间>4.7us SDA=1; Delay10us(); // 保持时间是>4us } /******************************************************************************* * 函数名 : I2cSendByte(unsigned char dat) * 函数功能 : 通过I2C发送一个字节。在SCL时钟信号高电平期间,保持发送信号SDA保持稳定 * 输入 : num * 输出 : 0或1。发送成功返回1,发送失败返回0 * 备注 : 发送完一个字节SCL=0,SDA=1 *******************************************************************************/ unsigned char I2cSendByte(unsigned char dat) { unsigned char a=0, b=0; // 最大255,一个机器周期为1us,最大延时255us for(a=0;a<8;a++) // 一个字节8位,循环8次,每次发送1位二进制,从最高位开始 { // 起始信号之后SCL=0,所以可以直接改变SDA信号,右移7位,剩下1位(即最高位) SDA=dat>>7; dat<<=1; // dat=dat<<1;左移1位,把已经发送过去的最高位去掉 Delay10us(); SCL=1; // 时钟线为高电平,保持数据稳定 Delay10us(); // 建立时间>4.7us,发送数据 SCL=0; // 时钟线为低电平,为下次发送数据做准备 Delay10us(); // 时间大于4us } // 释放数据线和时钟线,等待从机的应答 SDA=1; Delay10us(); SCL=1; // 根据应答信号,如果从机应答(发送成功),则SDA是低电平,非应答(发送结束或失败),则SDA为高电平 while(SDA) // 如果为高电平,则非应答,即数据发送结束或失败,一直在循环中,一定次数后跳出循环 { b++; if(b>200) // 如果超过2000us没有应答发送失败,或者为非应答,表示接收结束 { SCL=0; Delay10us(); return 0; } } // 应答,数据发送成功/结束,返回数据 SCL=0; Delay10us(); return 1; } /******************************************************************************* * 函数名 : I2cReadByte() * 函数功能 : 使用I2c读取一个字节 * 输入 : 无 * 输出 : dat * 备注 : 接收完一个字节SCL=0,SDA=1. *******************************************************************************/ unsigned char I2cReadByte() { unsigned char a=0, dat=0; SDA=1; // 起始和发送一个字节之后SCL都是0 Delay10us(); for(a=0;a<8;a++) { SCL=1; // 高电平,数据稳定 Delay10us(); dat<<=1; // 第一次:00 移位后:01 第二次:10 dat|=SDA; // 第一次:01 移位后:10 第二次:11 Delay10us(); SCL=0; // 为低电平,数据可以改变 Delay10us(); } return dat; } /******************************************************************************* * 函数名 : void At24c02Write(unsigned char addr,unsigned char dat) * 函数功能 : 往24c02的一个地址写入一个数据 * 输入 : 无 * 输出 : 无 *******************************************************************************/ void At24c02Write(unsigned char addr, unsigned dat) { I2cStart(); // 产生起始信号 I2cSendByte(0xa0); // 发送写器件地址,高四位是固定的1010,低四位:A2,A1,A0默认为0就好,最后一位是方向位,1为读,0为写,所以是10100000,即0xa0 I2cSendByte(addr); // 发送要写入内存地址 I2cSendByte(dat); // 发送数据 I2cStop(); // 产生终止信号 } /******************************************************************************* * 函数名 : unsigned char At24c02Read(unsigned char addr) * 函数功能 : 读取24c02的一个地址的一个数据 * 输入 : 无 * 输出 : 无 *******************************************************************************/ unsigned char At24c02Read(unsigned char addr) { unsigned char num; // 读取到的数据 I2cStart(); // 产生起始信号 I2cSendByte(0xa0); // 发送写器件地址 I2cSendByte(addr); // 发送要读取的地址 I2cStart(); // 由主机向从机写数据,到从机向主机读数据,数据传输方向改变,需要重新发送起始信号 I2cSendByte(0xa1); // 发送读器件地址,写器件地址+1,高四位固定为1010,A2,A1,A0保持默认值0,最后一位,1为读,0为写,所以是10100001,即0xa1 num=I2cReadByte(); // 读取数据 I2cStop(); // 产生终止信号 return num; }
main.c:
/************************************************************************************** * EEPROM-IIC实验 * 实现现象:下载程序后数码管后4位显示0,按K1保存显示的数据,按K2读取上次保存的数据, 按K3显示数据加一,按K4显示数据清零。最大能写入的数据是255. 注意事项:由于P3.2口跟红外线共用,所以做按键实验时为了不让红外线影响实验效果,最好把红外线先取下来。 ***************************************************************************************/ #include <reg52.h> #include <i2c.h> // 引入自定义的I2C的库文件 #define u16 unsigned int #define u8 unsigned char // 74LS138译码器的管脚(用于控制动态数码管的显示,本例只使用右边的四个数码管) sbit LSA=P2^2; sbit LSB=P2^3; sbit LSC=P2^4; // 独立按键的端口,k1-k4分别控制:保存、显示、加1、清零 sbit k1=P3^1; sbit k2=P3^0; sbit k3=P3^2; sbit k4=P3^3; // 数码管显示0-9 u8 code smgduan[10]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; char num=0; // 保存数据的临时变量 u8 disp[4]; // 将4位数码管的数字保存到一个数组中 /******************************************************************************* * 函 数 名 : delay * 函数功能 : 延时函数,i=1时,大约延时10us *******************************************************************************/ void delay(u16 i) { while(i--); } /******************************************************************************* * 函数名 :Keypros() * 函数功能 :按键处理函数 * 输入 : 无 * 输出 : 无 *******************************************************************************/ void Keypros() { if(k1==0) // 按下k1按键 { delay(1000); // 消抖处理 if(k1==0) { At24c02Write(1, num); // 在地址1内写入数据num } while(!k1); } if(k2==0) { delay(1000); // 消抖处理 if(k2==0) { num=At24c02Read(1); // 读取EEPROM地址1内的数据保存在num中 } while(!k2); } if(k3==0) { delay(100); //消抖处理 if(k3==0) { num++; //数据加1 if(num>255)num=0; } while(!k3); } if(k4==0) { delay(1000); //消抖处理 if(k4==0) { num=0; //数据清零 } while(!k4); } } /******************************************************************************* * 函数名 :datapros() * 函数功能 :数据处理函数 * 输入 : 无 * 输出 : 无 *******************************************************************************/ void datapros() { disp[0]=smgduan[num/1000];//千位 disp[1]=smgduan[num%1000/100];//百位 disp[2]=smgduan[num%1000%100/10];//个位 disp[3]=smgduan[num%1000%100%10]; } /******************************************************************************* * 函数名 :DigDisplay() * 函数功能 :数码管显示函数 * 输入 : 无 * 输出 : 无 *******************************************************************************/ void DigDisplay() { u8 i; for(i=0;i<4;i++) { switch(i) //位选,选择点亮的数码管, { case(0): LSA=0;LSB=0;LSC=0; break;//显示第0位 case(1): LSA=1;LSB=0;LSC=0; break;//显示第1位 case(2): LSA=0;LSB=1;LSC=0; break;//显示第2位 case(3): LSA=1;LSB=1;LSC=0; break;//显示第3位 } P0=disp[3-i];//发送数据 delay(100); //间隔一段时间扫描 P0=0x00;//消隐 } } /******************************************************************************* * 函 数 名 : main * 函数功能 : 主函数 * 输 入 : 无 * 输 出 : 无 *******************************************************************************/ void main() { while(1) { Keypros(); //按键处理函数 datapros(); //数据处理函数 DigDisplay();//数码管显示函数 } }