学习日志-C51-数码管秒钟-I2C

学习日志-C51-数码管秒钟-I2C
现象:由数码管显示秒钟,Key1为暂停/开始键,Key2清零键,Key3存储键,Key4读取键(掉电不丢失)。
其他细节问题参考江科协
1.AT24C02
在这里插入图片描述

AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息,此处我们用于存储数码管地址与显示数据。
存储介质:E2PROM
通讯接口:I2C总线
容量:256字节
在这里插入图片描述
2.I2C
(1)I2C
两根通信线:SCL(Serial Clock)、SDA(Serial Data)
同步、半双工,带数据应答
电路规范:
在这里插入图片描述

采用主从模式,所有I2C设备的SCL连在一起,SDA连在一起,我们以单片机为主设备,其他为从机。
(2)读写数据(从最高位开始)
在这里插入图片描述
写入数据:每个从机的地址是唯一的,AT24C02地址为1010,为了确定与哪个从机通讯,需要发送7位地址码,在发送一位读写位,写入为0,然后一位为接收应答信号,是由从机发送给主机,如果从机接收到了信息,则会回复0,然后8位为寄存器地址,接收成功回复0,然后8位为具体要写入的数据,接收成功回复0,最后一位为停止位。
在这里插入图片描述
读取数据:与写入类似,主机通过从机地址以及寄存器地址通知从机数据存储位置,在发送一次起始位与设备地址,发送读取数据,则可以开始读取数据,读取完毕后主机向从机发送应答信号,最后一位为停止位。
时序:
(1)起始位:SCL高电平期间,SDA从高电平切换到低电平
在这里插入图片描述

void I2C_Start()//起始位
{
	I2C_SDA=1;
	I2C_SCL=1;
	I2C_SDA=0;
	I2C_SCL=0;
}

起始位要实现在SCL为1时,SDA由1→0的过程。空闲时SCL与SDA均为1,由于在读取数据时起始位可能接在应答信号后,不确定SDA的数值,因此要先将SDA置1,确保SDA可以进行下降沿,在将SCL置1,在SCL为1的状态下,再将SDA置0,实现SDA由1→0的过程,SCL再置0是为了后面好拼接各个时序块。
(2)停止位:SCL高电平期间,SDA从低电平切换到高电平
在这里插入图片描述

void I2C_Stop()//停止位
{
	I2C_SDA=0;
	I2C_SCL=1;
	I2C_SDA=1;
}

停止位要实现在SCL为1时,SDA由0→1的过程。停止位前SDA不确定,因此需要先置0,再将SCL置1后将SDA置1。结束后两者均为1,与空闲状态相符。
(3)发送数据/写
在这里插入图片描述
SCL低电平期间,主机将数据位依次放到SDA线上(由高位开始),然后SCL置1,从机将在SCL高电平期间读取数据位。
SCL置1时SDA不能变化,依次循环上述过程8次,即可发送一个字节。

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;
	}
}

(4)接收数据/读
SCL低电平期间,主机将数据位依次放到SDA线上(由高位开始),然后SCL置1,从机将在SCL高电平期间读取数据位。
SCL置1时SDA不能变化,依次循环上述过程8次,即可接收一个字节,主机再接收数据前要将SDA释放,即将SDA控制权给从机。

unsigned char I2C_ReceiveByte()//接收一个数据
{
	unsigned char i,Byte=0x00;
	I2C_SDA=1;//释放SDA,将SDA控制权给从机
	for(i=0;i<8;i++)
	{
		I2C_SCL=1;//SCL为1时可读取SDA值
		if(I2C_SDA){Byte|=(0x80>>i);}//如果从机发送的SDA为1,则可读取Byte该位为1
		I2C_SCL=0;
	}
	return Byte;
}

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

void I2C_SendAck(unsigned char AckBit)//发送应答位
{
	I2C_SDA=AckBit;
	I2C_SCL=1;
	I2C_SCL=0;
}
unsigned char I2C_ReceiveAck(void)//接收应答位
{
	unsigned char AckBit;
	I2C_SDA=1;//释放SDA
	I2C_SCL=1;
	AckBit=I2C_SDA;
	I2C_SCL=0;
	return AckBit;
}

3.秒钟功能实现
在这里插入图片描述
按键与数码管同时都需要定时器0进行扫描,而定时器0只对应一个中断,则可再加入中介函数,将按键驱动函数与数码管驱动函数同时再中断程序中调用,则相当于两者均实现了扫描。
(1)按键

#include <REGX52.H>
/*需要函数:
得到当前按键状态的函数;
比较当前状态与上次状态,确定是否动作的函数;
得到当前按键键码的函数。
*/

unsigned char Key_GetState()//获得当前按键状态的函数,KeyNumber
{
	unsigned char KeyNumber=0;//不按时为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;//返回按键状态01234
}
unsigned char Key_KeyNum;
void Key_Drive()
{
	static unsigned char NewState,LastState;
	LastState=NewState;
	NewState=Key_GetState();//获得新状态
	if(LastState==1&&NewState==0)//上个状态为按下Key1,新状态松手
		{Key_KeyNum=1;}//键码为1
	if(LastState==2&&NewState==0)//上个状态为按下Key1,新状态松手
		{Key_KeyNum=2;}//键码为2
	if(LastState==3&&NewState==0)//上个状态为按下Key1,新状态松手
		{Key_KeyNum=3;}//键码为3
	if(LastState==4&&NewState==0)//上个状态为按下Key1,新状态松手
		{Key_KeyNum=4;}//键码为4		
}
unsigned char Key(void)//获得键码
{
	unsigned char Temp=0;//键码值不会自动清零,因此加入一个中间量
	Temp=Key_KeyNum;//加入中间变量i,将键码赋值给i
	Key_KeyNum=0;//将循环中的键码清零
	return Temp;
}

(2)数码管

#include <REGX52.H>
unsigned char Nixie_Buf[9]={0,10,10,10,10,10,10,10,10};//数值缓存区
unsigned char Nixietude[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x00,0x40};//0123456789 -(显示的数值)
void Nixie(unsigned char location,Number)//数码管显示
{
	switch(location)
	{
		P0=0x00;
		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=Nixietude[Number];	
}
void Nixie_SetBuf(unsigned char location,Number)
{
	Nixie_Buf[location]=Number;//将需要显示的数值存放到对应缓存区
}
void Nixie_Drive()//驱动函数
{
	static unsigned char Count=1;
	Nixie(Count,Nixie_Buf[Count]);//第i位显示缓存区第i个数值(因此缓存区设置了9位,因为第0位用不到)
	Count++;
	if(Count>8){Count=0;}
}

(3)AT24C02
在这里插入图片描述
在这里插入图片描述

#include <REGX52.H>
#include "I2C.h"
#define AT24C02_ADDRESS		0xA0//定义AT24C02地址
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();//停止位
}
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;
}

(4)主函数

#include <REGX52.H>
#include "T0.h"
#include "Key.h"
#include "Nixie.h"
#include "Delay.h"
#include "AT24C02.h"
unsigned char Sec,Min,HunSec;
unsigned char KeyNum;
unsigned char Pause;
void main()
{
	Timer0Init();
	while(1)
	{
		KeyNum=Key();//得到按键值
		if(KeyNum==1)
		{
			Pause=!Pause;//按键1为停止/开始键
		}
		if(KeyNum==2)
		{
			Sec=0;//按下按键2则清0
			Min=0;
			HunSec=0;
		}
		if(KeyNum==3)//按键3存入数据
		{
			AT24C02_WriteByte(0,Min);	
			Delay(5);
			AT24C02_WriteByte(1,Sec);
			Delay(5);
			AT24C02_WriteByte(2,HunSec);
			Delay(5);
		}
		if(KeyNum==4)//按键4读取数据			
		{
			Min=AT24C02_ReadByte(0);	
			Sec=AT24C02_ReadByte(1);
			HunSec=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,HunSec/10);
	  Nixie_SetBuf(8,HunSec%10);
	}
}
void Sec_Drive()//秒种驱动
{
	if(Pause)
	{
		HunSec++;
		if(HunSec>99)
		{
			HunSec=0;
			Sec++;
			if(Sec>59)
			{
				Sec=0;
				Min++;
				if(Min>59)
				{Min=0;}
			}
		}
	}
}
void Timer0_Routine() interrupt 1  //中断程序,1ms中断一次
{
	static unsigned int KeyCount,NixieCount,SecCount;
	TL0 = 0x66;		//初值,决定中断时间
	TH0 = 0xFC;		
	KeyCount++;
	if(KeyCount>=20)//20ms调用一次按键驱动函数
	{
		KeyCount=0;
		Key_Drive();	
	}
	NixieCount++;
	if(NixieCount>=2)//2ms调用一次数码管驱动
	{
		NixieCount=0;
		Nixie_Drive();
	}
	SecCount++;
	if(SecCount>=10)//10ms调用一次秒钟驱动函数,因为秒钟后两位为百分秒,因此10ms调用一次
	{
		SecCount=0;
		Sec_Drive();
	}
}


  • 22
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值