❤️ 专栏简介:本专栏记录了从零学习单片机的过程,其中包括51单片机和STM32单片机两部分;建议先学习51单片机,其是STM32等高级单片机的基础;这样再学习STM32时才能融会贯通。
☀️ 专栏适用人群 :适用于想要从零基础开始学习入门单片机,且有一定C语言基础的的童鞋。
🌙专栏目标:实现从零基础入门51单片机和STM32单片机,力求在玩好单片机的同时,能够了解一些计算机的基本概念,了解电路及其元器件的基本理论等。⭐️ 专栏主要内容: 主要学习51单片机的功能、各个模块、单片机的外设、驱动等,最终玩好单片机和单片机的外设,全程手敲代码,实现我们所要实现的功能。
🌴 专栏说明 :如果文章知识点有错误的地方,欢迎大家随时在文章下面评论,我会第一时间改正。让我们一起学习,一起进步。
💑专栏主页:http://t.csdn.cn/HCD8v
本学习过程参考:https://space.bilibili.com/383400717
单片机安装软件、各种资料以及源码的路径:
https://pan.baidu.com/s/1vDTN2o8ffvczzNQGfyjHng
提取码:gdzf
本节主要介绍学习DS1302实时时钟的相关知识,包括DS1302实时时钟基础知识介绍、本节目标等;并利用两个小实验来写程序进行练习,分别是DS1302时钟以及DS1302可调时钟,最后附上相关代码。
文章目录
- 一、DS1302实时时钟和本节目标
- 1.1 DS1302基础知识
- 1.2 本节目标
- 二、DS1302时钟
- 三、DS1302可调时钟
一、DS1302实时时钟和本节目标
1.1 DS1302基础知识
DS1302在开发版上的位置:
一定要学会通过看手册来学习和使用一款新的芯片,这里以DS1302为例,来学习一下看手册:
DS1302手册的路径:51单片机入门教程资料\开发板资料包\HC6800-ES V2.0\开发板芯片资料\DS1302中文手册.pdf
DS1302芯片是用来计时的,引脚定义和应用电路如下图所示
DIP是直插式,直接通过引脚插在MCU上的;SO是贴片式,贴在MCU上的;虽然方式不一样,但是内部芯片是一样的。
Vcc2是主电源,由MCU通电后对其进行供电;Vcc1是备用电源,MCU掉电时使用电池进行备用使用。
X1、X2接的是32.768KHz晶振;晶振给负责给实时时钟系统提供稳定的计数脉冲;
CE、I/O、SCLK三个引脚是通信引脚,MCU通过这三个引脚对时钟系统进行时间的读取和写入。
内部结构框图
CE为时钟使能开关,当CE为高电平时,对时钟的读写等操作才有效。
寄存器定义
上图中每个寄存器都有一个地址,各个寄存器的功能如下图所示:
而左边的READ和WRITE则表示读和写的命令字;例如,对于秒控制器,当命令字设置为0x81时,表示要对秒寄存器进行读操作;反之,当命令字设置为0x80,则表示对秒寄存器进行写操作。
那什么是命令字呢?
在时钟芯片上,我们如果想要对以上寄存器进行读写时操作,需要有三个条件,一是在哪里,二是什么操作,三是读/写什么;也就是说要确认,在哪个寄存器进行什么操作(读呢还是写呢?),读/写的内容是什么;
那么命令字就可以解决前两个条件,通过命令字就可以确认时在哪个寄存器进行什么操作,比如命令字设置为0x81,则表示对秒寄存器进行读操作;再比如命令字设置为0x84,则表示对时寄存器进行写操作。
至于第三个条件,读/写的内容怎么确认?命令字和写入的数据是怎么对应起来的呢?就要通过时序图:
时序图中是对CE(操作使能)、SCLK(时钟)、I/O(数据)三个引脚的操作,我们要学会看时序图,上面是单字符读的时序图,下面是单字符写的时序图;
以写的时序图为例,来解释一下,
可以看到,再下面写的时序图里,CE引脚,开始写时从零变为1,然后全程是高电平的,写完后右边为低电平0;
SCLK时钟引脚,是一个高电平,一个低电平,再一个高电平,再一个低电平,如此循环,即时钟信号先来一个上升沿,在一个下降沿,再一个上升沿,再一个下降沿,如此往复;且时钟里规定,在SCLK的上升沿,I/O口将写入一个对应的数据;在SCLK的下降沿,DS1302就会将数据输出到MCU;总结一句话就是,在SCLK的上升沿,MCU向时钟写入数据,在SCLK的下降沿,时钟向MCU写入数据,也等价于MCU向时钟读取数据;这种方式跟之前提到的SPI通信方式很类似。
I/O引脚,左边半部分是表示命令字,从左到右是命令字八个位的从低位到高位;右边半部分表示要写入的字节,从左到右也是字节的从低位到高位。
下面详细介绍一下时序图的过程(以写数据为例):
首先将CE置为高电平1;然后对于写数据来说,需要给到时钟输入两个字节,第一个字节是命令字,第二个字节表示要写入的字节数据;
我们首先取第一个字节的最低位,然后将SCLK置为高电平1(对应上升沿),此时就将第一个字节的最低位给到了R/W,然后将SCLK置为低电平0(对应下降沿);然后取第一个字节的第二位,然后将SCLK置为高电平1(对应上升沿),此时就将第二个字节的第二位给到了A0,然后将SCLK置为低电平0(对应下降沿);如此循环8次,直到将第一个字节的所有位都给到了命令字;然后同样的方法,将第二个字节的每一位写入到D0-D7;此时就完成了整个单字节写的过程。
1.2 本节目标
目标1:实现一个DS1302时钟,显示在LCD1602上,效果如下:
目标2:实现一个DS1302时钟,并能通过按键对时间进行修改
其中第一个按键功能是在显示模式和调节时间模式间进行切换
比如当按下按键第一个按键时,时钟由显示模式改为了调节时间模式,即年时间在闪烁:
第二个按键的功能是选择调节的位,比如上图已经到了调节年,按下第二个键时,可以发现到了调节月,即月时间在闪烁:
同样的,再按下第二个按键时,可以发现到了调节日,即日时间在闪烁:
第三个按键是对时间进行加操作,第四个按键是对时间进行减操作。
并且逻辑里加入了闰年、闰月、每个月是28天还是29天还是30天还是31天等等逻辑进行了判断,所以逻辑比较复杂。
二、DS1302时钟
代码路径:51单片机入门教程资料\课件及程序源码\程序源码\KeilProject\10-1 DS1302时钟
具体代码:
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
void main()
{
LCD_Init();
DS1302_Init();
LCD_ShowString(1,1," - - ");//静态字符初始化显示
LCD_ShowString(2,1," : : ");
DS1302_SetTime();//设置时间
while(1)
{
DS1302_ReadTime();//读取时间
LCD_ShowNum(1,1,DS1302_Time[0],2);//显示年
LCD_ShowNum(1,4,DS1302_Time[1],2);//显示月
LCD_ShowNum(1,7,DS1302_Time[2],2);//显示日
LCD_ShowNum(2,1,DS1302_Time[3],2);//显示时
LCD_ShowNum(2,4,DS1302_Time[4],2);//显示分
LCD_ShowNum(2,7,DS1302_Time[5],2);//显示秒
}
}
DS1302.c:
#include <REGX52.H>
//引脚定义
sbit DS1302_SCLK=P3^6;
sbit DS1302_IO=P3^4;
sbit DS1302_CE=P3^5;
//寄存器写入地址/指令定义
#define DS1302_SECOND 0x80
#define DS1302_MINUTE 0x82
#define DS1302_HOUR 0x84
#define DS1302_DATE 0x86
#define DS1302_MONTH 0x88
#define DS1302_DAY 0x8A
#define DS1302_YEAR 0x8C
#define DS1302_WP 0x8E
//时间数组,索引0~6分别为年、月、日、时、分、秒、星期
unsigned char DS1302_Time[]={19,11,16,12,59,55,6};
/**
* @brief DS1302初始化
* @param 无
* @retval 无
*/
void DS1302_Init(void)
{
DS1302_CE=0;
DS1302_SCLK=0;
}
/**
* @brief DS1302写一个字节
* @param Command 命令字/地址
* @param Data 要写入的数据
* @retval 无
*/
void DS1302_WriteByte(unsigned char Command,Data)
{
unsigned char i;
DS1302_CE=1;
for(i=0;i<8;i++)
{
DS1302_IO=Command&(0x01<<i);
DS1302_SCLK=1;
DS1302_SCLK=0;
}
for(i=0;i<8;i++)
{
DS1302_IO=Data&(0x01<<i);
DS1302_SCLK=1;
DS1302_SCLK=0;
}
DS1302_CE=0;
}
/**
* @brief DS1302读一个字节
* @param Command 命令字/地址
* @retval 读出的数据
*/
unsigned char DS1302_ReadByte(unsigned char Command)
{
unsigned char i,Data=0x00;
Command|=0x01; //将指令转换为读指令
DS1302_CE=1;
for(i=0;i<8;i++)
{
DS1302_IO=Command&(0x01<<i);
DS1302_SCLK=0;
DS1302_SCLK=1;
}
for(i=0;i<8;i++)
{
DS1302_SCLK=1;
DS1302_SCLK=0;
if(DS1302_IO){Data|=(0x01<<i);}
}
DS1302_CE=0;
DS1302_IO=0; //读取后将IO设置为0,否则读出的数据会出错
return Data;
}
/**
* @brief DS1302设置时间,调用之后,DS1302_Time数组的数字会被设置到DS1302中
* @param 无
* @retval 无
*/
void DS1302_SetTime(void)
{
DS1302_WriteByte(DS1302_WP,0x00);
DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10);//十进制转BCD码后写入
DS1302_WriteByte(DS1302_MONTH,DS1302_Time[1]/10*16+DS1302_Time[1]%10);
DS1302_WriteByte(DS1302_DATE,DS1302_Time[2]/10*16+DS1302_Time[2]%10);
DS1302_WriteByte(DS1302_HOUR,DS1302_Time[3]/10*16+DS1302_Time[3]%10);
DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[4]/10*16+DS1302_Time[4]%10);
DS1302_WriteByte(DS1302_SECOND,DS1302_Time[5]/10*16+DS1302_Time[5]%10);
DS1302_WriteByte(DS1302_DAY,DS1302_Time[6]/10*16+DS1302_Time[6]%10);
DS1302_WriteByte(DS1302_WP,0x80);
}
/**
* @brief DS1302读取时间,调用之后,DS1302中的数据会被读取到DS1302_Time数组中
* @param 无
* @retval 无
*/
void DS1302_ReadTime(void)
{
unsigned char Temp;
Temp=DS1302_ReadByte(DS1302_YEAR);
DS1302_Time[0]=Temp/16*10+Temp%16;//BCD码转十进制后读取
Temp=DS1302_ReadByte(DS1302_MONTH);
DS1302_Time[1]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_DATE);
DS1302_Time[2]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_HOUR);
DS1302_Time[3]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_MINUTE);
DS1302_Time[4]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_SECOND);
DS1302_Time[5]=Temp/16*10+Temp%16;
Temp=DS1302_ReadByte(DS1302_DAY);
DS1302_Time[6]=Temp/16*10+Temp%16;
}
三、DS1302可调时钟
代码路径:51单片机入门教程资料\课件及程序源码\程序源码\KeilProject\10-2 DS1302可调时钟
具体代码:
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Key.h"
#include "Timer0.h"
unsigned char KeyNum,MODE,TimeSetSelect,TimeSetFlashFlag;
void TimeShow(void)//时间显示功能
{
DS1302_ReadTime();//读取时间
LCD_ShowNum(1,1,DS1302_Time[0],2);//显示年
LCD_ShowNum(1,4,DS1302_Time[1],2);//显示月
LCD_ShowNum(1,7,DS1302_Time[2],2);//显示日
LCD_ShowNum(2,1,DS1302_Time[3],2);//显示时
LCD_ShowNum(2,4,DS1302_Time[4],2);//显示分
LCD_ShowNum(2,7,DS1302_Time[5],2);//显示秒
}
void TimeSet(void)//时间设置功能
{
if(KeyNum==2)//按键2按下
{
TimeSetSelect++;//设置选择位加1
TimeSetSelect%=6;//越界清零
}
if(KeyNum==3)//按键3按下
{
DS1302_Time[TimeSetSelect]++;//时间设置位数值加1
if(DS1302_Time[0]>99){DS1302_Time[0]=0;}//年越界判断
if(DS1302_Time[1]>12){DS1302_Time[1]=1;}//月越界判断
if( DS1302_Time[1]==1 || DS1302_Time[1]==3 || DS1302_Time[1]==5 || DS1302_Time[1]==7 ||
DS1302_Time[1]==8 || DS1302_Time[1]==10 || DS1302_Time[1]==12)//日越界判断
{
if(DS1302_Time[2]>31){DS1302_Time[2]=1;}//大月
}
else if(DS1302_Time[1]==4 || DS1302_Time[1]==6 || DS1302_Time[1]==9 || DS1302_Time[1]==11)
{
if(DS1302_Time[2]>30){DS1302_Time[2]=1;}//小月
}
else if(DS1302_Time[1]==2)
{
if(DS1302_Time[0]%4==0)
{
if(DS1302_Time[2]>29){DS1302_Time[2]=1;}//闰年2月
}
else
{
if(DS1302_Time[2]>28){DS1302_Time[2]=1;}//平年2月
}
}
if(DS1302_Time[3]>23){DS1302_Time[3]=0;}//时越界判断
if(DS1302_Time[4]>59){DS1302_Time[4]=0;}//分越界判断
if(DS1302_Time[5]>59){DS1302_Time[5]=0;}//秒越界判断
}
if(KeyNum==4)//按键3按下
{
DS1302_Time[TimeSetSelect]--;//时间设置位数值减1
if(DS1302_Time[0]<0){DS1302_Time[0]=99;}//年越界判断
if(DS1302_Time[1]<1){DS1302_Time[1]=12;}//月越界判断
if( DS1302_Time[1]==1 || DS1302_Time[1]==3 || DS1302_Time[1]==5 || DS1302_Time[1]==7 ||
DS1302_Time[1]==8 || DS1302_Time[1]==10 || DS1302_Time[1]==12)//日越界判断
{
if(DS1302_Time[2]<1){DS1302_Time[2]=31;}//大月
if(DS1302_Time[2]>31){DS1302_Time[2]=1;}
}
else if(DS1302_Time[1]==4 || DS1302_Time[1]==6 || DS1302_Time[1]==9 || DS1302_Time[1]==11)
{
if(DS1302_Time[2]<1){DS1302_Time[2]=30;}//小月
if(DS1302_Time[2]>30){DS1302_Time[2]=1;}
}
else if(DS1302_Time[1]==2)
{
if(DS1302_Time[0]%4==0)
{
if(DS1302_Time[2]<1){DS1302_Time[2]=29;}//闰年2月
if(DS1302_Time[2]>29){DS1302_Time[2]=1;}
}
else
{
if(DS1302_Time[2]<1){DS1302_Time[2]=28;}//平年2月
if(DS1302_Time[2]>28){DS1302_Time[2]=1;}
}
}
if(DS1302_Time[3]<0){DS1302_Time[3]=23;}//时越界判断
if(DS1302_Time[4]<0){DS1302_Time[4]=59;}//分越界判断
if(DS1302_Time[5]<0){DS1302_Time[5]=59;}//秒越界判断
}
//更新显示,根据TimeSetSelect和TimeSetFlashFlag判断可完成闪烁功能
if(TimeSetSelect==0 && TimeSetFlashFlag==1){LCD_ShowString(1,1," ");}
else {LCD_ShowNum(1,1,DS1302_Time[0],2);}
if(TimeSetSelect==1 && TimeSetFlashFlag==1){LCD_ShowString(1,4," ");}
else {LCD_ShowNum(1,4,DS1302_Time[1],2);}
if(TimeSetSelect==2 && TimeSetFlashFlag==1){LCD_ShowString(1,7," ");}
else {LCD_ShowNum(1,7,DS1302_Time[2],2);}
if(TimeSetSelect==3 && TimeSetFlashFlag==1){LCD_ShowString(2,1," ");}
else {LCD_ShowNum(2,1,DS1302_Time[3],2);}
if(TimeSetSelect==4 && TimeSetFlashFlag==1){LCD_ShowString(2,4," ");}
else {LCD_ShowNum(2,4,DS1302_Time[4],2);}
if(TimeSetSelect==5 && TimeSetFlashFlag==1){LCD_ShowString(2,7," ");}
else {LCD_ShowNum(2,7,DS1302_Time[5],2);}
}
void main()
{
LCD_Init();
DS1302_Init();
Timer0Init();
LCD_ShowString(1,1," - - ");//静态字符初始化显示
LCD_ShowString(2,1," : : ");
DS1302_SetTime();//设置时间
while(1)
{
KeyNum=Key();//读取键码
if(KeyNum==1)//按键1按下
{
if(MODE==0){MODE=1;TimeSetSelect=0;}//功能切换
else if(MODE==1){MODE=0;DS1302_SetTime();}
}
switch(MODE)//根据不同的功能执行不同的函数
{
case 0:TimeShow();break;
case 1:TimeSet();break;
}
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=500)//每500ms进入一次
{
T0Count=0;
TimeSetFlashFlag=!TimeSetFlashFlag;//闪烁标志位取反
}
}