仿真电路如下:
代码如下:
i2c.h
#ifndef _I2C_H //写头文件的固定格式
#define _I2C_H //写头文件的固定格式
#include <reg52.h>
sbit SCL=P1^2; //E2PROM24C02的引脚定义
sbit SDA=P1^3; //E2PROM24C02的引脚定义
sbit WP=P1^4; //读写保护
unsigned char I2CSendByte(unsigned char dat);
unsigned char I2CReadByte();
void I2CStop();
void I2CStart();
void At24c02Write(unsigned char addr,unsigned char dat); //在头文件中申明,在主函数中便可调用
unsigned char At24c02Read(unsigned char addr);
#endif //写头文件的固定格式
i2c.c
#include "i2c.h"
void delay5us(void) //误差 0us
{
unsigned char a;
for(a=1;a>0;a--);
}
void I2CStart(){ //I2C开始信号,严格按照时序图
SDA=1;
SCL=1;
delay5us();
SDA=0;
delay5us();
SCL=0;
delay5us();
}
void I2CStop(){ //停止信号
SDA=0;
SCL=1;
delay5us();
SDA=1;
delay5us(); //后面的可以不管了,因为已经能起到stop的作用
}
unsigned char I2CSendByte(unsigned char dat){ //写一个字节函数
unsigned char a=8,b;
for(a=8;a>0;a--){
SDA=dat>>7;
dat=dat<<1;
delay5us();
SCL=1; //时钟线为低时数据才能传送,时钟线为高电平的时候数据是要求保持不变的
delay5us(); //检测SDA是否为0,及是否应答
SCL=0;
delay5us();
}
SDA=1; //释放时钟线和数据线,等待应答
delay5us();
SCL=1;
while(SDA){ //检测是否应答,若应答SDA=0跳出循环,若SDA=1则非应答
b++; //SDA=1,设定一个时间跳出循环,返回0
if(b>20){
SCL=0;
delay5us();
return 0;
}
}
SCL=0;
delay5us();
return 1; //发送成功返回1
}
unsigned char I2CReadByte(){ //读一个字节数据
unsigned char a=8,dat=0;
SDA=1; //拉高准备读
delay5us();
for(a=8;a>0;a--){
SCL=1; //SCL时数据稳定,读取时要求数据稳定
delay5us();
dat<<=1; //在这里读和写的顺序不一样,读:移位———>读数;写:写数-->移位
dat|=SDA; //为什么这里读数据可以直接移位然后相与呢?移位时钟线与数据线不是同一根线
delay5us();
SCL=0; //SCL为零时,SDA上的数据可以改变
delay5us();
}
return dat;
}
/* I2CSendByte、I2CReadByte这两个函数是用来确定8位字节数据是在什么条件下能读写出来的*/
/*可以吧I2CSendByte、I2CReadByte这两个函数理解为小环境*/
/*有了满足了小环境的读写(一个字节8位数的连续传输),就要创造大环境(即这个整体器件是在什么条件下怎么传送数据的*/
/*或者可以理解为先根据时序图写出满足I2C的读写数据方式,在写出满足器件读写的数据方式*/
void At24c02Write(unsigned char addr,unsigned char dat){ //先写地址,确定数据存放的位置;再写数据 (根据器件的写数据的流程来编写这个函数)
I2CStart();
I2CSendByte(0xa0); //确定器件地址,及哪个器件
I2CSendByte(addr); //写器件内首地址,确定存放数据的首地址位置
I2CSendByte(dat); //确定玩地址后,就发送数据
//关于应答部分已经写在发送函数中,相当于一个整体
I2CStop(); //发送玩了,一个停止信号
}
unsigned char At24c02Read(unsigned char addr){ //读数据需要设一个返回值,靠这个返回值得到数据更容易编写
unsigned char num;
I2CStart();
I2CSendByte(0xa0); //读的时候先伪写入
I2CSendByte(addr); //伪写入包括两部分:确定器件地址和确定器件内首地址
I2CStart(); //在传送过程中,需要改变传送数据的方向时,起始信号和从机地址都会被重复产生一个,但两次读写方向正好相反
I2CSendByte(0xa1); //这两部很重要
num=I2CReadByte(); //获取读的数据
I2CStop();
return num;
}
main.c
#include <reg52.h>
#include "i2c.h"
typedef unsigned int u16;
typedef unsigned char u8;
/*控制读写过程*/
sbit K1=P3^1; //保存显示数据,即写入数据至E2Prom保存
sbit K2=P3^0; //读取保存的数据
sbit K3=P3^2; //累加
sbit K4=P3^3; //清零
sbit LSA=P2^2; //三八译码器的引脚设置
sbit LSB=P2^3;
sbit LSC=P2^4;
u8 code segment[10]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
u8 num=0,disp[4]; //disp也可以写成一个缓冲池 :disp[4]={1,1,2,3};
void delay(u16 i){
while(i--);
}
void Keypros(){ //按键处理函数
if(K1==0){
delay(100); //消抖
if(K1==0){
WP=0; /*关闭保护*/
At24c02Write(2,num); //写入存放的首地址和要存放的数据(AT24C02有256个地址,2是其中的一个,num可以是自己设的一个数值)
delay(100) ;/*等待读完成*/
WP=1; /*打开保护*/
}
while(!K1); //这个很重要,判断开关是否断开
}
if(K2==0){
delay(100); //消抖
if(K2==0){
num=At24c02Read(2); //之前写入的地址为2,因此读2 ,num最后等于读取的返回值
}
while(!K2);
}
if(K3==0){
delay(100); //消抖
if(K3==0){
num++; // num累计
if(num>255){
num=0;
}
}
while(!K3);
}
if(K4==0){
delay(100); //消抖
if(K4==0){
num=0;
}
while(!K4);
}
}
void datapros(){ //数据处理函数,将存储器里的数转换为数码管理的数
disp[0]=segment[num/1000]; //最高位
disp[1]=segment[num%1000/100];
disp[2]=segment[num%1000%100/10];
disp[3]=segment[num%1000%100%10];
}
void Dispiay(){ //显示函数
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]; //根据数据处理函数得到要显示的数,在循环的条件下,将一个数利用4个数码管显示了出来
delay(100);
P0=0x00; //消隐
}
}
void main(){ // 进入主函数调用
while(1){
Keypros(); //先判断按键是否有操作
datapros(); //判断玩后对获取的数据要进行处理
Dispiay(); //数据处理完了就可以显示
}
}
仿真结果图:
1、按下K3,数码管显示数字自动加1,按3下显示:
2、按下K1,数码管上的显示数自动保存至24C02中(本程序中地址是2),接着按几次K3改变数码管的显示数值,然后按下K1,数码管】显示保存的数“3”.按下K4,数码管清零。
**注意:**这个项目中将I2C的主从应答部分直接编程在unsigned char I2CSendByte(unsigned char dat)函数中,没有单独的验证主从应答的子函数。
小编水平有限,但希望对大家有帮助!!!