(51单片机)第八章-I2C总线AT24C02芯片应用

2024-04-14 06:12

本文主要是介绍(51单片机)第八章-I2C总线AT24C02芯片应用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

8.1 I2C总线概述

1. I2C总线介绍

        I2C总线(Inter IC Bus)由PHILIPS 公司推出,是近年来微电子通信控制领域广泛采用的一种新型总线标准,它是同步通信的一种特殊形式,具有接口线少、控制简单、器件封装形式小、通信速率较高等优点。在主从通信中,可以有多个I2C总线器件同时接到I2C总线上,所有与I2C兼容的器件都具有标准的接口通过地址来识别通信对象,使它们可以经由I2C总线互相直接通信

        I2C总线由数据线 SDA 时钟线 SCL两条线构成通信线路,既可发送数据,也可接收数据。在 CPU与被控IC之间、IC与IC之间都可进行双向传送,最高传送速率为400kbps,各种被控器件均并联在总线上,但每个器件都有唯一的地址。在信息传输过程中,I2C总线上并联的每一个器件既是被控器(或主控器),又是发送器(或接收器),这取决于它所要完成的功能。CPU 发出的控制信号分为地址码数据码两部分:地址码用来选址,即接通需要控制的电路;数据码是通信的内容,这样各IC控制电路虽然挂在同一条总线上,却彼此独立。

2. I2C总线硬件结构图

        下图为I2C总线系统的硬件结构图,其中,SCL是时钟线,SDA是数据线。总线上各器件都采用漏极开路结构与总线相连,因此SCL和SDA 均需接上拉电阻总线在空闲状态下均保持高电平,连到总线上的任一器件输出的低电平,都将使总线的信号变低,即各器件的 SDA 及 SCL 都是线“”关系。

        I2C总线支持多主和主从两种工作方式,通常为主从工作方式。在主从工作方式中,系统中只有一个主器件(单片机),其他器件都是具有I2C总线的外围从器件。在主从工作方式中,主器件启动数据的发送(发出启动信号),产生时钟信号,发出停止信号。

 3. I2C总线通信格式

        下图为I2C总线上进行一次数据传输的通信格式:

4. 数据位的有效性规定

        I2C总线进行数据传输时,时钟信号为高电平期间数据线上的数据必须保持稳定只有时钟信号为低电平期间,数据信号的高电平或低电平才允许变化:

5. 发送启动(始)信号

        在利用I2C总线进行一次数据传输时,首先由主机发出启动信号,启动I2C总线,在SCL为高电平期间,SDA出现下降沿(原书写错)为启动信号,此时,具有I2C总线接口的从器件会检测到该信号,启动时序如下图所示:

 6. 发送寻址信号

        主机发送启动信号后,再发出寻址信号。器件地址有7位和10位两种,这里只介绍7位地址寻址方式。寻址字节的位定义如下图所示,寻址信号由一个字节构成,7位为地址位最低位为方向位,用以表明主机与从器件的数据传送方向方向位为0,表明主机接下来对从器件进行写操作方向位为1,表明主机接下来对从器件进行读操作

        主机发送地址时,总线上的每个从机都将这7位地址码与自己的地址进行比较,如果相同,则认为自己正被主机寻址,根据 R/W位将自己确定为发送器或接收器。

        从机的地址由固定部分可编程部分组成。在一个系统中可能希望接入多个相同的从机,从机地址中可编程部分决定了可接入总线该类器件的最大数目。如一个从机的7位寻址位有4位是固定位,3位是可编程位,这时仅能寻址8个同样的器件,即可以有8个同样的器件接入到该IC总线系统中。

  7. 应答信号

        I2C总线协议规定,每传送一个字节数据(含地址及命令字)后,都要有一个应信号,以确定数据传送是否被对方收到。应答信号由接收设备产生,在SCL信号为高电平期间,接收设备将 SDA 拉为低电平,表示数据传输正确,产生应答,时序图如下图所示。

8. 数据传输

        主机发送寻址信号并得到从器件应答后,便可以进行数据传输,每次一个字节,但每次都应在得到应答信号后再进行下一字节的传送。

9. 非应答信号

        当主机为接收设备时,主机对最后一个字节不应答,以向发送设备表示数据传送结束

10. 发送停止信号

        在全部数据传送完毕后,主机发送停止信号,记在SCL为高电平期间,SDA上产生一上升沿信号,停止时序图如下图所示:

8.2 单片机模拟I2C

        目前市场上很多单片机都已经具有硬件I2C总线控制单元,这类单片机在工作时,总线状态由硬件监测,无须用户介入,操作非常方便。但是还有许多单片机并不具有I2C总线接口,如51单片机,但可以在单片机应用系统中通过软件模拟I2C总线的工作时序,在使用时,只需正确调用各个函数就能方便地扩展I2C总线接口器件。在总线的一次数据传送过程中,可以有以下几种组合方式

        (1)主机向从机发送数据,数据传送方向在整个传送过程中不变;

        (2)主机在第一个字节后,立即从从机读数据;

        (3)在传送过程中,当需要改变传送方向时,需将起始信号和从机地址各重复产生一次,而两次读/写方向位正好相反。

        为了保证数据传送的可靠性,标准I2C总线的数据传送有严格的时序要求。I2C总线的起始信号、终止信号、应答或发送“0”、非应答或发送“1”的模拟时序如下图所示。

        单片机在模拟I2C总线通信时,需写出如下几个关键部分的程序:总线初始化、启动信应答信号、停止信号、写一个字节、读一个字节。下面分别给出具体函数的写法以供参考,在阅读代码时请参考前面相关部分的文字描述及时序图。

1. 总线初始化

2. 启动信号

3. 应答信号

4. 停止信号

5. 写一个字节

6. 读一个字节

8.3 E2PROM AT24C02与单片机的通信实例

        具有I2C总线接口的E2PROM 有多个厂家的多种类型产品。在此仅介绍ATMEL公司生产的 AT24C系列E-PROM,主要型号有AT24C01/02104/08/16等,其对应的存储容量分别头128x8/256x8/512x8/1024x8/2048x8。采用这类芯片可解决掉电数据保存问题,可对所存数据保存100年,并可多次擦写,擦写次数可达10万次以上。在一些应用系统设计中,有时需要对工作数据进行掉电保护,如电子式电能表等智能化产品。若采用普通存储器,在掉电时需要备用电池供电,并需要在硬件上增加掉电检测电路,但存在电池不可靠及扩展存储芯片占用单片机过多口线的缺点。采用具有I2C总线接口的串行 E2PROM 器件可很好地解决掉电数据保存问题,且硬件电路简单。下面以AT24C02芯片为例,介绍具有I2C总线接口的 E2PROM 的具体应用。

1. AT24C02引脚配置与引脚功能

        AT24C02 芯片的常用封装形式有直插(DIP8)式和贴片(SO-8)式两种,无论直插式还是贴片式,其引脚功能与序号都一样:

 

        各引脚功能如下:

                1,2,3(A0、A1、A2)-可编程地址输入端。

                4(GND)——电源地。

                5(SDA)——串行数据输入/输出端。

                6(SCL)——串行时钟输入端。

                7(WP)——写保护输入端,用于硬件数据保护。当其为低电平时,可以对整个存储器进行正常的读/写操作;当其为高电平时,存储器具有写保护功能,但读操作不受影响。

                8(Vcc)——电源正端。

2. 存储结构与寻址

        AT24C02的存储容量为2KB,内部分成32页,每页8B,共256B,操作时有两种寻址方式:芯片寻址片内子地址寻址

(1)芯片寻址。

        AT24C02的芯片地址为1010,其地址控制字格式为1010A2A1A0 R/w。其中 A2,A1,A0为可编程地址选择位。A2,A1,A0引脚接高、低电平后得到确定的三位编码,与1010形成7位编码,即为该器件的地址码。R/w为芯片读写控制位,该位为0,表示对芯片进行写操作;该位为1,表示对芯片进行读操作。

(2)片内子地址寻址。

        芯片寻址可对内部256B中的任一个进行读/写操作,其寻址范围为00~FF,共256个寻址单元。

 3. 读/写操作时序

        串行 E2PROM 一般有两种写入方式:一种是字节写入方式,另一种是页写入方式。页写入方式允许在一个写周期内(10ms左右)对一个字节到一页的若干字节进行编程写入,AT24C02的页面大小为8B。采用页写方式可提高写入效率,但也容易发生事故。AT24C系列片内地址在接收到每一个数据字节后自动加1,故装载一页以内数据字节时,只需输入首地址,如果写到此页的最后一个字节,主器件继续发送数据,数据将重新从该页的首地址写入,进而造成原来的数据丢失,这就是页地址空间的“上卷”现象。

        解决“上卷”的方法是:在第8个数据后将地址强制加1,或是将下一页的首地址重新赋给寄存器。

(1) 字节写入方式

        单片机在一次数据帧中只访问E2PROM一个单元。该方式下,单片机先发送启动信号,然后送一个字节的控制字,再送一个字节的存储器单元子地址,上述几个字节都得到 E2PROM 响应后,再发送8位数据,最后发送1位停止信号。发送格式如下图所示。

(2) 页写入方式

        单片机在一个数据写周期内可以连续访问1页(8个)E2PROM存储单元。在该方式中,单片机先发送启动信号,接着送一个字节的控制字,再送1个字节的存储器起始单元地址,上述几个字节都得到EPROM 应答后就可以发送最多1页的数据,并顺序存放在以指定起始地址开始的相继单元中,最后以停止信号结束。页写入帧格式如下图所示。

(3) 指定地址读操作

        读指定地址单元的数据。单片机在启动信号后先发送含有片选地址的写操作控制字,E2PROM 应答后再发送1个(2KB以内的EPROM)字节的指定单元的地址,E2PROM 应答后再发送1个含有片选地址的读操作控制字,此时如果EPROM 做出应答,被访问单元的数据就会按SCL信号同步出现在串行数据/地址线SDA上。这种读操作的数据帧格式如下图所示。

(4)指定地址连续读

        此种方式的读地址控制与前面指定地址读相同。单片机接收到每个字节数据后应做出应答,只要E2PROM 检测到应信号,其内部的地址寄存器就自动加1指向下一单元,并顺序将指向的单元的数据送到SDA串行数据线上。当需要结束读操作时单片机接收到数据后在需要应答的时刻发送一个非应答信号,接着再发送一个停止信号即可这种读操作的数据帧格式如下图所示。

4. TX-1C实验板上AT24C02连接图

        TX-1C实验板上 AT24C02与单片机连接如下图所示,其中A0、A1、A2与 WP 都接地,SDA 接单片机 P2.0脚,SCL接单片机 P2.1脚,SDA与SCL分别与Vcc之间接10kΩ上拉电阻,因为 AT24C02总线内部是漏极开路形式,不接上拉电阻无法确定总线空闲时的电平状态。

        

【例8.3.1】

        用C语言编写程序,在TX-1℃实验板上实现如下功能:利用定时器产生个0~99 秒变化的秒表,并且显示在数码管上,每过一秒将这个变化的数写入板上AT24C02内部。当关闭实验板电源,并再次打开实验板电源时,单片机先从AT24C02中将原来写入的数读取出来,接着此数继续变化并显示在数码管上。

        通过本实验可以看到,若向AT24C02中成功写入,并且成功读取则数码管上显示的数会接着关闭实验板时的数继续显示,否则有可能显示乱码。

        注:为加快显示本人的程序不是1s一变

#include<reg52.h>
#define uchar unsigned char
#define uint unsigned intbit write=0; //写AT24C02的标志sbit SDA=P2^0; //声明数据线端口
sbit SCL=P2^1; //声明时钟线端口
sbit dula=P2^6; //声明U1锁存器的锁存端
sbit wela=P2^7; //声明U2锁存器的锁存端
sbit led1=P1^0; //第一位发光二极管(0亮1灭)
sbit beep=P2^3;	//声明蜂鸣器引脚(0响1灭)uchar code dula_table[]={0x3f,0x06,0x5b,0x4f, //0,1,2,30x66,0x6d,0x7d,0x07, //4,5,6,70x7f,0x6f,0x77,0x7c, //8,9,10,110x39,0x5e,0x79,0x71  //12,13,14,15
};uchar code wela_table[]=
{0xdf,0xef,0xf7,0xfb,0xfd,0xfe	//从右向左数第一到六位
};uchar sec,tcnt,ms100,ms10;
uint wei;void main()
{void init(); // 总线初始化void init_wedu(); //数码管显示和定时器初始化操作void write_add(uchar address,uchar Data);void display();init();init_wedu();while(1){display();if(write==1){write=0;ms100=sec/10;ms10=sec%10;write_add(2,sec);}}}void delay() //微秒级延时函数,该延时函数大约延时4~5微秒,用于操作I2C总线时用
{;;
}void delayxms(uint xms)
{uint x,y;for(x=xms;x>0;x--)for(y=124;y>0;y--);	
}    void init() // 总线初始化
{void delay();//将总线都拉高以释放总线SCL=1; //时钟线拉高delay();SDA=1; //数据线拉高delay();
}void init_wedu() //数码管显示和定时器初始化操作
{uchar read_add(uchar address);dula=0; //初始化,段选置0wela=0; //初始化,片选置0led1=1;	//初始化,二极管灯灭beep=1;	//初始化,蜂鸣器不响TMOD=0x10;//设置定时器0和1的工作方式为1(0001 0001)TH1=(65536-4608)/256; //装初值TL1=(65536-4608)%256; //装初值EA=1;//打开总中断ET1=1;//打开定时器1中断TR1=1;//启动定时器1sec=read_add(2);if(sec>100) sec=0; //防止首次读取出错误数据ms100=sec/10;ms10=sec%10;display();
}void start() //启动信号
{SDA=1; //数据线拉高delay();SCL=1; //时钟线拉高delay();SDA=0; //数据线拉低,在SCL高电平期间产生下降沿启动信号delay();	
}void respond() //应答信号
{void delay();uchar i=0;SCL=1; //时钟线拉高delay();while((SDA==1)&&(i<255)) i++; //若一段时间主器件没有收到从器件的应答则默认//从器件已经收到数据不再等待应答信号//若不加这个延时退出,一旦从器件没有发送应答信号//程序将永远停留在此处SCL=0; //SCL高电平期间,SDA被从设备置低电平表示应答delay();
}void stop() //停止信号
{void delay();//SCL高电平期间,SDA一个上升沿产生停止信号SDA=0;delay();SCL=1;delay();SDA=1;delay();
}void write_byte(uchar Data) //写一个字节
{void delay();uchar i,temp;temp=Data;for(i=0;i<8;i++){temp=temp<<1; //串行发送字节是,需要将该字节的8位逐位发送//temp=temp<<1表示将temp左移一位,最高位将一如PSW寄存器的CY位中//然后将CY赋给SDA进而在SCL的控制下发送出去SCL=0;delay();SDA=CY;delay();SCL=1;delay();}SCL=0;delay();SDA=1;delay();
}uchar read_byte() //读一个字节
{void delay();uchar i,k; //定义的临时变量kSCL=0;delay();SDA=1;for(i=0;i<8;i++){//串行接收字节时需要将8位逐位接收,再组合成一个字节//k左移一位后与SDA进行“或”运算//依次把8个独立的位放入一个字节中完成接收SCL=1;delay();k=(k<<1)|SDA;SCL=0;delay();}return k;
}void write_add(uchar address,uchar Data)
{void start(); //启动信号void write_byte(uchar Data); //写一个信号void respond(); //应答信号void stop(); //停止信号start();write_byte(0xa0);respond();write_byte(address);respond();write_byte(Data);respond();stop();
}uchar read_add(uchar address)
{void start(); //启动信号void write_byte(uchar Data); //写一个信号uchar read_byte(); //读一个字节void respond(); //应答信号void stop(); //停止信号uchar Data;start();write_byte(0xa0);respond();write_byte(address); //写入芯片地址,最末位表示方向:0respond();start();write_byte(0xa1); //读取芯片地址,最末位表示方向:1respond();Data=read_byte();stop();return Data;
}void display()
{void wedu(uchar dula_num,uchar wela_num);for(wei=0;wei<2;wei++){switch(wei){case 0: wedu(ms10,wei);break;case 1: wedu(ms100,wei);break;}	}
}void wedu(uchar dula_num,uchar wela_num)
{wela=1; //打开U2锁存端P0=wela_table[wela_num]; //送入U2锁存端wela=0; //关闭U2锁存端P0=0xc0; //消影,防止P0残留电位信号干扰段选dula=1; //打开U1锁存端P0=dula_table[dula_num]; //送入段选信号dula=0; //关闭U1锁存端P0=0xff; //消影,防止P0残留电位信号干扰片选delayxms(1);
}void T1_time() interrupt 3 //中断程序
{TH1=(65536-4608)/256; //装初值TL1=(65536-4608)%256; //装初值tcnt++;if(tcnt==20){led1=~led1;beep=~beep;tcnt=0;sec++;write=1; //1s写一次AT24C02if(sec==100) sec=0; //定时100s再从零开始计时}
}

 一个尝试:

结合(51单片机)第三章-数码管显示原理及应用实现-中断_单片机复位后数码管显示机-CSDN博客

中的一段程序加以改进,但是仍然存在问题:主程序while(1)里面写入语句的用时比较长,没运行完定时中断再次触发,故每次write_add(5,s10);这段语句都无法正常执行——

#include<reg52.h>
#define uchar unsigned char
#define uint unsigned intbit write=0; //写AT24C02的标志sbit SDA=P2^0; //声明数据线端口
sbit SCL=P2^1; //声明时钟线端口
sbit dula=P2^6; //声明U1锁存器的锁存端
sbit wela=P2^7; //声明U2锁存器的锁存端
sbit led1=P1^0; //第一位发光二极管(0亮1灭)
sbit beep=P2^3;	//声明蜂鸣器引脚(0响1灭)uchar code dula_table[]={0x3f,0x06,0x5b,0x4f, //0,1,2,30x66,0x6d,0x7d,0x07, //4,5,6,70x7f,0x6f,0x77,0x7c, //8,9,10,110x39,0x5e,0x79,0x71  //12,13,14,15
};uchar code wela_table[]=
{0xdf,0xef,0xf7,0xfb,0xfd,0xfe	//从右向左数第一到六位
};uchar dula_num,wela_num,num,wei;
uchar min10,min,s10,s,ms100,ms10;void main()
{void init(); // 总线初始化void init_wedu(); //数码管显示和定时器初始化操作void write_add(uchar address,uchar Data);void display();init();init_wedu();while(1){display();if(write==1){write=0;write_add(2,ms10);write_add(3,ms100);write_add(4,s);write_add(5,s10);write_add(6,min);write_add(7,min10);}}}void delay() //微秒级延时函数,该延时函数大约延时4~5微秒,用于操作I2C总线时用
{;;
}void delayxms(uint xms)	//为显示稳定y的初值在本程序中有所调整
{uint x,y;for(x=xms;x>0;x--)for(y=62;y>0;y--);	
}    void init() // 总线初始化
{void delay();//将总线都拉高以释放总线SCL=1; //时钟线拉高delay();SDA=1; //数据线拉高delay();
}void init_wedu() //数码管显示和定时器初始化操作
{uchar read_add(uchar address);dula=0; //初始化,段选置0wela=0; //初始化,片选置0led1=1;	//初始化,二极管灯灭beep=1;	//初始化,蜂鸣器不响num=0;ms10=read_add(2);ms100=read_add(3);s=read_add(4);s10=read_add(5);min=read_add(6);min10=read_add(7);if(ms10>=10) ms10=0;if(ms100>=10) ms100=0;if(s>=10) s=0;if(s10>=6) s10=0; if(min>=10) min=0;if(min10>=6) min10=0;display();TMOD=0x10;//设置定时器0和1的工作方式为1(0001 0001)TH1=(65536-4608)/256; //装初值TL1=(65536-4608)%256; //装初值EA=1;//打开总中断ET1=1;//打开定时器1中断TR1=1;//启动定时器1
}void start() //启动信号
{SDA=1; //数据线拉高delay();SCL=1; //时钟线拉高delay();SDA=0; //数据线拉低,在SCL高电平期间产生下降沿启动信号delay();	
}void respond() //应答信号
{void delay();uchar i=0;SCL=1; //时钟线拉高delay();while((SDA==1)&&(i<255)) i++; //若一段时间主器件没有收到从器件的应答则默认//从器件已经收到数据不再等待应答信号//若不加这个延时退出,一旦从器件没有发送应答信号//程序将永远停留在此处SCL=0; //SCL高电平期间,SDA被从设备置低电平表示应答delay();
}void stop() //停止信号
{void delay();//SCL高电平期间,SDA一个上升沿产生停止信号SDA=0;delay();SCL=1;delay();SDA=1;delay();
}void write_byte(uchar Data) //写一个字节
{void delay();uchar i,temp;temp=Data;for(i=0;i<8;i++){temp=temp<<1; //串行发送字节是,需要将该字节的8位逐位发送//temp=temp<<1表示将temp左移一位,最高位将一如PSW寄存器的CY位中//然后将CY赋给SDA进而在SCL的控制下发送出去SCL=0;delay();SDA=CY;delay();SCL=1;delay();}SCL=0;delay();SDA=1;delay();
}uchar read_byte() //读一个字节
{void delay();uchar i,k; //定义的临时变量kSCL=0;delay();SDA=1;for(i=0;i<8;i++){//串行接收字节时需要将8位逐位接收,再组合成一个字节//k左移一位后与SDA进行“或”运算//依次把8个独立的位放入一个字节中完成接收SCL=1;delay();k=(k<<1)|SDA;SCL=0;delay();}return k;
}void write_add(uchar address,uchar Data)
{void start(); //启动信号void write_byte(uchar Data); //写一个信号void respond(); //应答信号void stop(); //停止信号start();write_byte(0xa0);respond();write_byte(address);respond();write_byte(Data);respond();stop();
}uchar read_add(uchar address)
{void start(); //启动信号void write_byte(uchar Data); //写一个信号uchar read_byte(); //读一个字节void respond(); //应答信号void stop(); //停止信号uchar Data;start();write_byte(0xa0);respond();write_byte(address);respond();start();write_byte(0xa1);respond();Data=read_byte();stop();return Data;
}void display()
{void wedu(uchar dula_num,uchar wela_num);for(wei=0;wei<6;wei++){switch(wei){case 0: wedu(ms10,wei);break;case 1: wedu(ms100,wei);break;case 2: wedu(s,wei);break;case 3: wedu(s10,wei);break;case 4: wedu(min,wei);break;case 5: wedu(min10,wei);break;}dula=1; //打开U1锁存端P0=0x00; //防止最高位数码管过亮dula=0; //关闭U1锁存端	}
}void wedu(uchar dula_num,uchar wela_num)
{wela=1; //打开U2锁存端P0=wela_table[wela_num]; //送入U2锁存端wela=0; //关闭U2锁存端P0=0xc0; //消影,防止P0残留电位信号干扰段选dula=1; //打开U1锁存端P0=dula_table[dula_num]; //送入段选信号dula=0; //关闭U1锁存端P0=0xff; //消影,防止P0残留电位信号干扰片选delayxms(1);
}void T1_time() interrupt 3 //中断程序
{TH1=(65536-4608)/256; //装初值TL1=(65536-4608)%256; //装初值num++;if(num==2){num=0;write=1; //写一次AT24C02	 ms10++;//write_add(2,ms10);if(ms10==10){ms10=0;ms100++;//write_add(3,ms10);if(ms100==10){ms100=0;s++;beep=0;delayxms(10);beep=1;//write_add(3,s);if(s==10){s=0;s10++;//write_add(4,s10);if(s10==6){s10=0;min++;//write_add(5,s10);if(min==10){min=0;min10++;//write_add(6,min);if(min10==6){min10=0;}//write_add(7,min10);}}}}}}
}

        和chatgpt 交流的结果(将数字分为高低两个部分存储,但没有成功实现):

#include<reg52.h>
#define uchar unsigned char
#define uint unsigned intbit write=0; //写AT24C02的标志sbit SDA=P2^0; //声明数据线端口
sbit SCL=P2^1; //声明时钟线端口
sbit dula=P2^6; //声明U1锁存器的锁存端
sbit wela=P2^7; //声明U2锁存器的锁存端
sbit led1=P1^0; //第一位发光二极管(0亮1灭)
sbit beep=P2^3;	//声明蜂鸣器引脚(0响1灭)uchar code dula_table[]={0x3f,0x06,0x5b,0x4f, //0,1,2,30x66,0x6d,0x7d,0x07, //4,5,6,70x7f,0x6f,0x77,0x7c, //8,9,10,110x39,0x5e,0x79,0x71  //12,13,14,15
};uchar code wela_table[]=
{0xdf,0xef,0xf7,0xfb,0xfd,0xfe	//从右向左数第一到六位
};uchar dula_num,wela_num,num;
uint ms10_memory,min10,min,s10,s,ms100,ms10,wei;void main()
{void init(); // 总线初始化void init_wedu(); //数码管显示和定时器初始化操作void write_add_uint(uchar address,uint Data);void display();init();init_wedu();while(1){display();if(write==1){write=0;write_add_uint(2,ms10_memory);}}}void delay() //微秒级延时函数,该延时函数大约延时4~5微秒,用于操作I2C总线时用
{;;
}void delayxms(uint xms)
{uint x,y;for(x=xms;x>0;x--)for(y=124;y>0;y--);	
}    void init() // 总线初始化
{void delay();//将总线都拉高以释放总线SCL=1; //时钟线拉高delay();SDA=1; //数据线拉高delay();
}void init_wedu() //数码管显示和定时器初始化操作
{uint read_add_uint(uchar address);dula=0; //初始化,段选置0wela=0; //初始化,片选置0led1=1;	//初始化,二极管灯灭beep=1;	//初始化,蜂鸣器不响TMOD=0x10;//设置定时器0和1的工作方式为1(0001 0001)TH1=(65536-4608)/256; //装初值TL1=(65536-4608)%256; //装初值EA=1;//打开总中断ET1=1;//打开定时器1中断TR1=1;//启动定时器1num=0;ms10_memory=read_add_uint(2);if(ms10_memory>36000) ms10_memory=0;min10=ms10_memory/6000;min=ms10_memory%6000/600;s10=ms10_memory%6000%600/100;s=ms10_memory%6000%600%100;ms100=ms10_memory%6000%600%100/10;ms10=ms10_memory%6000%600%100%10;	
}void start() //启动信号
{SDA=1; //数据线拉高delay();SCL=1; //时钟线拉高delay();SDA=0; //数据线拉低,在SCL高电平期间产生下降沿启动信号delay();	
}void respond() //应答信号
{void delay();uchar i=0;SCL=1; //时钟线拉高delay();while((SDA==1)&&(i<255)) i++; //若一段时间主器件没有收到从器件的应答则默认//从器件已经收到数据不再等待应答信号//若不加这个延时退出,一旦从器件没有发送应答信号//程序将永远停留在此处SCL=0; //SCL高电平期间,SDA被从设备置低电平表示应答delay();
}void stop() //停止信号
{void delay();//SCL高电平期间,SDA一个上升沿产生停止信号SDA=0;delay();SCL=1;delay();SDA=1;delay();
}void write_byte(uchar Data) //写一个字节
{void delay();uchar i,temp;temp=Data;for(i=0;i<8;i++){temp=temp<<1; //串行发送字节是,需要将该字节的8位逐位发送//temp=temp<<1表示将temp左移一位,最高位将一如PSW寄存器的CY位中//然后将CY赋给SDA进而在SCL的控制下发送出去SCL=0;delay();SDA=CY;delay();SCL=1;delay();}SCL=0;delay();SDA=1;delay();
}uchar read_byte() //读一个字节
{void delay();uchar i,k; //定义的临时变量kSCL=0;delay();SDA=1;for(i=0;i<8;i++){//串行接收字节时需要将8位逐位接收,再组合成一个字节//k左移一位后与SDA进行“或”运算//依次把8个独立的位放入一个字节中完成接收SCL=1;delay();k=(k<<1)|SDA;SCL=0;delay();}return k;
}void write_add_uint(uchar address, uint Data) 
{uchar data_low = (uchar)(Data & 0xFF);          // 获取低 8 位数据uchar data_high = (uchar)((Data >> 8) & 0xFF);  // 获取高 8 位数据// 写入低字节start();write_byte(0xA0);   // 写入芯片地址respond();write_byte(address);    // 写入地址respond();write_byte(data_low);   // 写入低字节数据respond();stop();// 写入高字节start();write_byte(0xA0);   // 写入芯片地址respond();write_byte(address + 1);  // 写入地址的下一个位置respond();write_byte(data_high);  // 写入高字节数据respond();stop();
}uint read_add_uint(uchar address) 
{uchar high_byte, low_byte;// 读取低字节start();write_byte(0xA0);   // 写入芯片地址respond();write_byte(address);    // 写入地址respond();start();write_byte(0xA1);   // 读取芯片地址respond();low_byte = read_byte();  // 读取低字节数据respond();stop();// 读取高字节start();write_byte(0xA0);   // 写入芯片地址	最末位表示方向:0respond();write_byte(address + 1);  // 写入地址的下一个位置respond();start();write_byte(0xA1);   // 读取芯片地址	最末位表示方向:1respond();high_byte = read_byte();  // 读取高字节数据respond();stop();// 将高低字节合并为 uint 类型的数据并返回return ((uint)high_byte << 8) | low_byte;
}void display()
{void wedu(uchar dula_num,uchar wela_num);for(wei=0;wei<6;wei++){switch(wei){case 0: wedu(ms10,wei);break;case 1: wedu(ms100,wei);break;case 2: wedu(s,wei);break;case 3: wedu(s10,wei);break;case 4: wedu(min,wei);break;case 5: wedu(min10,wei);break;}	}
}void wedu(uchar dula_num,uchar wela_num)
{wela=1; //打开U2锁存端P0=wela_table[wela_num]; //送入U2锁存端wela=0; //关闭U2锁存端P0=0xc0; //消影,防止P0残留电位信号干扰段选dula=1; //打开U1锁存端P0=dula_table[dula_num]; //送入段选信号dula=0; //关闭U1锁存端P0=0xff; //消影,防止P0残留电位信号干扰片选delayxms(1);
}void T1_time() interrupt 3 //中断程序
{TH1=(65536-4608)/256; //装初值TL1=(65536-4608)%256; //装初值num++;if(num==2){num=0;ms10_memory++;if(ms10_memory == 36000) ms10_memory=0; //时间记满后重新计时write=1; //10ms写一次AT24C02	 ms10++;if(ms10==10){ms10=0;ms100++;}if(ms100==10){ms100=0;s++;beep=0;delayxms(10);beep=1;}if(s==10){s=0;s10++;}if(s10==6){s10=0;min++;}if(min==10){min=0;min10++;}if(min10==6){min10=0;}	}
}

这个问题如果有机会再回来填坑

参考资料: 

[1] 郭天祥. 新概念51单片机C语言教程:入门、提高、开发、拓展全攻略[M]. 北京: 电子工业出版社, 2009.

[2] (51单片机)第三章-数码管显示原理及应用实现-中断_单片机复位后数码管显示机-CSDN博客

 

这篇关于(51单片机)第八章-I2C总线AT24C02芯片应用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/902278

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

zoj3820(树的直径的应用)

题意:在一颗树上找两个点,使得所有点到选择与其更近的一个点的距离的最大值最小。 思路:如果是选择一个点的话,那么点就是直径的中点。现在考虑两个点的情况,先求树的直径,再把直径最中间的边去掉,再求剩下的两个子树中直径的中点。 代码如下: #include <stdio.h>#include <string.h>#include <algorithm>#include <map>#

【区块链 + 人才服务】可信教育区块链治理系统 | FISCO BCOS应用案例

伴随着区块链技术的不断完善,其在教育信息化中的应用也在持续发展。利用区块链数据共识、不可篡改的特性, 将与教育相关的数据要素在区块链上进行存证确权,在确保数据可信的前提下,促进教育的公平、透明、开放,为教育教学质量提升赋能,实现教育数据的安全共享、高等教育体系的智慧治理。 可信教育区块链治理系统的顶层治理架构由教育部、高校、企业、学生等多方角色共同参与建设、维护,支撑教育资源共享、教学质量评估、

day-51 合并零之间的节点

思路 直接遍历链表即可,遇到val=0跳过,val非零则加在一起,最后返回即可 解题过程 返回链表可以有头结点,方便插入,返回head.next Code /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}*

AI行业应用(不定期更新)

ChatPDF 可以让你上传一个 PDF 文件,然后针对这个 PDF 进行小结和提问。你可以把各种各样你要研究的分析报告交给它,快速获取到想要知道的信息。https://www.chatpdf.com/

【区块链 + 人才服务】区块链集成开发平台 | FISCO BCOS应用案例

随着区块链技术的快速发展,越来越多的企业开始将其应用于实际业务中。然而,区块链技术的专业性使得其集成开发成为一项挑战。针对此,广东中创智慧科技有限公司基于国产开源联盟链 FISCO BCOS 推出了区块链集成开发平台。该平台基于区块链技术,提供一套全面的区块链开发工具和开发环境,支持开发者快速开发和部署区块链应用。此外,该平台还可以提供一套全面的区块链开发教程和文档,帮助开发者快速上手区块链开发。

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝