目录
DS1302介绍
引脚定义和应用电路
寄存器定义
命令字
时序定义
代码编写
今天,我们的任务是要编写一个用51开发板编写的可调小时钟,接下来就让我们一步一步来实现它吧!
DS1302介绍
- DS1302是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等多种功能
- RTC(Real Time Clock):实时时钟,是一种集成电路,通常称为时钟芯片
文章图片
引脚定义和应用电路
文章图片
这边是DS1302与CPU之间的一个关系,我先列一个表格来介绍一下各个部分的功能。
引脚名 | 作用 |
---|---|
VCC2 |
主电源 |
VCC1 |
备用电池 |
GND |
接地 |
X1、X2 |
32.768KHz晶振 |
CE |
芯片使能 |
IO |
数据输入/输出 |
SCLK |
串行时钟 |
首先是电源部分,VCC2接的是外部电源,而VCC1接的是内置电源(51开发板上没有内置电源,所以不能看到掉电之后继续走的现象了,但我的闹钟可以掉电后走,hahaha!) ,之后就是GND接地。
之后就是晶振部分,接的是32.768KH的晶振,而单片机本身是11.0592MHZ,这个要注意区分,主要作用就是提供一个稳定的计数脉冲,用来计时。
之后的CE就是芯片使能,不使能就不能读取,但时钟还是可以运行滴;IO数据输入输出,用来获取和修改数据;SCLK串行时钟,和之前的SERCLK是类似的。这个是更为详细的图片:
文章图片
寄存器定义 DS1302的寄存器比较多,我们来一一介绍一下:
文章图片
从上往下,依次是秒,分,时,天,月,星期几(1~7),年。之后的WP是写入保护,置1则写入无效哎,但还可以读数。再下面一行,就是涓流充电模式,也就是内置电源,51单片机没有,我们就先不写这个。注:CH是时钟停止引脚
命令字
文章图片
命令字要解决的问题就是,我要去哪操作,以及,我要读出还是写入这两个问题。第一位,就是看是读出还是写入,当为0时,是写入,为1是读出;2~6位是地址,7位是操控的地方是时钟还是RAM;8位都是1。命令字其实都已经在前两列表示出来了。时序定义
这部分内容是数电的第六章内容,我还没学到,呜呜呜,就只能先浅显的理解一下了。
文章图片
首先,我们需要写好刚刚介绍的命令字部分,每一个上升沿就是一次写入,而下降沿就是输出,所以输入输出主要的区别就是后面部分,从D0~D7,就是我们要操作的数据。代码编写 接下来,我们就开始着手来编写一下代码了,先把原理图放上来。
文章图片
// DS1302.c的代码
#include // 根据原理图,应该可以写出下面几个引脚定义
sbit DS1302_SCLK = P3^6;
sbit DS1302_IO = P3^4;
sbit DS1302_CE = P3^5;
// 初始化函数,因为单片机接电后默认为1
void DS1302_Init(void)
{
DS1302_CE = 0;
DS1302_SCLK = 0;
}
// 写入函数
void DS1302_WriteByte(unsigned char Command, Data)
{
unsigned char i;
DS1302_CE = 1;
// 读取低位
for(i=0;
i<8;
i++){
DS1302_IO = Command&(0x01<
// DS1302.h的代码
#ifndef __DS1302_H__
#define __DS1302_H__unsigned char DS1302_ReadByte(unsigned char Command);
void DS1302_WriteByte(unsigned char Command, Data);
void DS1302_Init(void);
#endif
// 主函数代码
#include
#include "LCD1602.h"
#include "DS1302.h"
#include "Delay.h"
unsigned char Second;
void main(){
LCD_Init();
DS1302_Init();
LCD_ShowString(1, 1,"RTC");
// 解除写入保护
DS1302_WriteByte(0x8E,0x00);
DS1302_WriteByte(0x80,0x03);
while(1){
Second = DS1302_ReadByte(0x81);
LCD_ShowNum(2,1,Second,3);
}
}
如果这样写的话,代码是可以运行的,但是会遇到问题,如下所示,直接从9跳到了16,主要原因就是码制的原因,这个地方用到了数电里的BCD码,如有不清楚的小伙伴,可以去看看我的数电笔记。
所以我们要对代码进行修改,如下所示:
#include
#include "LCD1602.h"
#include "DS1302.h"
#include "Delay.h"
unsigned char Second;
void main(){
LCD_Init();
DS1302_Init();
LCD_ShowString(1, 1,"RTC");
// 解除写入保护
DS1302_WriteByte(0x8E,0x00);
DS1302_WriteByte(0x80,0x03);
while(1){
Second = DS1302_ReadByte(0x81);
// 将BCD码变成十进制
LCD_ShowNum(2,1,Second/16*10+Second%16,3);
}
}
最后,我们把最终优化的代码写出来。(其实还可以优化,但是会使代码不容易理解,因为代码还是要使人看懂,所以就不优化了)
#include // 根据原理图,应该可以写出下面几个引脚定义
sbit DS1302_SCLK = P3^6;
sbit DS1302_IO = P3^4;
sbit DS1302_CE = P3^5;
//寄存器写入地址/指令定义
#define DS1302_SECOND0x80
#define DS1302_MINUTE0x82
#define DS1302_HOUR0x84
#define DS1302_DATE0x86
#define DS1302_MONTH0x88
#define DS1302_DAY0x8A
#define DS1302_YEAR0x8C
#define DS1302_WP0x8E//时间数组,索引0~6分别为年、月、日、时、分、秒、星期
unsigned char DS1302_Time[]={22,7,3,11,57,55,7};
// 初始化函数,因为单片机接电后默认为1
/**
* @briefDS1302初始化
* @param无
* @retval 无
*/
void DS1302_Init(void)
{
DS1302_CE = 0;
DS1302_SCLK = 0;
}// 写入函数
/**
* @briefDS1302写一个字节
* @paramCommand 命令字/地址
* @paramData 要写入的数据
* @retval 无
*/void DS1302_WriteByte(unsigned char Command, Data)
{
unsigned char i;
DS1302_CE = 1;
// 读取低位
for(i=0;
i<8;
i++){
DS1302_IO = Command&(0x01<
#ifndef __DS1302_H__
#define __DS1302_H__//外部可调用时间数组,索引0~6分别为年、月、日、时、分、秒、星期
extern unsigned char DS1302_Time[];
void DS1302_Init(void);
void DS1302_WriteByte(unsigned char Command,Data);
unsigned char DS1302_ReadByte(unsigned char Command);
void DS1302_SetTime(void);
void DS1302_ReadTime(void);
#endif
#include
#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);
//显示秒
}
}
运行结果如下所示:
最后呢,就是可调时钟的实现了,运用到了之前学的独立按键以及定时器,还有比较多的逻辑判断,不了解的同学也不要紧,我们就简单地把代码给出,同学们自己去看。主要的思想就是,不同的按键控制不同的功能。
#include
#include "LCD1602.h"
#include "DS1302.h"
#include "Key.h"
#include "Time0.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)//按键4按下
{
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;
//闪烁标志位取反
}
}
运行结果如下所示:
【51单片机笔记|(九)51单片机——DS1302时钟】好了,DS1302的知识点就介绍这么多了。
推荐阅读
- 嵌入式开发|STM32人脸识别系统设计(程序代码+论文)
- STM32的简单应用|DHT11 温湿度传感器
- 单片机|STM32/KEIL/MDK 查看 FLASH 和 RAM 使用情况
- 单片机软硬件开发|keil编译后ROM和RAM变化
- 嵌入式|陀螺仪数据处理(BMI088)
- 嵌入式|CAN通讯
- STM32的使用|STM32F4软件IIC的使用
- 嵌入式|STM32 0.96寸4针IOLED显示器驱动IIC(HAL库)
- stm32|STM32Cubemx——IIC驱动0.96寸OLED