仿真电路如下:
代码如下:
i2c.h
#ifndef _I2C_H //写头文件的固定格式
#define _I2C_H //写头文件的固定格式
#include 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 #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(); //数据处理完了就可以显示 } } 仿真结果图: 2、按下K1,数码管上的显示数自动保存至24C02中(本程序中地址是2),接着按几次K3改变数码管的显示数值,然后按下K1,数码管】显示保存的数“3”.按下K4,数码管清零。 **注意:**这个项目中将I2C的主从应答部分直接编程在unsigned char I2CSendByte(unsigned char dat)函数中,没有单独的验证主从应答的子函数。
1、按下K3,数码管显示数字自动加1,按3下显示:
上一篇:51单片机实验2——I2C通信——24C01扩展实验
下一篇:51单片机系列——单总线通信方式——DS18B20温度检测的设计
推荐阅读最新更新时间:2024-11-09 12:22