STC89C52单片机学习——第28节: [12-2] AT24C02数据存储&秒表(定时器扫描按键数码管)

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做!

本文写于:2025.03.20

前言

   本次笔记是用来记录我的学习过程,同时把我需要的困难和思考记下来,有助于我的学习,同时也作为一种习惯,可以督促我学习,是一个激励自己的过程,让我们开始51单片机的学习之路。
   欢迎大家给我提意见,能给我的嵌入式之旅提供方向和路线,现在作为小白,我就先学习51单片机了,就跟着B站上的江协科技开始学习了.
   在这里会记录下江协科技51单片机开发板的配套视频教程所作的实验和学习笔记内容,因为我之前有一个开发板,我大概率会用我的板子模仿着来做.让我们一起加油!
   另外为了增强我的学习效果:每次笔记把我不知道或者问题在后面提出来,再下一篇开头作为解答!

开发板说明

   本人采用的是慧净的开发板,因为这个板子是我N年前就买的板子,索性就拿来用了。不再另外购买视频中的普中开发板了。
   原理图如下
在这里插入图片描述
视频中的都用这个开发板来实现,如果有资源就利用起来。
仔细看了看:开发板的晶振为:11.0592Mhz;12Mhz晶振是用来给CH340G芯片外置晶振;

下图是实物图
在这里插入图片描述

引用

51单片机入门教程-2020版 程序全程纯手打 从零开始入门
还参考了下图中的书籍:
手把手教你学51单片机(C语言版)
在这里插入图片描述
STC89C52手册
在这里插入图片描述

解答和科普

一、编写AT24C02的读写函数

1.I2C的拼图
在main函数中调出来,AT.C就不用调出来I2C了。
在这里插入图片描述
在这里插入图片描述

位声明:

sbit I2C_SCL =P1^7;
sbit I2C_SDA=P1^2;

1.1起始拼图
在这里插入图片描述

void I2C_Start(void)
{
	 I2C_SDA=1;
	 I2C_SCL=1;

	I2C_SDA=0;
	I2C_SCL=0;
	
}

1.2终止拼图
在这里插入图片描述

void I2C_Stop(void)
{
	I2C_SDA=0;
	I2C_SCL=1;
	I2C_SDA=1;
}

1.3发送一个字节
在这里插入图片描述

void I2C_SendByte(unsigned char Byte)	
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
	I2C_SDA=Byte&(0x80>>i);		//把数据放在线上
	I2C_SCL=1;
	I2C_SCL=0;			//看时序是否满足能够承受的时间是多少
	}
}

1.4接受一个字节

在这里插入图片描述

unsigned char I2C_ReceiveByte(void)	
{
	unsigned char i,Byte=0x00;
	I2C_SDA=1;					//释放总线
	for(i=0;i<8;i++)
	{
	I2C_SCL=1;
	if(I2C_SDA){Byte|=(0x80>>i);}
	I2C_SCL=0;
	
	}
	return Byte;
}

1.5主机的发送应答
在这里插入图片描述
这里相当于主机写:

void I2C_SendAck(unsigned char AckBit)
{
	I2C_SDA=AckBit;
	I2C_SCL=1;
	I2C_SCL=0;
}

1.6 从机的接收应答
这里是从机写,反过来就是主机读
在这里插入图片描述

unsigned char I2C_ReceiveAck(void)
{
	unsigned char AckBit=0;
	I2C_SDA=1;
	I2C_SCL=1;
	AckBit=I2C_SDA;			//高电平读取
	I2C_SCL=0;
	return AckBit;
}

2.AT24C02 的数据帧

先宏定义一个地址:W

#define AT24C02_ADDRESS   0xA0

地址:R

AT24C02_ADDRESS|0x01

2.1字节写
在这里插入图片描述

#define AT24C02_ADDRESS   0xA0

/**
  * @brief    AT24C02向指定地址写入一个字节数据
  * @param    WordAddress:字地址 范围0-255,Data要写入的数据
  * @retval   无
  */
void AT24C02_WriteByte(unsigned char WordAddress,Data)
{
	
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS);
	I2C_ReceiveAck();	//先不处理
	I2C_SendByte(WordAddress);
	I2C_ReceiveAck();
	I2C_SendByte(Data);
	I2C_ReceiveAck();
	I2C_Stop();
}

2.2 随机读
在这里插入图片描述

/**
  * @brief    AT24C02从指定地址读出一个字节数据
  * @param   WordAddress:要读出的字地址和写入的地址一样
  * @retval 	读出的字节数据
  */

unsigned char  AT24C02_ReadByte(unsigned char WordAddress)
{
	unsigned char Data;
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS);
	I2C_ReceiveAck();
	I2C_SendByte(WordAddress);
	I2C_ReceiveAck();
	
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS|0x01);
	I2C_ReceiveAck();
	Data=I2C_ReceiveByte();
	I2C_SendAck(1);
	I2C_Stop();
	
	return Data;
}

3、写入数据测试
在这里插入图片描述

#include <REGX52.H>
#include "Key.h"
#include "LCD1602.h"
#include "AT24C02.h"
#include "Delay.H"

unsigned char Data;
void main(void)
{
	LCD_Init();
	LCD_ShowString(1,1,"HELLO");
	AT24C02_WriteByte(1,66);
	Delay(5);
	Data=AT24C02_ReadByte(1);
	LCD_ShowNum(2,1,Data,2);
	while(1)
	{

	}
}

测试现象
在这里插入图片描述
先写后读,掉电不丢失。在这里插入图片描述
AT24C02按键控制

#include <REGX52.H>
#include "Key.h"
#include "LCD1602.h"
#include "AT24C02.h"
#include "Delay.H"
#include "Init.h"

unsigned char KeyNum;
unsigned int Num;
void main(void)
{
	
	LCD_Init();
	LCD_ShowNum(1,1,Num,5);
	Nixietube_OFF();
	DianZhengGuan();
	while(1)
	{
		KeyNum=Key();
		if(KeyNum==1)
		{
			Num++;
			LCD_ShowNum(1,1,Num,5);
			
		}
		if(KeyNum==2)
		{
			Num--;
			LCD_ShowNum(1,1,Num,5);
			
		}
		if(KeyNum==3)
		{
			AT24C02_WriteByte(0,Num%256);
			Delay(5);
			AT24C02_WriteByte(1,Num/256);
			Delay(5);
			LCD_ShowString(2,1,"Write  OK");
			Delay(1000);
			LCD_ShowString(2,1,"         ");
			
		}
		if(KeyNum==4)
		{
			Num=AT24C02_ReadByte(0);
			Num|=AT24C02_ReadByte(1)<<8;		
			LCD_ShowNum(1,1,Num,5);
			
			LCD_ShowString(2,1,"Read   OK");
			Delay(1000);
			LCD_ShowString(2,1,"         ");
			
		}
	
	}


}

实现现象

AT24C02存储数据(按键控制)

二、定时器扫描

之前扫描按钮和数码管都是需要通过CPU主循环进行的,使用这种方式有着很大的弊端,(1)首先是会占用CPU的资源,在扫描按钮和数码管时会浪费一定的时间,(2)其次是我们的按钮检测是通过松手检测进行的,当我们按下按钮还没有松开时,程序即会进入长时间的while循环中,无法完成其他的操作,必须要松手后才能释放CPU资源完成其他的功能。因此使用定时器代替CPU进行扫描和检测是非常必要的。
在这里插入图片描述
在定时器中断函数中,调用Key和Nixie函数中的函数,这样就实现了定时器用来扫描多个部分。

1、定时器扫描按键
在这里插入图片描述
用定时器扫描的好处是:不用在主函数while等待按键,而是用定时器扫描获取按键值,主函数仍然可以做事情。
1.1 获取按键键码值(第几个按键按下)

unsigned char Key_GetState()				//按键按下获取键码值
{
	unsigned char KeyNumber=0;
	
	if(P3_0==0){KeyNumber=1;}
	if(P3_1==0){KeyNumber=2;}
	if(P3_2==0){KeyNumber=3;}
	if(P3_3==0){KeyNumber=4;}
	if(P3_4==0){KeyNumber=5;}
	if(P3_5==0){KeyNumber=6;}
	if(P3_6==0){KeyNumber=7;}
	if(P3_7==0){KeyNumber=8;}
	
	return KeyNumber;
	
}

1.2 检测松手(在上一状态为按下的同时,现状态等于1,说明在上个函数值,没有按键按下,就是松手了也就是对应的按键为高电平了)

void Key_Loop(void)
{
	static unsigned char  NowState,LastState; //记录上一刻的数据
	LastState=NowState;				//未更新的NowState就是上次的状态值
	NowState= Key_GetState();		//更新现在获取按键按下的按键值	
	if(LastState==1&&NowState==0)	//上个状态不断获取1,说明按键1按下,现在状态是0,说明松手了,设置标志位	
	{
		 Key_KeyNumber=1;
	}
	if(LastState==2&&NowState==0)	//上个状态不断获取2,说明按键1按下,现在状态是0,说明松手了,设置标志位	
	{
		 Key_KeyNumber=2;
	}
	if(LastState==3&&NowState==0)	//上个状态不断获取3,说明按键1按下,现在状态是0,说明松手了,设置标志位	
	{
		 Key_KeyNumber=3;
	}
	if(LastState==4&&NowState==0)	//上个状态不断获取4,说明按键1按下,现在状态是0,说明松手了,设置标志位	
	{
		 Key_KeyNumber=4;
	}
	if(LastState==5&&NowState==0)	//上个状态不断获取5,说明按键1按下,现在状态是0,说明松手了,设置标志位	
	{
		 Key_KeyNumber=5;
	}
	if(LastState==6&&NowState==0)	//上个状态不断获取6,说明按键1按下,现在状态是0,说明松手了,设置标志位	
	{
		 Key_KeyNumber=6;
	}
	if(LastState==7&&NowState==0)	//上个状态不断获取7,说明按键1按下,现在状态是0,说明松手了,设置标志位	
	{
		 Key_KeyNumber=7;
	}
	if(LastState==8&&NowState==0)	//上个状态不断获取8,说明按键1按下,现在状态是0,说明松手了,设置标志位	
	{
		 Key_KeyNumber=8;
	}									//KeyNumber 不会清零


}

1.3 返回获取的按键值

unsigned char Key(void)		//先把这个值清零再返回来
{
	unsigned char Temp;
	Temp=Key_KeyNumber;
	Key_KeyNumber=0;
	return Temp;
}

1.4 调用函数测试
实现定时器扫描按键,不用在主函数使用了。

#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include "Nixie.h"

unsigned char KeyNum,Temp;

void main()
{
	Timer0_Init();

	while(1)
	{
		KeyNum=Key();		//这个函数不会被卡主
		if(KeyNum) Temp=KeyNum;
		Nixie(1,Temp);
		
	}

}

void Timer0_Routine()   interrupt  1	//跳转到这里,触发中断
{
	static unsigned int T0Count1;		//Tcount为计数值  static只有本函数使用
	TL0 = 0x66;					//重新设置定时初值
	TH0 = 0xFC;					//重新设置定时初值
	T0Count1++;
	if(T0Count1>=20)
	{
	T0Count1=0;
	Key_Loop();
	
	}
	
}

while(1)
	{
		KeyNum=Key();		//这个函数不会被卡主
		if(KeyNum) Temp=KeyNum;
		Nixie(1,Temp);
		
	}

这里说明是中断函数已经对按键扫描出来了,只是显示出来。主程序并没有对按键进行扫描。
2.定时器对数码管扫描
2.1直接由定时器中断调用扫描
主程序根本没有调用,定时器每2ms扫描一下数码管。

#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include "Nixie.h"
#include "Delay.h"

unsigned char KeyNum,Temp;

void main()
{
	Timer0_Init();

	while(1)
	{
		KeyNum=Key();		//这个函数不会被卡主
	}

}
void Timer0_Routine()   interrupt  1	//跳转到这里,触发中断
{
	static unsigned int T0Count1,T0Count2;		//Tcount为计数值  static只有本函数使用
	TL0 = 0x66;					//重新设置定时初值
	TH0 = 0xFC;					//重新设置定时初值
	T0Count1++;
	if(T0Count1>=20)
	{
	T0Count1=0;
	Key_Loop();
	
	}
	T0Count2++;
	if(T0Count2>=2)
	{
	T0Count2=0;
	Nixie_Loop();	
	}
}

2.2 数码管封装
Nixie_Buf[]缓冲区存放要显示的8为数据。

 unsigned char Nixie_Buf[9];
 
void Nixie_Loop(void)
{

	static unsigned char i;
	Nixie_Scan(i,Nixie_Buf[i]);
	i++;
	if(i>=9){i=1;}
	
}

2.3数码管函数

#include <REGX52.H>
#include "Delay.h"

 
sbit  	wei= P2^1;
sbit	duan=P2^0;
unsigned char NixieDuanTable[]= {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x00};
unsigned char NixieWeiTable[]={0xFF,0xFE,0xFD,0xFB,0xF7,0xEF,0xDF,0xBF,0x7F};

unsigned char Nixie_Buf[9]={0,17,17,17,17,17,17,17,17};	//存放要显示的数字数据

/**
  * @brief    设置数码管缓存区的数据,如设置第一位显示6,只需调用输入参数1,6
  * @param  	Location数码管的位置,范围1-8;Number:要显示的数字
  * @retval 	无
  */
void Nixie_SetBuf(unsigned char Location,Number)
{
	Nixie_Buf[Location]=Number;
}

/**
  * @brief    数码管扫描函数
  * @param    WeiNum:位置范围1-8;DuanNum段码:范围1-F
  * @retval   无
  */
void Nixie_Scan(unsigned char WeiNum, DuanNum)
{
	
	
	duan=1;
	P0=NixieDuanTable[DuanNum];
	duan=0;
	
	wei=1;
	P0=NixieWeiTable[WeiNum];
	wei=0;
	Delay(1);
	                                                                                                                                                                                                                                   
}

/**
  * @brief    数码管循环扫描函数
  * @param  	无
  * @retval 	无
  */
void Nixie_Loop(void)
{

	static unsigned char i=1;
	Nixie_Scan(i,Nixie_Buf[i]);
	i++;
	if(i>=9){i=1;}
	
}

2.4实验代码

#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include "Nixie.h"
#include "Delay.h"
#include "Init.h"

unsigned char KeyNum,Temp;

void main()
{
	Timer0_Init();
	DianZhengGuan();
	while(1)
	{
		KeyNum=Key();		//这个函数不会被卡主
		if(KeyNum)
		{
				Nixie_SetBuf(1,KeyNum);
				Nixie_SetBuf(2,KeyNum);
				Nixie_SetBuf(3,KeyNum);
		}
		
		
	}

}


void Timer0_Routine()   interrupt  1	//跳转到这里,触发中断
{
	static unsigned int T0Count1,T0Count2;		//Tcount为计数值  static只有本函数使用
	TL0 = 0x66;					//重新设置定时初值
	TH0 = 0xFC;					//重新设置定时初值
	T0Count1++;
	if(T0Count1>=20)
	{
	T0Count1=0;
	Key_Loop();
	
	}
	T0Count2++;
	if(T0Count2>=2)
	{
	T0Count2=0;
	Nixie_Loop();	
	}
	
}

实验现象
在这里插入图片描述
主循环再Delay,不会再影响按键和数码管的扫描。
思路就是在模块中定义一个函数,并且在主函数里面每隔一段时间调用一下,同时做到了定时器复用的功能。可以完成多个任务,在模块中的不要写耗时间的函数,尤其是Delay,因为每间隔1ms进入一次,你一旦延迟,整个定时器都会乱套。每隔1ms,延迟2ms。

三、秒表

在二的基础上,我们完成秒表的功能,同样利用定时器,定时10ms对应的MinSec+1,就是计时单位为10ms,也是定时器完成。
1、定义变量,分,秒,Min秒(100进位)

unsigned char Min,Sec,MinSec;

2、定时器扫描


	T0Count3++;
	if(T0Count3>=10)
	{
	T0Count3=0;
	Sec_Loop();	
	}

3、功能实现
3.1开始设置计数逻辑,并显示在主函数

void Sec_Loop(void)
{
	
	MinSec++;
	if(MinSec>=100)
	{
		MinSec=0;
		Sec++;
		if(Sec>=60)
		{
			Sec=0;
			Min++;
			if(Min>=60)
			{
			Min=0;
				
			}
		}
	}

}

while(1)
	{

		KeyNum=Key();		//这个函数不会被卡主

				Nixie_SetBuf(1,Min/10);
				Nixie_SetBuf(2,Min%10);
				Nixie_SetBuf(3,17);
				Nixie_SetBuf(4,Sec/10);
				Nixie_SetBuf(5,Sec%10);
				Nixie_SetBuf(6,17);
				Nixie_SetBuf(7, MinSec/10);
				Nixie_SetBuf(8,MinSec%10);
				
		
		
	}

4、设置启动还是暂停位

4.1设置启动标志位

unsigned char RunFlaght;	//控制启动暂停标志位

4.1 主函数通过按键控制启动标志位

KeyNum=Key();		//这个函数不会被卡主
		if(KeyNum==1)
		{
			RunFlaght=!RunFlaght;
		}

4.3启动暂停的功能
在编制为为1时启动,执行计数功能

oid Sec_Loop(void)
{
	if(RunFlaght)			//如果非0,就执行
	{
	MinSec++;
	if(MinSec>=100)
	{
		MinSec=0;
		Sec++;
		if(Sec>=60)
		{
			Sec=0;
			Min++;
			if(Min>=60)
			{
			Min=0;
				
			}
		}
	}
	
	}
	
	
}

4.4 清零位设置

		if(KeyNum==2)
		{
		Min=0;
		Sec=0;
		MinSec=0;
		}

5、总体代码

#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include "Nixie.h"
#include "Delay.h"
#include "Init.h"

unsigned char KeyNum;
unsigned char Min,Sec,MinSec;
unsigned char RunFlaght;	//控制启动暂停

void main()
{
	Timer0_Init();
	DianZhengGuan();
	while(1)
	{

		KeyNum=Key();		//这个函数不会被卡主
		if(KeyNum==1)
		{
			RunFlaght=!RunFlaght;
		}
		
		if(KeyNum==2)
		{
		Min=0;
		Sec=0;
		MinSec=0;
		}
				Nixie_SetBuf(1,Min/10);
				Nixie_SetBuf(2,Min%10);
				Nixie_SetBuf(3,17);
				Nixie_SetBuf(4,Sec/10);
				Nixie_SetBuf(5,Sec%10);
				Nixie_SetBuf(6,17);
				Nixie_SetBuf(7, MinSec/10);
				Nixie_SetBuf(8,MinSec%10);
				
		
		
	}

}


void Sec_Loop(void)
{
	if(RunFlaght)			//如果非0,就执行
	{
	MinSec++;
	if(MinSec>=100)
	{
		MinSec=0;
		Sec++;
		if(Sec>=60)
		{
			Sec=0;
			Min++;
			if(Min>=60)
			{
			Min=0;
				
			}
		}
	}
	
	}
	
	
}


void Timer0_Routine()   interrupt  1	//跳转到这里,触发中断
{
	static unsigned int T0Count1,T0Count2,T0Count3;		//Tcount为计数值  static只有本函数使用
	TL0 = 0x66;					//重新设置定时初值
	TH0 = 0xFC;					//重新设置定时初值
	T0Count1++;
	if(T0Count1>=20)
	{
	T0Count1=0;
	Key_Loop();
	
	}
	T0Count2++;
	if(T0Count2>=1)
	{
	T0Count2=0;
	Nixie_Loop();	
	}
	
	T0Count3++;
	if(T0Count3>=10)
	{
	T0Count3=0;
	Sec_Loop();	
		
	}

	
}


实验现象:

时钟秒表暂停清零

四、运用AT24C02

1、main`

#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include "Nixie.h"
#include "Delay.h"
#include "Init.h"
#include "AT24C02.h"



unsigned char KeyNum;
unsigned char Min,Sec,MinSec;
unsigned char RunFlaght;	//控制启动暂停

void main()
{
	Timer0_Init();

	while(1)
	{

		KeyNum=Key();		//这个函数不会被卡主
		if(KeyNum==1)
		{
			RunFlaght=!RunFlaght;
		}
		
		if(KeyNum==2)
		{
		Min=0;
		Sec=0;
		MinSec=0;
		}
		
		if(KeyNum==3)			//K3按键按下
		{
			AT24C02_WriteByte(0,Min);	//将分秒写入AT24C02
			Delay(5);
			AT24C02_WriteByte(1,Sec);
			Delay(5);
			AT24C02_WriteByte(2,MinSec);
			Delay(5);
		}
		
		
		if(KeyNum==4)
		{	
			Min=AT24C02_ReadByte(0);
			Sec=AT24C02_ReadByte(1);
			MinSec=AT24C02_ReadByte(2);
		}
		
		
				Nixie_SetBuf(1,Min/10);
				Nixie_SetBuf(2,Min%10);
				Nixie_SetBuf(3,17);
				Nixie_SetBuf(4,Sec/10);
				Nixie_SetBuf(5,Sec%10);
				Nixie_SetBuf(6,17);
				Nixie_SetBuf(7, MinSec/10);
				Nixie_SetBuf(8,MinSec%10);
				
		
		
	}

}


void Sec_Loop(void)
{
	if(RunFlaght)			//如果非0,就执行
	{
	MinSec++;
	if(MinSec>=100)
	{
		MinSec=0;
		Sec++;
		if(Sec>=60)
		{
			Sec=0;
			Min++;
			if(Min>=60)
			{
			Min=0;
				
			}
		}
	}
	
	}
	
	
}


void Timer0_Routine()   interrupt  1	//跳转到这里,触发中断
{
	static unsigned int T0Count1,T0Count2,T0Count3;		//Tcount为计数值  static只有本函数使用
	TL0 = 0x66;					//重新设置定时初值
	TH0 = 0xFC;					//重新设置定时初值
	T0Count1++;
	if(T0Count1>=20)
	{
	T0Count1=0;
	Key_Loop();
	
	}
	T0Count2++;
	if(T0Count2>=1)
	{
	T0Count2=0;
	Nixie_Loop();	
	}
	
	T0Count3++;
	if(T0Count3>=10)
	{
	T0Count3=0;
	Sec_Loop();	
		
	}

	
}

2、Delay

#include <REGX52.H>


void Delay(unsigned int xms)		//@11.0592MHz
{
	
	while(xms--)
	{
	unsigned char i, j;
		
	i = 2;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
	}
}
#ifndef		__DELAY_H
#define 	 __DELAY_H
void Delay(unsigned int xms);
#endif

3、Nixie

#include <REGX52.H>
#include "Delay.h"

 
sbit  	wei= P2^1;
sbit	duan=P2^0;
unsigned char code NixieDuanTable[]= {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x00,0x40};
unsigned char code NixieWeiTable[]={0xFF,0xFE,0xFD,0xFB,0xF7,0xEF,0xDF,0xBF,0x7F};

unsigned char Nixie_Buf[9]={0,17,17,17,17,17,17,17,17};	//存放要显示的数字数据

/**
  * @brief    设置数码管缓存区的数据,如设置第一位显示6,只需调用输入参数1,6
  * @param  	Location数码管的位置,范围1-8;Number:要显示的数字
  * @retval 	无
  */
void Nixie_SetBuf(unsigned char Location,Number)
{
	Nixie_Buf[Location]=Number;
}

/**
  * @brief    数码管扫描函数
  * @param    WeiNum:位置范围1-8;DuanNum段码:范围1-F
  * @retval   无
  */
void Nixie_Scan(unsigned char WeiNum, DuanNum)
{
	
	
	duan=1;
	P0=NixieDuanTable[DuanNum];
	duan=0;
	
	wei=1;
	P0=NixieWeiTable[WeiNum];
	wei=0;

	                                                                                                                                                                                                                                   
}

/**
  * @brief    数码管循环扫描函数
  * @param  	无
  * @retval 	无
  */
void Nixie_Loop(void)
{

	static unsigned char i=1;
	Nixie_Scan(i,Nixie_Buf[i]);
	i++;
	if(i>=9){i=1;}
	
}
#ifndef		__NIXIE_H
#define 	 __NIXIE_H

void Nixie_Scan(unsigned char WeiNum, DuanNum);
void Nixie_Loop(void);
void Nixie_SetBuf(unsigned char Location,Number);

#endif


4、Timer0

#include <REGX52.H>


void Timer0_Init(void)		//1毫秒@11.0592MHz
{
						//	TMOD不能复制,如果同时使用定时器1和0,它会覆盖淹没,
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	
	
	ET0=1;
	EA=1;
	PT0=0;
}

#ifndef		__TIMER0_H
#define 	 __TIMER0_H

void Timer0_Init(void)	;

#endif

5、Key

#include <REGX52.H>
#include "Delay.h"


unsigned char Key_KeyNumber;
unsigned char Key(void)		//先把这个值清零再返回来
{
	unsigned char Temp;
	Temp=Key_KeyNumber;
	Key_KeyNumber=0;
	return Temp;
}


unsigned char Key_GetState()				//按键按下获取键码值
{
	unsigned char KeyNumber=0;
	
	if(P3_0==0){KeyNumber=1;}
	if(P3_1==0){KeyNumber=2;}
	if(P3_2==0){KeyNumber=3;}
	if(P3_3==0){KeyNumber=4;}
	if(P3_4==0){KeyNumber=5;}
	if(P3_5==0){KeyNumber=6;}
	if(P3_6==0){KeyNumber=7;}
	if(P3_7==0){KeyNumber=8;}
	
	return KeyNumber;
	
}


void Key_Loop(void)
{
	static unsigned char  NowState,LastState;
	LastState=NowState;				//未更新的NowState就是上次的状态值
	NowState= Key_GetState();		//更新现在获取按键按下的按键值	
	if(LastState==1&&NowState==0)	//上个状态不断获取1,说明按键1按下,现在状态是0,说明松手了,设置标志位	
	{
		 Key_KeyNumber=1;
	}
	if(LastState==2&&NowState==0)	//上个状态不断获取2,说明按键1按下,现在状态是0,说明松手了,设置标志位	
	{
		 Key_KeyNumber=2;
	}
	if(LastState==3&&NowState==0)	//上个状态不断获取3,说明按键1按下,现在状态是0,说明松手了,设置标志位	
	{
		 Key_KeyNumber=3;
	}
	if(LastState==4&&NowState==0)	//上个状态不断获取4,说明按键1按下,现在状态是0,说明松手了,设置标志位	
	{
		 Key_KeyNumber=4;
	}
	if(LastState==5&&NowState==0)	//上个状态不断获取5,说明按键1按下,现在状态是0,说明松手了,设置标志位	
	{
		 Key_KeyNumber=5;
	}
	if(LastState==6&&NowState==0)	//上个状态不断获取6,说明按键1按下,现在状态是0,说明松手了,设置标志位	
	{
		 Key_KeyNumber=6;
	}
	if(LastState==7&&NowState==0)	//上个状态不断获取7,说明按键1按下,现在状态是0,说明松手了,设置标志位	
	{
		 Key_KeyNumber=7;
	}
	if(LastState==8&&NowState==0)	//上个状态不断获取8,说明按键1按下,现在状态是0,说明松手了,设置标志位	
	{
		 Key_KeyNumber=8;
	}									//KeyNumber 不会清零


}
#ifndef		__KEY_H
#define 	__KEY_H

unsigned char Key();
void Key_Loop(void);
#endif

6、I2C

#include <REGX52.H>


sbit I2C_SCL =P1^7;
sbit I2C_SDA=P1^2;


/**
  * @brief    I2C开始
  * @param  	无
  * @retval 	无
  */
void I2C_Start(void)
{
	 I2C_SDA=1;
	 I2C_SCL=1;

	I2C_SDA=0;
	I2C_SCL=0;
	
}

/**
  * @brief    I2C停止
  * @param  	无
  * @retval 	无
  */
void I2C_Stop(void)
{
	I2C_SDA=0;		//确实为低电平
	I2C_SCL=1;
	I2C_SDA=1;
}

/**
  * @brief    I2C发送一个字节
  * @param  	Byte 要发送的字节
  * @retval 	无
  */

void I2C_SendByte(unsigned char Byte)	
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
	I2C_SDA=Byte&(0x80>>i);		//把数据放在线上
	I2C_SCL=1;
	I2C_SCL=0;			//看时序是否满足能够承受的时间是多少
	}
}

/**
  * @brief    	I2C接收一个字节
  * @param  	无
  * @retval 	接收到的一个字节
  */
unsigned char I2C_ReceiveByte(void)	
{
	unsigned char i,Byte=0x00;
	I2C_SDA=1;					//释放SDA
	for(i=0;i<8;i++)
	{
	I2C_SCL=1;
		if(I2C_SDA){Byte|=(0x80>>i);}		//置为为1,只有选中位置1,因为任何数与0相与不变,所以这个是置为选中的位,其他位置不变
	I2C_SCL=0;
	
	}
	return Byte;
}
/**
  * @brief    I2C发送应答位
  * @param    AckBit 应答位  0为应答;1为非应答
  * @retval 	无
  */
void I2C_SendAck(unsigned char AckBit)
{
	I2C_SDA=AckBit;			//主机发送应答放在SDA线上
	I2C_SCL=1;				//高电平写入
	I2C_SCL=0;				
}
/**
  * @brief    I2C接收应答位
  * @param  	无
  * @retval 	接收到的应答位
  */
unsigned char I2C_ReceiveAck(void)
{
	unsigned char AckBit=0;
	I2C_SDA=1;
	
	I2C_SCL=1;
	AckBit=I2C_SDA;			//高电平读取SDA(从机的应答位)
	I2C_SCL=0;
	return AckBit;
}

#ifndef __I2C_H__
#define __I2C_H__
void I2C_Start(void);
void I2C_Stop(void);
void I2C_SendByte(unsigned char Byte);
unsigned char I2C_ReceiveByte(void)	;
void I2C_SendAck(unsigned char AckBit);
unsigned char I2C_ReceiveAck(void);

#endif

7、AT24C02

#include <REGX52.H>
#include "I2C.h"

#define AT24C02_ADDRESS   0xA0

/**
  * @brief    AT24C02向指定地址写入一个字节数据
  * @param    WordAddress:字地址 范围0-255,Data要写入的数据
  * @retval   无
  */
void AT24C02_WriteByte(unsigned char WordAddress,Data)
{
	
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS);
	I2C_ReceiveAck();	//先不处理
	I2C_SendByte(WordAddress);
	I2C_ReceiveAck();
	I2C_SendByte(Data);
	I2C_ReceiveAck();
	I2C_Stop();
}


/**
  * @brief    	AT24C02从指定地址读出一个字节数据
  * @param   	WordAddress:要读出的字地址
  * @retval 	读出的字节数据
  */

unsigned char  AT24C02_ReadByte(unsigned char WordAddress)
{
	unsigned char Data;
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS);
	I2C_ReceiveAck();
	I2C_SendByte(WordAddress);
	I2C_ReceiveAck();
	
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS|0x01);
	I2C_ReceiveAck();
	Data=I2C_ReceiveByte();
	I2C_SendAck(1);
	I2C_Stop();
	
	return Data;
}
#ifndef __AT24C02_H__
#define __AT24C02_H__
void AT24C02_WriteByte(unsigned char WordAddress,Data);
unsigned char  AT24C02_ReadByte(unsigned char WordAddress);

#endif

0、Init

#include <REGX52.H>

sbit	dianzheng=P2^2;
sbit	wei=P2^1;

void DianZhengGuan()
{
dianzheng=0;
}
void DianZhengKai()
{
dianzheng=1;
}

//数码管位关断
void Nixie_OFF(void)
{
wei=1;
P0=0xFF;
wei=0;

}
#ifndef		__INIT_H
#define 	 __INIT_H

void DianZhengGuan();

void DianZhengKai();
void Nixie_OFF();
#endif


实验现象:

定时器扫描秒表 掉电不丢失

问题

1、今天我花了好几个小时用来解决问题,因为出错了,我急的不行,最后发现了问题是我在扫描数码管的时候没有把本身的Delay(1)删除,就相当于没1ms,延迟1ms,这种情况一定要避免,整个定时器都乱套了,我说怎么不对呀。

void Nixie_Scan(unsigned char WeiNum, DuanNum)
{
	
	
	duan=1;
	P0=NixieDuanTable[DuanNum];
	duan=0;
	
	wei=1;
	P0=NixieWeiTable[WeiNum];
	wei=0;
	Delay(1);
	                                                                                                                                                                                                                                   
}

2、为了搞定这个问题,我想着用示波器处理一下波形,看看I2C哪里有问题,结果我发现我根本不会,怎么触发都不会,整个流程都没搞懂!!
真的感觉我好笨呀,这几天全在看着这I2C.

总结

通过本次课学会了I2C的协议,然后又编写了AT24C02的数据帧格式。
1、先介绍了六个拼图的时序软件实现
2、拼起来完成整个时序的完成还有AT24C02的运用
3、学会了定时器扫描按键和数码管
4、同时完成了定时器扫描和I2C通信和AT24C02的掉点不丢失

示波器完全不会用呀,我一定得学会!

今天是我学单片机最黑暗的一天,人都晕了,最后好在解决了那个定时器中断调用的函数中还有延迟的错误

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值