关于学习在B站上学习江科大的51单片机,DS1302这一部分的学习总结和思路梳理。用DS1302和LCD1602实现了江科大视频所展示的效果。
实现的功能:用DS1302进行计时,并用LCD1602进行显示,可以使用独立按键进行设置,K1按键进行显示模式和设置时间模式切换,K2:当为设置时间模式时,可以按下K2选取任意时间位,K3:当K2选取了某时间位后,按下K3该位的时间会加1,K4:同理K3,按下后该时间位会减1.
首先先实现,DS1302芯片的写入时间和读取时间的功能。
这是DS1302的寄存器地址和单字节读、写的时序图。可知,无论读写,都是要先将要读的那个寄存器地址由单片机发送到DS1302,之后便能进行读或写数据的操作。
由晋中51-实验板原理图,对这3个引脚进行命名,方便编写代码。因为单片机上电所有引脚为高电平,又由DS1302时序图可知三个引脚都是低电平,所以要对这三个引脚进行初始化,将它们设置为低电平。
这是写数据的代码部分,定义两个形参来接受要写入DS1302的哪个寄存器和写入的数据,因为时序图是一个数一个数的写入,所以使用了按位与的方法来确保8个位只有一个数有效,然后通过左移和for循环把这八个位在每一次的时钟上升沿中,写入到DS1302。要注意的是,手册中当单字节 读或者写完成后,CE、SCLK都要置为低电平。
这是读取数据代码的部分。
此上,写入和读取的函数功能就已经完成了。现在可以来写入时间,并读取时间了,并使用LCD1602显示出来。首先先宏定义一下这些寄存器的地址,以便使用。
定义一个char类型的数组来存放初始时间,分别是年、月、日、时、分、秒。
时间初始化函数,将初始时间,分别写入DS1302的对应寄存器,因为DS1302要写入的是BCD码,所以要将10进制数,通过这种方式转换成BCD码再按位写入,Write_WP是DS1302的写保护位,在写入数据之前要置0,写完后置1.
读数据并显示数据函数,去读已经写入的寄存器的时间,并在LCD上合适的位置显示出来 。
为了更好观看,用“—”和“:”字符把时间隔离起来。
目前显示的时间的功能已经完成了,如果想要修改时间,只能去修改数组的内容,重新将文件烧录进单片机才能完成,很不方便,接下来是去完成通过用按键修改时间的功能。
定义一个Mode变量,如果Mode为0则是显示模式,显示时间,如果Mode为1 ,则进入校时模式,可以修改时间,再定义一个Num变量,来接收独立按键的置,当k1按下时返回1,再通过一个if判断,当按键按下时就能改变Mode。全局变量Mode为0。再用一个switch,case来选择这两个模式。
现在只差修改某位时间和修改时间的时候修改位的闪烁提示功能。
定义一个全局变量Timeselect,作为时间位选变量,按键2按下可以位选会加1,因为只有年、月、日、时、分、秒,所以当它大于5时,就会越界,要将它清零。因为这个timeselect是用来选数组的某个元素的,所以是从0开始,0-5刚好6个数。
当按下第三个按键或者第四个按键时,对应的选择位的时间就会被修改,修改时间的功能已经完成,现在要对时间的越界进行判断。
这是时间越界判断部分。
这部分是校时模式中,实时显示修改之后时间,并对应修改位有闪烁提示。闪烁功能的完成是用了一个Timeflag变量,并用了定时中断,每500ms进行取反,再用if,else判断,让选择时间位在LCD上来回 显示数字和空格就达到了闪烁提示的效果。
当从校时模式切换到显示模式时,又调用一次Set_ time()函数,对修改之后的时间,又写入DS1302,就能完成修改功能了。
定时器代码、LCD模块、独立按键代码都是直接用的江科大的。
DS1302和主函数代码如下:
#include <REGX52.H>
sbit SCLK=P3^6;
sbit CE=P3^5;
sbit IO=P3^4;
void DS1302_init()
{
SCLK=0;
CE=0;
IO=0;
}
void DS1302_Write(unsigned char Command,unsigned char Data)
{
unsigned char i=0;
CE=1;
for(i=0;i<8;i++)
{
IO=Command&(0x01<<i);
SCLK=1;
SCLK=0;
}
for(i=0;i<8;i++)
{
IO=Data&(0x01<<i);
SCLK=1;
SCLK=0;
}
CE=0;
}
unsigned char DS1302_Read(unsigned char Command)
{
unsigned char i=0;
unsigned char Data=0x00;
CE=1;
for(i=0;i<8;i++)
{
IO=Command&(0x01<<i);
SCLK=0;
SCLK=1;
}
for(i=0;i<8;i++)
{
SCLK=1;
SCLK=0;
if(IO)
{
Data|=(0x01<<i);//读取DS1302某个寄存器的数据,在SCLK上升沿读取数据,判断IO口为高电平还是低电平
} //由Data作为一个临时的存储站,用按位或的方法去记录这8次上升沿IO口的电平情况,
//并将Data返回,就能得到此时DS1302数据
}
CE=0;
IO=0;
return Data;
}
主函数代码:
#include <REGX52.H>
#include "LCD1602.h"
#include "Delay.h"
#include "DS1302.h"
#include "key.h"
#include "Timer0.h"
#define Read_Year 0x8D
#define Read_Month 0x89
#define Read_Date 0x87
#define Read_Hour 0x85
#define Read_Min 0x83
#define Read_Second 0x81
#define Write_WP 0x8E
#define Write_Year 0x8C
#define Write_Month 0x88
#define Write_Date 0x86
#define Write_Hour 0x84
#define Write_Min 0x82
#define Write_Second 0x80
char Time[]={23,8,17,20,44,55};
unsigned char Mode,Num,Timeselect;//0是显示模式,1是校时模式
unsigned char Timeflag;
void Gap();
void Read_Show();
void Change_time();
void Set_time();
void main()
{
DS1302_init();
Timer0Init();
LCD_Init();
Gap();
Set_time();
while(1)
{
Num=key();
if(Num==1)
{
if(Mode==0){Mode=1;Num=0;}
else if(Mode==1){Mode=0;Set_time();Num=0;Timeselect=0;}
}
switch(Mode)
{
case 0:
Read_Show();
break;
case 1:
Change_time();
break;
}
}
}
void Gap()
{
LCD_ShowString(1,1," - - ");
LCD_ShowString(2,1," : : ");
}
void Read_Show()
{
unsigned char Temp;
Temp=DS1302_Read(Read_Year);
Time[0]=Temp/16*10+Temp%16;//BCD码转十进制后读取
Temp=DS1302_Read(Read_Month);
Time[1]=Temp/16*10+Temp%16;
Temp=DS1302_Read(Read_Date);
Time[2]=Temp/16*10+Temp%16;
Temp=DS1302_Read(Read_Hour);
Time[3]=Temp/16*10+Temp%16;
Temp=DS1302_Read(Read_Min);
Time[4]=Temp/16*10+Temp%16;
Temp=DS1302_Read(Read_Second);
Time[5]=Temp/16*10+Temp%16;
LCD_ShowNum(1,1,Time[0],2);
LCD_ShowNum(1,4,Time[1],2);
LCD_ShowNum(1,7,Time[2],2);
LCD_ShowNum(2,1,Time[3],2);
LCD_ShowNum(2,4,Time[4],2);
LCD_ShowNum(2,7,Time[5],2);
}
void Set_time()
{
DS1302_Write(Write_WP,0x00);
DS1302_Write(Write_Year,Time[0]/10*16+Time[0]%10);
DS1302_Write(Write_Month,Time[1]/10*16+Time[1]%10);
DS1302_Write(Write_Date,Time[2]/10*16+Time[2]%10);
DS1302_Write(Write_Hour,Time[3]/10*16+Time[3]%10);
DS1302_Write(Write_Min,Time[4]/10*16+Time[4]%10);
DS1302_Write(Write_Second,Time[5]/10*16+Time[5]%10);
DS1302_Write(Write_WP,0x80);
}
void Change_time()
{
if(Num==2)
{
Timeselect++;
if(Timeselect>5){Timeselect=0;}
}
if(Num==3)
{
Time[Timeselect]++;
if(Time[1]>12){Time[1]=1;}//月越界判断
//日越界判断
if(Time[1]==1||Time[1]==3||Time[1]==5||Time[1]==7||Time[1]==8||Time[1]==10||Time[1]==12)
{
if(Time[2]>31){Time[2]=1;}
}
else if(Time[1]==4||Time[1]==6||Time[1]==9||Time[1]==11)
{
if(Time[2]>30){Time[2]=1;}
}
else if(Time[1]==2)
{
if(Time[0]%4==0)
{
if(Time[2]>29){Time[2]=1;}
}
else
{
if(Time[2]>28){Time[2]=1;}
}
}
if(Time[3]>23){Time[3]=0;}//时越界判断
if(Time[4]>59){Time[4]=0;}//分越界判断
if(Time[5]>59){Time[5]=0;}//秒越界判断
}
if(Num==4)
{
Time[Timeselect]--;
if(Time[1]<1){Time[1]=12;}//月越界判断
//日越界判断
if(Time[1]==1||Time[1]==3||Time[1]==5||Time[1]==7||Time[1]==8||Time[1]==10||Time[1]==12)
{
if(Time[2]<1){Time[2]=31;}
if(Time[2]>31){Time[2]=1;}
}
else if(Time[1]==4||Time[1]==6||Time[1]==9||Time[1]==11)
{
if(Time[2]<1){Time[2]=30;}
if(Time[2]>30){Time[2]=1;}
}
else if(Time[1]==2)
{
if(Time[0]%4==0)
{
if(Time[2]<1){Time[2]=29;}
if(Time[2]>29){Time[2]=1;}
}
else
{
if(Time[2]<1){Time[2]=28;}
if(Time[2]>28){Time[2]=1;}
}
}
if(Time[3]<0){Time[3]=23;}//时越界判断
if(Time[4]<0){Time[4]=59;}//分越界判断
if(Time[5]<0){Time[5]=59;}//秒越界判断
}
if(Timeflag==1&&Timeselect==0){LCD_ShowString(1,1," ");}
else {LCD_ShowNum(1,1,Time[0],2);}
if(Timeflag==1&&Timeselect==1){LCD_ShowString(1,4," ");}
else {LCD_ShowNum(1,4,Time[1],2);}
if(Timeflag==1&&Timeselect==2){LCD_ShowString(1,7," ");}
else {LCD_ShowNum(1,7,Time[2],2);}
if(Timeflag==1&&Timeselect==3){LCD_ShowString(2,1," ");}
else {LCD_ShowNum(2,1,Time[3],2);}
if(Timeflag==1&&Timeselect==4){LCD_ShowString(2,4," ");}
else {LCD_ShowNum(2,4,Time[4],2);}
if(Timeflag==1&&Timeselect==5){LCD_ShowString(2,7," ");}
else {LCD_ShowNum(2,7,Time[5],2);}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=500)//每500ms进入一次
{
T0Count=0;
Timeflag=!Timeflag;//闪烁标志位取反
}
}