【51单片机】秒表(数码管显示)-附仿真文件

目录

一、概念

1、AT24C02

2、I2C总线

(1)I2C时序结构

 (2)I2C数据帧

4、AT24C02数据帧

 二、代码

1、main.c

2、I2C.c

3、I2C.h

4、AT24C02.c

5、AT24C02.h

6、Nixie.c

7、Nixie.h

8、Key.c

9、Key.h

10、Timer0.c

11、Timer0.h

12、Delay.c

13、Delay.h

 三、仿真


 用定时器可实现秒表计时的功能,而要实现秒表存储数据的功能需要用到AT24C02。

一、概念

1、AT24C02

AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息。

        ●存储介质:E2PROM

        ●通讯接口:I2C总线

        ●容量:256字节

2、I2C总线

(1)I2C时序结构

在了解AT24C02前,我们先了解一下其通信接口,即I2C总线的时序结构。我们可以将通过I2C协议实现主机和从机通信的过程分为六个部分:

设置起始条件:SCL高电平期间,SDA从高电平切换到低电平。

即保证SDA置1,后SCL也置1(为了时序拼图完整:均从1开始),SDA变为0,SCL后变为0。

SCL时钟频率最大为1000Hz,而单片机IO口的时钟频率为500Hz,故有足够的时间来读取数 据,不用设置延时。

设置停止条件:SCL高电平期间,SDA从低电平切换到高电平。

即保证SDA为0,SCL变为或保持1,SDA后变为1.

发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。

接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA) 。

发送应答:在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答。

接收应答:在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)。

 (2)I2C数据帧

①发送一帧数据

用杆子的例子来解释,主机先控制杆子,发出地址,然后松开杆子的控制权,然后对应的从机控制杆子,给个0拉下杆子返回给主机,表示接收到数据了(应答),然后主机再次得到控制器,开始发数据,循环往复。

②接收一帧数据

 两个合并

可以这样理解:开始上课,老师向小明提问,小明回答收到,老师问1+1=多少,小明回答收到,老师问1+2=多少,小明回答收到......问完,老师让小明回答,小明说收到,小明回答2,老师回答收到,小明回答3,老师回答收到......回答完毕老师让小明坐下,结束。 

4、AT24C02数据帧

字节写:在WORD ADDRESS处写入数据DATA随机读:读出在WORD ADDRESS处的数据DATA

AT24C02的固定地址为1010,可配置地址本开发板上为000     所以SLAVE ADDRESS+W为0xA0,SLAVE ADDRESS+R为0xA1 

 二、代码

功能按键1按下开始计时,再按下计时暂停;按键2按下,分秒清0;按键3按下,将数据写入AT24C02;按键4按下,读出AT24C02的数据,并在数码管上显示。

1、main.c

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

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

void main()
{
	Timer0_Init();
	while(1)
	{
		KeyNum = Key();
		if(KeyNum == 1) 					//K1按键按下
		{
			RunFlag =! RunFlag; 		    //启动标志位翻转
		}
		if(KeyNum == 2) 					//K2按键按下
		{
			Min = 0;						//分秒清0
			Sec = 0;
			CenSec = 0;
		}		
		if(KeyNum == 3) 					//K3按键按下
		{
			AT24C02_WriteByte(0, Min);		//将分秒写入AT24C02
			Delay(5);
			AT24C02_WriteByte(1, Sec);
			Delay(5);
			AT24C02_WriteByte(2, CenSec);
			Delay(5);
		}		
		if(KeyNum == 4) 					//K4按键按下
		{
			Min = AT24C02_ReadByte(0);		//读出AT24C02数据
			Sec = AT24C02_ReadByte(1);
			CenSec = AT24C02_ReadByte(2);
		}		
		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, CenSec / 10);
		Nixie_SetBuf(8, CenSec % 10);
	}
}

/**
  * @brief  秒表驱动函数,在中断中调用
  * @param  无
  * @retval 无
  */
void Sec_Loop(void)
{
	if(RunFlag)
	{
		CenSec++;
		if(CenSec >= 100)
		{
			CenSec = 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 = 0x18;			//设置定时初值
	TH0 = 0xFC;			//设置定时初值
	T0Count1++;
	if(T0Count1 >= 20)
	{
		T0Count1 = 0;
		Key_Loop();		//20ms调用一次按键驱动函数
	}
	T0Count2++;
	if(T0Count2 >= 2)
	{
		T0Count2 = 0;
		Nixie_Loop();	//2ms调用一次数码管驱动函数
	}
	T0Count3++;
	if(T0Count3 >= 10)
	{
		T0Count3 = 0;
		Sec_Loop();		//10ms调用一次数秒表驱动函数
	}
}

2、I2C.c

#include <REGX52.H>

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

/**
  * @brief  I2C开始
  * @param  无
  * @retval 无
  */
void I2C_Start(void)
{
	I2C_SDA = 1;//先拉高SDA
	I2C_SCL = 1;//后拉高SCL
	I2C_SDA = 0;
	I2C_SCL = 0;
}

/**
  * @brief  I2C结束
  * @param  无
  * @retval 无
  */
void I2C_Stop(void)
{
	I2C_SDA = 0;//在停止之前保证SDA为0
	I2C_SCL = 1;
	I2C_SDA = 1;
}

/**
  * @brief  I2C发送一个字节
  * @param  byte 要发送的字节
  * @retval 无
  */
void I2C_SendByte(unsigned char byte)
{
	int i = 0;
	for(i = 0; i < 8; i++)
	{
		I2C_SDA = byte & (0x80 >> i);
		I2C_SCL = 1;
		I2C_SCL = 0;
	}
}

/**
  * @brief  I2C接收一个字节
  * @param  无
  * @retval byte 接收到的一个字节数据
  */
unsigned char I2C_ReceiveByte(void)
{
	unsigned char i = 0, 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;
}

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

/**
  * @brief  I2C接收应答位
  * @param  无
  * @retval AskBit 接收到的应答位,0为应答,1为非应答
  */
unsigned char I2C_ReceiveAsk(void)
{
	unsigned char AskBit = 0; 
	I2C_SDA = 1;//接收前释放SDA
    I2C_SCL = 1;//主机在SCL高电平期间读取数据位
	AskBit = I2C_SDA;//接收数据位
	I2C_SCL = 0;
	return AskBit;
}

3、I2C.h

#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_SendAsk(unsigned char AskBit);
unsigned char I2C_ReceiveAsk(void);

#endif

4、AT24C02.c

#include <REGX51.H>
#include "I2C.h"
  
#define AT24C02_ADDRESS 0xA0

/**
  * @brief  AT24C02写入一个字节
  * @param  WordAddress 要写入的字节
  * @param  Data 要写入的数据
  * @retval 无
  */
void AT24C02_WriteByte(unsigned char WordAddress, Data)
{
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS);//写入从机地址
	I2C_ReceiveAsk();
	I2C_SendByte(WordAddress);//写入字地址
	I2C_ReceiveAsk();
	I2C_SendByte(Data);
	I2C_ReceiveAsk();
	I2C_Stop();
}

/**
  * @brief  AT24C02读取一个字节
  * @param  WordAddress 要读出字节的地址
  * @retval 读出的数据
  */
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
	unsigned char Data;
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS);
	I2C_ReceiveAsk();
	I2C_SendByte(WordAddress);
	I2C_ReceiveAsk();
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS | 0x01);//读从机地址
	I2C_ReceiveAsk();
	Data = I2C_ReceiveByte();
	I2C_SendAsk(1);
	I2C_Stop();
	return Data;
}

5、AT24C02.h

#ifndef __AT24C02_H__
#define __AT24C02_H__

void AT24C02_WriteByte(unsigned char WordAddress, Data);
unsigned char AT24C02_ReadByte(unsigned char WordAddress);

#endif

6、Nixie.c

#include <REGX52.H>

//数码管显示缓存区
unsigned char Nixie_Buf[9] = {0, 10, 10, 10, 10, 10, 10, 10, 10};//第0位不要,第1~8位对应NixieTable[10]即不显示

//数码管段码表
unsigned char NixieTable[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x00, 0x40};//0--9, 10:不显示, 11:显示-

/**
  * @brief  设置显示缓存区
  * @param  Location 要设置的位置,范围:1~8
  * @param  Number 要设置的数字,范围:段码表索引范围
  * @retval 无
  */
void Nixie_SetBuf(unsigned int Location, Number)
{
	Nixie_Buf[Location] = Number;
}

/**
  * @brief  数码管扫描显示
  * @param  Location 要显示的位置,范围:1~8
  * @param  Number 要显示的数字,范围:段码表索引范围
  * @retval 无
  */
void Nixie_Scan(unsigned int 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];//段码输出
}

/**
  * @brief  数码管驱动函数,在中断中调用
  * @param  无
  * @retval 无
  */
void Nixie_Loop(void)
{
	static unsigned char i = 1;
	Nixie_Scan(i, Nixie_Buf[i]);
	i++;
	if(i >= 9)
	{
		i = 1;
	}
}

7、Nixie.h

#ifndef __NIXIE_H__
#define __NIXIE_H__

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

#endif

8、Key.c

#include <REGX52.H>

unsigned char Key_KeyNumber;

/**
  * @brief  获取按键键码
  * @param  无
  * @retval 按下按键的键码,范围:0,1~4, 0表示无按键按下
  */
unsigned char Key(void)
{
	unsigned char Temp = 0;
	Temp = Key_KeyNumber;
	Key_KeyNumber = 0;
	return Temp;
}

/**
  * @brief  获取当前按键的状态,无消抖及松手检测
  * @param  无
  * @retval 按下按键的键码,范围:0,1~4, 0表示无按键按下
  */
unsigned char KeyState()
{
	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;
}

/**
  * @brief  按键驱动函数,在中断中调用
  * @param  无
  * @retval 无
  */
void Key_Loop(void)
{
	static unsigned char NowState, LastState;
	LastState = NowState;//将现状态赋给上状态
	NowState = KeyState();//获取当前按键状态
	//如果上个时间点按键按下,这个时间点未按下,则是松手瞬间,以此避免消抖和松手检测
	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;
	}
}

9、Key.h

#ifndef __KEY_H__
#define __KEY_H__

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

#endif

10、Timer0.c

#include <REGX52.H>

/**
  * @brief  定时器0初始化,1毫秒@12.000MHz
  * @param  无
  * @retval 无
  */
void Timer0_Init(void)
{
	TMOD &= 0xF0;		 //设置定时器模式
	TMOD |= 0x01;		 //设置定时器模式
	TL0 = 0x18;		 	 //设置定时初值
	TH0 = 0xFC;		   //设置定时初值
	TF0 = 0;		     //清除TF0标志
	TR0 = 1;		     //定时器0开始计时
	ET0 = 1;
	EA = 1;
	PT0 = 0;
}

11、Timer0.h

#ifndef __TIMER0_H__
#define __TIMER0_H__

void Timer0_Init(void);

#endif

12、Delay.c

void Delay(unsigned int xms)		
{
	unsigned char i, j;
	while(xms--)
	{
	i = 2;
	j = 199;
		do
		{
			while (--j);
		} while (--i);
	}
}

13、Delay.h

#ifndef __DELAY_H__
#define __DELAY_H__

void Delay(unsigned int xms);

#endif

 三、仿真

需要自取

链接:https://pan.baidu.com/s/1BDx7cT6DH1juG0078GBRpA?pwd=6664 
提取码:6664

此文章为个人笔记,如有错漏还请各位看官指正。

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值