(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 int

bit 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,3
	0x66,0x6d,0x7d,0x07, //4,5,6,7
	0x7f,0x6f,0x77,0x7c, //8,9,10,11
	0x39,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; //初始化,段选置0
	wela=0; //初始化,片选置0

	led1=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;//启动定时器1

	sec=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; //定义的临时变量k
	SCL=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); //写入芯片地址,最末位表示方向:0
	respond();

	start();
	write_byte(0xa1); //读取芯片地址,最末位表示方向:1
	respond();
	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写一次AT24C02
		if(sec==100) sec=0; //定时100s再从零开始计时
	}
}

 一个尝试:

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

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

#include<reg52.h>
#define uchar unsigned char
#define uint unsigned int

bit 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,3
	0x66,0x6d,0x7d,0x07, //4,5,6,7
	0x7f,0x6f,0x77,0x7c, //8,9,10,11
	0x39,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; //初始化,段选置0
	wela=0; //初始化,片选置0

	led1=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; //定义的临时变量k
	SCL=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 int

bit 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,3
	0x66,0x6d,0x7d,0x07, //4,5,6,7
	0x7f,0x6f,0x77,0x7c, //8,9,10,11
	0x39,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; //初始化,段选置0
	wela=0; //初始化,片选置0

	led1=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;//启动定时器1

	num=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; //定义的临时变量k
	SCL=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);   // 写入芯片地址	最末位表示方向:0
    respond();
    write_byte(address + 1);  // 写入地址的下一个位置
    respond();
    start();
    write_byte(0xA1);   // 读取芯片地址	最末位表示方向:1
    respond();
    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博客

 

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-14 05:28:04       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-14 05:28:04       106 阅读
  3. 在Django里面运行非项目文件

    2024-04-14 05:28:04       87 阅读
  4. Python语言-面向对象

    2024-04-14 05:28:04       96 阅读

热门阅读

  1. 设计模式|装饰器模式(Decorator Pattern)

    2024-04-14 05:28:04       43 阅读
  2. 如何使用GitLab构建Docker镜像并托管Docker镜像仓库

    2024-04-14 05:28:04       40 阅读
  3. 记录一个腾讯云上kafka不能正常启动问题

    2024-04-14 05:28:04       44 阅读
  4. Ubuntu 点击图标窗口最小化

    2024-04-14 05:28:04       47 阅读
  5. 栈的实现以及使用实例 python

    2024-04-14 05:28:04       200 阅读
  6. C语言经典例题(27)

    2024-04-14 05:28:04       45 阅读
  7. 我国首单国产内燃机车保税租赁出口非洲

    2024-04-14 05:28:04       42 阅读
  8. 大湾区新能源产品受非洲市场青睐

    2024-04-14 05:28:04       46 阅读
  9. Synchronized的锁升级过程

    2024-04-14 05:28:04       49 阅读
  10. Mac下安装NVM,NVM安装Node(附带NPM)

    2024-04-14 05:28:04       47 阅读
  11. Redis实现分布式锁

    2024-04-14 05:28:04       140 阅读
  12. C++手搓单链表

    2024-04-14 05:28:04       38 阅读