51单片机学习-AT24C02数据存储&秒表(定时器扫描按键数码管)

51单片机学习-AT24C02数据存储&秒表(定时器扫描按键数码管)_第1张图片
首先编写I2C模块,根据下面的原理图进行位声明:
51单片机学习-AT24C02数据存储&秒表(定时器扫描按键数码管)_第2张图片

sbit I2C_SCL = P2^1;
sbit I2C_SDA = P2^0;


再根据下面的时序结构图编写函数:
51单片机学习-AT24C02数据存储&秒表(定时器扫描按键数码管)_第3张图片

/**
  * @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;
}


51单片机学习-AT24C02数据存储&秒表(定时器扫描按键数码管)_第4张图片

/**
  * @brief I2C发送一个字节
  * @param 要发送的字节
  * @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;
	}
}


51单片机学习-AT24C02数据存储&秒表(定时器扫描按键数码管)_第5张图片

/**
  * @brief I2C接受一个字节
  * @param 无
  * @retval 接收到的字节
  */
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;
}


51单片机学习-AT24C02数据存储&秒表(定时器扫描按键数码管)_第6张图片

/**
  * @brief I2C发送应答
  * @param 应答位,0为应答,1为非应答
  * @retval 无
  */
void I2C_SendAck(unsigned char AckBit)
{
	I2C_SDA = AckBit;
	I2C_SCL = 1;
	I2C_SCL = 0;
}


/**
  * @brief I2C接收应答
  * @param 无
  * @retval 接收到的应答位,0为应答,1为非应答
  */
unsigned char I2C_ReceiveAck(void)
{
	unsigned char AckBit;
	I2C_SDA = 1;
	I2C_SCL = 1;
	AckBit = I2C_SDA;
	I2C_SCL = 0;
	
	return AckBit;
}


接下来利用写好的I2C模块,创建AT24C02模块来凑成数据帧:

51单片机学习-AT24C02数据存储&秒表(定时器扫描按键数码管)_第7张图片
同样根据图中的时序模拟就可以了:

#include 
#include "I2C.h"

#define AT24C02_ADDRESS  0xA0 //写地址  

/**
  * @brief AT24C02写入一个字节
  * @param 要写入字节的地址,0~255
  * @param 要写入的数据
  * @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 要读出字节的地址 0~255
  * @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;
}


在main中调用看一下效果如何(这种存储,写入之后是掉电不丢失的):

#include 
#include "Delay.h"
#include "Key.h"
#include "LCD1602.h"
#include "AT24C02.h"

unsigned char Data;

void main()
{
	LCD_Init();
	LCD_ShowString(1, 1, "Hello");
	AT24C02_WriteByte(1, 66);
	Delay(5); //写完不要马上去读,写需要时间
	Data = AT24C02_ReadByte(1);
	LCD_ShowNum(2, 1, Data, 3);
	while(1)
	{
		
	}
}

51单片机学习-AT24C02数据存储&秒表(定时器扫描按键数码管)_第8张图片



接下来配合上按键就可以做出一个按键存储器啦,有了之前编写的模块,只要适当地调用就可以了,以下是按键存储器的main.c文件代码:
按键1:增大数据
按键2:减小数据
按键3:写入
按键4:读出

#include 
#include "Delay.h"
#include "Key.h"
#include "LCD1602.h"
#include "AT24C02.h"

unsigned char KeyNum;
unsigned int Num;

void main()
{
	LCD_Init();
	LCD_ShowNum(1, 1, Num, 5);

	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); //Num十六位,先取低八位
			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); //高八位左移8位或上去
			
			LCD_ShowNum(1, 1, Num, 5);
			LCD_ShowString(2, 1, "Read OK");
			Delay(1000);
			LCD_ShowString(2, 1, "       ");
		}
	}
}

接下来,利用定时器扫描按键和数码管,制作一个秒表:
由于按键和数码管都需要中断函数,所以就为Key和Nixie都定义一个中断的时候需要执行的操作,然后在中断函数中调用它们即可。。

先来看重新编写的Key模块,只要在中断函数中每隔20ms执行一次Key_Loop()就能保证读到每一次按键松开时的键码值,这样也达到了定时器扫描按键的目的

#include 
#include "Delay.h"

unsigned char Key_KeyNumber;

/**
  * @brief 获取独立按键键码
  * @param 无
  * @retval 按下的按键的键码,范围0~4,无按键按下时返回0
  */
unsigned char Key(void)
{
	unsigned char Temp;
	Temp = Key_KeyNumber;
	Key_KeyNumber = 0;
	
	return Temp;
}

/**
  * @brief 获取按键实时状态,0为松开
  * @param 无
  * @retval 无
  */
unsigned char Key_GetState()
{
	unsigned char KeyNumber = 0;
	
	if(P3_1 == 0){KeyNumber = 1;}
	if(P3_0 == 0){KeyNumber = 2;}
	if(P3_2 == 0){KeyNumber = 3;}
	if(P3_3 == 0){KeyNumber = 4;}
	
	return KeyNumber;
}



void Key_Loop(void) //捕捉按键松开瞬间,并得到键码
{
	static unsigned char NowState, LastState;
	LastState = NowState;
	NowState = Key_GetState();
	if(LastState == 1 && NowState == 0) //此时按键从1松开
	{
		Key_KeyNumber = 1;
	}
	if(LastState == 2 && NowState == 0) //此时按键从2松开
	{
		Key_KeyNumber = 2;
	}
	if(LastState == 3 && NowState == 0) //此时按键从3松开
	{
		Key_KeyNumber = 3;
	}
	if(LastState == 4 && NowState == 0) //此时按键从4松开
	{
		Key_KeyNumber = 4;
	}
}


接下来要改写Nixie,使定时器扫描数码管,每隔2ms调用一次Nixie_Loop();

#include 
#include "Delay.h"

unsigned char NixieTable[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x00};

unsigned char Nixie_Buf[9] = {0,10,10,10,10,10,10,10,10};




void Nixie_SetBuf(unsigned char Location, Number)
{
	Nixie_Buf[Location] = Number;
}


/**
  * @brief 在对应位置显示对应数字
  * @param 位置,数字
  * @retval 无
  */
void Nixie_Scan(unsigned char Location, Number)
{
	P0 = 0x00; //清零消影
	switch(Location)
	{
		case 1: P2_4 = 1; P2_3 = 1; P2_2 = 1; break;
		case 2: P2_4 = 1; P2_3 = 1; P2_2 = 0; break;
		case 3: P2_4 = 1; P2_3 = 0; P2_2 = 1; break;
		case 4: P2_4 = 1; P2_3 = 0; P2_2 = 0; break;
		case 5: P2_4 = 0; P2_3 = 1; P2_2 = 1; break;
		case 6: P2_4 = 0; P2_3 = 1; P2_2 = 0; break;
		case 7: P2_4 = 0; P2_3 = 0; P2_2 = 1; break;
		case 8: P2_4 = 0; P2_3 = 0; P2_2 = 0; break;
	}
	
	P0 = NixieTable[Number];
}


void Nixie_Loop(void) //每调用一次就向后显示一位
{
	static unsigned char i = 1;
	
	Nixie_Scan(i, Nixie_Buf[i]);
	i ++;
	if(i >= 9) i = 1;
}


因此,要同时维护这两个中断,中断函数需要这样写:

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count1, T0Count2;
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count1 ++;
	if(T0Count1 >= 20) //20ms扫描一次
	{
		T0Count1 = 0;
		
    Key_Loop();
	}
	
	T0Count2 ++; 
	if(T0Count2 >= 2) //2ms扫描一次
	{
		T0Count2 = 0;
		Nixie_Loop();
	}
}

有了以上这些模块,就可以开始编写秒表了:

#include 
#include "Timer0.h"
#include "Key.h"
#include "Nixie.h"
#include "Delay.h"

unsigned char KeyNum;
unsigned char Min, Sec, MiniSec;
unsigned char RunFlag;

void main()
{
	Timer0_Init();
	
	while(1)
	{
		KeyNum = Key();
		if(KeyNum == 1) //启动/暂停
		{
			RunFlag = !RunFlag;
		}
		if(KeyNum == 2) //清零复位
		{
			Min = 0;
			Sec = 0;
			MiniSec = 0;
		}
		
		
    	Nixie_SetBuf(1, Min / 10);
		Nixie_SetBuf(2, Min % 10);
		Nixie_SetBuf(3, 11);
		Nixie_SetBuf(4, Sec / 10);
		Nixie_SetBuf(5, Sec % 10);
		Nixie_SetBuf(6, 11);
		Nixie_SetBuf(7, MiniSec / 10);
		Nixie_SetBuf(8, MiniSec % 10);
	}
}

void Sec_Loop(void) //每过一个MiniSec执行一次
{
	if(RunFlag == 0) return;
	//暂停中不执行
	MiniSec ++;
	if(MiniSec >= 100)
	{
		MiniSec = 0;
		Sec ++;
		if(Sec >= 60)
		{
			Sec = 0;
			Min ++;
			if(Min >= 60)
			{
				Min = 0;
			}
		}
	}
}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count1, T0Count2, T0Count3;
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count1 ++;
	if(T0Count1 >= 20) //20ms扫描一次
	{
		T0Count1 = 0;
		
    Key_Loop();
	}
	
	T0Count2 ++; 
	if(T0Count2 >= 2) //2ms扫描一次
	{
		T0Count2 = 0;
		Nixie_Loop();
	}
	
	T0Count3 ++;
	if(T0Count3 >= 10) //一个MiniSec扫描一次
	{
		T0Count3 = 0;
		Sec_Loop();
	}
}

如果要和之前的AT24C02结合起来,那就KeyNum为3的时候写入秒表的时间,KeyNum为4的时候读出时间,利用WriteByte和ReadByte即可
51单片机学习-AT24C02数据存储&秒表(定时器扫描按键数码管)_第9张图片
51单片机学习-AT24C02数据存储&秒表(定时器扫描按键数码管)_第10张图片

你可能感兴趣的:(51单片机,51单片机,学习,嵌入式硬件,单片机)