写在前面:本节我们学习了51单片机的DS1302的相关内容,包括原理,电路图,相关寄存器的设置以及时序;完成了利用LCD1602与晶体管的结合,实现了时钟显示;如果只需代码,直接转至文末百度网盘;
在前面的定时器章节学习中,我们知道51单片机内部含有晶振,可以实现定时/计数功能,通过配置定时计数器的初值,可以实现时钟的功能;但是这种定时器计时存在一些缺陷,例如:精度不高、占用CPU时间,不能够掉电使用等等。本节我们学习51单片机开发的一个新模块--DS1302,利用DS1302实现多功能电子时钟,并将其显示在LCD1602以及晶体管表面。
本节我们要实现的功能是:
1、LCD1602显示日期,时间;格式为:XX-XX-XX(年月日);XX:XX:XX(时分秒);
2、数码管上显示电子时钟时分秒,格式为 XX-XX-XX(时分秒)。
DS1302是一种低功耗实时时钟芯片,实时时钟计算年、月、日、时、分、秒、星期,直到 2100 年,并有闰年调节功能。每月的天数和闰年的天数可自动调整。时钟操作可通过 AM/PM 指示决定采用 24 或 12 小时格式。DS1302 与 单片机之间能简单地采用同步串行的方式进行通信,仅需用到三根通信线:1、CE使能线;2、I/O 数据线;3、SCLK 串行时钟线;
上图为DS1302芯片的管脚定义,以及同CPU相连时的工作电路;其中管脚功能如下所示:
管脚 | 名称 | 功能 |
1 | VCC2 | 双供电配置中的主电源供应管脚(单片机供电); |
2 | X1 | DS1302 外部晶振引脚,通常需外接 32.768K 晶振; |
3 | X2 | DS1302 外部晶振引脚,通常需外接 32.768K 晶振; |
4 | GND | 电源地; |
5 | CE | 使能引脚,也是复位引脚,CE信号在读写时必须保持高电平; |
6 | I/O | 串行数据引脚,数据输出或者输入 |
7 | SCLK | 串行数据引脚 |
8 | VCC1 | 备用电源 |
在典型电路种,CPU与DS1302相连的为CE、SCLK以及I/O引脚;用于芯片的控制,X1\X2为外部 32.768kHz晶体.振荡电路工作时不需要任何外接的电阻或者电容。VCC1 连接备用电源,在VCC2连接主电源。 当 VCC2比 VCC1高 0.2V时,VCC2 给 DS1302供电.当 VCC1 比 VCC2高 时, VCC1给 DS1302供电.
DS1302 有一个控制寄存器、12 个日历、时钟寄存器和 31 个 RAM。
控制寄存器用于存放 DS1302 的控制命令字,DS1302 的CE引脚回到高电平后写入的第一个字节就为控制命令。它用于对 DS1302 读写过程进行控制,格式如下:
上图是 DS1302 的寄存器样式,我们看到:
1、最高位一直为1;
2、第六位中,1表示RAM,寻址内部存储器;
0表示CK,寻址内部寄存器;
3、第五至第一位为RAM/CK相关的地址;
4、最低位,高电平1表示 RD,即下一步操作将要“读”;低电平0表示 W,即 下一步操作将要“写”。
下图为相关寄存器的读写地址,控制命令;即通过红色框中的地址,控制相关寄存器的读与写;
下图为相关寄存器的位说明:
秒寄存器:低四位为秒的个位,高的次三位为秒的十位。最高位 CH 为 DS1302 的运行标志,当 CH=0 时,DS1302 内部时钟运行,反之 CH=1 时停止 ;
小时寄存器:时寄存器。最高位为 12/24 小时的格式选择位,该位为 1 时 表示 12 小时格式。当设置为 12 小时显示格式时,第 5 位的高电平表示下午 (PM);而当设置为 24 小时格式时,第 5 位位具体的时间数据;
写保护寄存器:当该寄存器最高位 WP 为 1 时,DS1302 只读不写,所以要在往 DS1302 写数据之前确保 WP 为0;
在日历/时钟寄存器中都是以 BCD 码存放数据,那么 BCD 码是 什么呢?BCD 码是通过 4 位二进制码来表示 1 位十进制中的 0~9 这 10 个数码。 如下所示:
所以,往相关寄存器中写入数据时先要转化为BCD码格式在进行写入;读取数据时,读到的是BCD码,使用时需要转化为相应的十进制或十六进制;
我们前面说过,CPU与DS1302相连的线有三根,分别为:CE、SCLK以及I/O引脚;那么如何通过这三根线实现两者之间的数据转换呢?这就不得不提及到DS1302的三个时序:
在控制指令字输入后的下一个 SCLK 时钟的上升沿时,数据被写入 DS1302,数据输入从低位(位 0)开始。同样,在紧跟 8 位的控制指令字后的下一个 SCLK 脉冲的下降沿读出 DS1302 的数据,读出数据时从低位 0 位到高位 7。完成单字节的读写;
1、CE:初始化后为低电平, 在整个读写器件,要保持高电平,一次字节读写完毕之后,在进行置低电平;
2、单字节写入:在CE为高电平后,SCLK会输出周期脉冲,每一个上升沿,IO线的数据就会进入控制寄存器,当控制寄存器配置完成(为写入数据,并且地址已给),紧接着的脉冲IO线的数据就会在上升沿进入对应地址的寄存器;
3、单字节读出:在CE为高电平后,SCLK会输出周期脉冲,每一个上升沿,IO线的数据就会进入控制寄存器,当控制寄存器配置完成(为读出数据,并且地址已给),紧接着对应地址的寄存器的数据就会在下升沿进入IO线;
注: 要记得在操作 DS1302 之前关闭写保护;不然指令是无法进入控制寄存器的;
本次所涉及的硬件如下:
1、DS1302;2、LCD1602;3、动态数码管;
开发板上 DS1302 时钟模块电路图为:
从上图中可知,DS1302 芯片的控制管脚接至单片机 P3.4-P3.6;X1、X2 管脚处外接了一个 32.768KHZ 晶振,为时钟运行提供一个稳定的时钟频率,C2 和 C3 为旁路电容,目的是消除晶振起振时产生的电感干扰。对于本开发 板无外接备用电池,如果需要可自行将外部备用电源接入第 8 脚 VCC1。
开发板上LCD1602 模块电路图为:
LCD1602的管脚说明以及相关的使用方法在之前的博客中已经说明,如果需要学习了解的可以看看下面这篇博客!
CSDN
开发板上晶体数码管模块电路图为:
晶体数码管的管脚说明以及相关的使用方法在之前的博客中已经说明,如果需要学习了解的可以看看下面这篇博客!
CSDN
三者在开发板上的位置:
功能描述:LCD1602显示日期,时间;格式为:XX-XX-XX(年月日);XX:XX:XX(时分秒);
源码
main.c
#include
#include "lcd1602.h"//包含LCD1602头文件
#include "DS1302.h"//包含DS1302头文件
void main ()//定义主函数
{
LCD_Init(); //LCD1602初始化
DS1302_Init();//DS1302初始化
LCD_ShowString(1,1," - - ");//设置年月日格式
LCD_ShowString(2,1," : : ");//设置时分秒格式
DS1302_SetTime();//设置时间,通过数组进行设置
while(1)
{
DS1302_ReadTime();//读取内部时间
LCD_ShowNumber(1,1,DS1302_Time[0],2);//显示年
LCD_ShowNumber(1,4,DS1302_Time[1],2);//显示月
LCD_ShowNumber(1,7,DS1302_Time[2],2);//显示日
LCD_ShowNumber(2,1,DS1302_Time[3],2);//显示时
LCD_ShowNumber(2,4,DS1302_Time[4],2);//显示分
LCD_ShowNumber(2,7,DS1302_Time[5],2);//显示秒
LCD_ShowNumber(2,13,DS1302_Time[6], 1);//显示星期
}
}
DS1302.h
#include "DS1302.c"
extern unsigned char DS1302_Time[];//声明设置时间的数组
void DS1302_Init();//声明初始化函数
void DS1302_WriteBety(unsigned char command,Data);//声明单字节写入函数
unsigned char DS1302_ReadBety(unsigned char command);//声明单字节读出函数
void DS1302_SetTime();//声明设置内部时间函数
void DS1302_ReadTime();//声明读取内部时间函数
DS1302.c
#include
//引脚定义;
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
//定义数组,用于存放设定的时间,年月日时分秒星期
unsigned char DS1302_Time[]={23,10, 28,19,00,59,6};//顺序:年月日时分秒星期
/**
*@breaf DS1302初始化
*@param无
*@retval无
*/
void DS1302_Init()
{
DS1302_CE=0;//将使能位置0,低电平;
DS1302_SCLK=0;//将时钟位置0,低电平;
}
/**
*@breaf DS1302单字节写入函数
*@param command:写入控制指令的指令,包含要写入寄存器的地址;
*@param Data:将要写入的数据内容;
*@retval 无
*/
void DS1302_WriteBety(unsigned char command,Data)
{
unsigned char i;
DS1302_CE=1;//使能位置高电平;
for(i=0;i<8;i++)//控制寄存器数据需要通过IO线一个一个写入控制寄存器;低位先写入
{
DS1302_IO=command&(0x01<
LCD1602.h
#include "LCD1602.c"
void LCD_Init();//声明初始化函数
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);//声明显示字符串函数
void LCD_ShowNumber(unsigned char Line,unsigned char Column,unsigned int number,unsigned char length);//声明显示数字函数
LCD1602.c
#include
//引脚配置:
sbit LCD_RS=P2^6; // RS引脚为数据/指令选择 1为数据,0为指令
sbit LCD_RW=P2^5; // RW引脚为读/写选择 1为读,0为写
sbit LCD_EN=P2^7; // EN引脚为使能 1为数据有效,下降沿执行命令
#define LCD_DataPort P0 //定义P0引脚为数据端口
//延迟函数的定义;LCD1602延时函数,12MHz调用可延时xms;
void LCD_Delay(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
//写指令函数定义: LCD1602写指令函数
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;//选择为指令,1为数据,0为指令
LCD_RW=0;//选择为写, 1为读,0为写
LCD_DataPort=Command;//写入指令的内容
LCD_EN=1; //使能脚E先上升沿写入
LCD_Delay(1);
LCD_EN=0; //使能脚E后负跳变完成写入
LCD_Delay(1);
}
// 写数据函数定义: LCD1602写数据函数
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1; //选择为数据,1为数据,0为指令
LCD_RW=0; //选择为写, 1为读,0为写
LCD_DataPort=Data;//写入指数据的内容
LCD_EN=1; //使能脚E先上升沿写入
LCD_Delay(1);
LCD_EN=0; //使能脚E后负跳变完成写入
LCD_Delay(1);
}
//初始化函数定义: LCD1602屏幕初始化
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
//LCD1602 进行清屏
void LCD_clear()
{
LCD_WriteCommand(0x01);
}
//设置光标位置
void LCD_SetCursor(unsigned char Line,unsigned char Column)//(行数1-2,列数1-16)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
// 字符串函数定义: LCD1602显示字符串
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)//(行数1-2,列数1-16)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
// 字符数字函数定义: LCD1602显示数字
int Pow(int x,int y)
{
unsigned char i;
int result = 1;
for(i = 0; i < y; i++)
{
result *= x;
}
return result;
}
void LCD_ShowNumber(unsigned char Line,unsigned char Column,unsigned int number,unsigned char length)
{
unsigned char i;
unsigned char temp;
LCD_SetCursor(Line,Column);
for(i =length ; i > 0 ; i--)
{
temp = number/Pow(10,i-1)%10 + '0'; //循环将每一位都提取出来并转换为字符
LCD_WriteData(temp);
}
}
实验现象
DS1302利用LCD1602显示时钟
功能说明:数码管上显示电子时钟时分秒,格式为 XX-XX-XX(时分秒);
源码
main.c
#include
#include "DS1302.h"
#include
unsigned char time[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//定义数码管上0-f 16位数字的段选码
unsigned char time_buf[7]={0};//定义数码管对应位
/**
*@breaf 延时函数/@11.0592MHz 1ms为单位
*@para t 延时时间设置
*@retval无
*/
void Delay1ms(unsigned char t) //@11.0592MHz
{
unsigned char i, j;
while(t--)
{
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
/**
*@breaf 数码管显示函数
*@para 显示的段选数据 数组
*@retval无
*/
void smg_display(unsigned char dat[])
{
unsigned char i=0;
for(i=0;i<8;i++)//扫描
{
switch(i)//位选
{
case 0: P2_4=1; P2_3=1; P2_2=1;break;
case 1: P2_4=1; P2_3=1; P2_2=0;break;
case 2: P2_4=1; P2_3=0; P2_2=1;break;
case 3: P2_4=1; P2_3=0; P2_2=0;break;
case 4: P2_4=0; P2_3=1; P2_2=1;break;
case 5: P2_4=0; P2_3=1; P2_2=0;break;
case 6: P2_4=0; P2_3=0; P2_2=1;break;
case 7: P2_4=0; P2_3=0; P2_2=0;break;
}
P0=dat[i];//传送段选数据
Delay1ms(1) ;//延时一段时间,等待显示稳定
P0=0x00;//消音
}
}
void main ()
{
DS1302_Init();//DS1302初始化
DS1302_SetTime();// DS1302设置时间
while(1)
{
DS1302_ReadTime();//DS1302读取时间,读取值为BCD码
time_buf[0]= time[DS1302_Time[3]/16];//将BCD码转为十进制,并且在数组中找到对应十进制的段选码;小时第一位;
time_buf[1]= time[DS1302_Time[3]&0x0f];//将BCD码转为十进制,并且在数组中找到对应十进制的段选码;小时的第二位;
time_buf[2]=0x40;//格式控制-;
time_buf[3]= time[DS1302_Time[4]/16];//将BCD码转为十进制,并且在数组中找到对应十进制的段选码;分钟的第一位;
time_buf[4]= time[DS1302_Time[4]&0x0f];//将BCD码转为十进制,并且在数组中找到对应十进制的段选码;分钟的第二位;
time_buf[5]=0x40;//格式控制-;
time_buf[6]= time[DS1302_Time[5]/16];//将BCD码转为十进制,并且在数组中找到对应十进制的段选码;秒的第一位;
time_buf[7]= time[DS1302_Time[5]&0x0f];//将BCD码转为十进制,并且在数组中找到对应十进制的段选码;秒的第二位;
smg_display(time_buf);//将时间信息传递给数码管显示函数
}
}
DS1302.c
#include
//引脚定义;
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
//定义数组,用于存放设定的时间,年月日时分秒星期
unsigned char DS1302_Time[]={23,10,28,19,01,55,6};//顺序:年月日时分秒星期
/**
*@breaf DS1302初始化
*@param无
*@retval无
*/
void DS1302_Init()
{
DS1302_CE = 0; // 将使能位置0,低电平;
DS1302_SCLK=0;//将时钟位置0,低电平;
}
/**
*@breaf DS1302单字节写入函数
*@param command:写入控制指令的指令,包含要写入寄存器的地址;
*@param Data:将要写入的数据内容;
*@retval 无
*/
void DS1302_WriteBety(unsigned char command, Data)
{
unsigned char i;
DS1302_CE = 1;//使能位置高电平;
for (i = 0; i<8; i++)//控制寄存器数据需要通过IO线一个一个写入控制寄存器;低位先写入
{
DS1302_IO = command&(0x01 << i);//相当于把第1--7位置0,只留第0位,如果第0位是0,则为0;反之则为1;
DS1302_SCLK = 1;
DS1302_SCLK = 0;
}
for (i = 0; i<8; i++)//数据写入
{
DS1302_IO = Data&(0x01 << i);//相当于把第1--7位置0,只留第0位,如果第0位是0,则为0;反之则为1;
DS1302_SCLK = 1;
DS1302_SCLK = 0;
}
DS1302_CE = 0;
}
/**
*@breaf DS1302单字节读出函数
*@param command:写入控制指令的指令,包含要读出寄存器的地址;
*@retval Data:读出的数据;
*/
unsigned char DS1302_ReadBety(unsigned char command)
{
unsigned i, Data = 0X00;
command |= 0X01;//写入指令与读出指令只在最后一位相差1,故在此利用或运算消除;
DS1302_CE = 1;//使能位置高电平;
for (i = 0; i<8; i++)//控制寄存器数据需要通过IO线一个一个写入控制寄存器;低位先写入
{
DS1302_IO = command&(0x01 << i);//相当于把第1--7位置0,只留第0位,如果第0位是0,则为0;反之则为1;
DS1302_SCLK = 0;
DS1302_SCLK = 1;
}
DS1302_IO = 0;
for (i = 0; i<8; i++)//数据读出
{
DS1302_SCLK = 1;
DS1302_SCLK = 0;
if (DS1302_IO)
{
Data |= (0X01 << i);
}
}
DS1302_CE = 0;
return Data;
}
/**
*@breaf 向DS1302内设定时间
*@param无
*@retval无
*/
void DS1302_SetTime()
{
DS1302_WriteBety(DS1302_WP, 0x00);//操作 DS1302 之前,关闭写保护,不然指令无法进入控制寄存器;
DS1302_WriteBety(DS1302_YEAR, DS1302_Time[0] / 10 * 16 + DS1302_Time[0] % 10);//写入年,并将10进制转化BCD码;
DS1302_WriteBety(DS1302_MONTH, DS1302_Time[1] / 10 * 16 + DS1302_Time[1] % 10);//写入月,并将10进制转化BCD码;
DS1302_WriteBety(DS1302_DATE, DS1302_Time[2] / 10 * 16 + DS1302_Time[2] % 10);//写入日,并将10进制转化BCD码;
DS1302_WriteBety(DS1302_HOUR, DS1302_Time[3] / 10 * 16 + DS1302_Time[3] % 10);//写入时,并将10进制转化BCD码;
DS1302_WriteBety(DS1302_MINUTE, DS1302_Time[4] / 10 * 16 + DS1302_Time[4] % 10);//写入分,并将10进制转化BCD码;
DS1302_WriteBety(DS1302_SECOND, DS1302_Time[5] / 10 * 16 + DS1302_Time[5] % 10);//写入秒,并将10进制转化BCD码;
DS1302_WriteBety(DS1302_DAY, DS1302_Time[6] / 10 * 16 + DS1302_Time[6] % 10);//写入星期,并将10进制转化BCD码;
DS1302_WriteBety(DS1302_WP, 0x80);//写入结束,开启写保护;
}
/**
*@breaf 读取DS1302内时间
*@param无
*@retval无
*/void DS1302_ReadTime()
{
DS1302_Time[3] = DS1302_ReadBety(DS1302_HOUR);//读取小时BCD码;
DS1302_Time[4]=DS1302_ReadBety(DS1302_MINUTE);//读取分钟BCD码;
DS1302_Time[5]=DS1302_ReadBety(DS1302_SECOND);//读取秒BCD码;
}
DS1302.h
#include "DS1302.c"
extern unsigned char DS1302_Time[];//声明设置时间的数组
void DS1302_Init();//声明初始化函数
void DS1302_WriteBety(unsigned char command, Data);//声明单字节写入函数
unsigned char DS1302_ReadBety(unsigned char command);//声明单字节读出函数
void DS1302_SetTime();//声明设置内部时间函数
void DS1302_ReadTime();//声明读取内部时间函数
实验现象:
DS1302利用晶体管显示时间
链接:https://pan.baidu.com/s/1YF6UYMKj4Dn7X2vKrrQZ8A
提取码:1022
总结:本节我们学习了51单片机的DS1302的相关内容,包括原理,电路图,相关寄存器的设置以及时序;完成了利用LCD1602与晶体管的结合,实现了时钟显示;读者看完后一定一定要多加练习,才能更好的掌握;
创作不易,还请大家多多点赞支持!!!