学习日志-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();
}
}