(51单片机)LCD显示日期时间时钟(DS1302时钟模块教学)(LCD1602教程)

发布于:2025-04-16 ⋅ 阅读:(21) ⋅ 点赞:(0)

目录

 源代码

 main.c

LCD1602.c

LCD1602.h

DS1302.c

DS1302.h

 代码解析与教程:

 LCD1602模块

 DS1302模块

效果视频: 

 源代码

如上图将5个文放在Keli5 中即可,然后烧录在单片机中就行了

烧录软件用的是STC-ISP,不知道怎么安装的可以去看江科大的视频:

【51单片机入门教程-2020版 程序全程纯手打 从零开始入门】https://www.bilibili.com/video/BV1Mb411e7re?p=2&vd_source=ada7b122ae16cc583b4add52ad89fd5e

源代码:

头文件要记得宏定义和重定义,避免重复调用:

#ifndef _Timer0_h_//名字根据文件名定义即可
#define _Timer0_h_

//声明函数……

#endif

 main.c

#include <STC89C5xRC.H>
#include "LCD1602.h"
#include "DS1302.h"

//主程序
void main(){
	LCD_Init();//LCD1602初始化
	DS1302_Init();//DS1302初始化
	LCD_ShowString(1,1,"  -  -  ");//LCD显示年月日间隔符号
	LCD_ShowString(2,1,"  :  :  ");//LCD显示时分秒间隔符号
	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);//显示秒
	}
}

LCD1602.c

#include <STC89C5xRC.H>


//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//函数定义:
/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()		//@11.0592MHz
{
	unsigned char i, j;

	i = 11;
	j = 190;
	do
	{
		while (--j);
	} while (--i);
}


/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
	}
}

LCD1602.h

#ifndef __LCD1602_H__
#define __LCD1602_H__

//用户调用函数:
void LCD_Init();//初始化
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);//显示单个字符
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);//显示字符串
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);//显示数字
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);//显示带符号数字
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);//显示十进制数字
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);//显示二进制数字

#endif

DS1302.c

#include <STC89C5xRC.H>

//初始化定义引脚
sbit DS1302_SCLK=P3^6;
sbit DS1302_IO=P3^4;
sbit DS1302_CE=P3^5;

//宏定义日期,和写入保护地址
#define DS1302_S 0x80//秒
#define DS1302_M 0x82//分
#define DS1302_H 0x84//时
#define DS1302_D 0x86//日
#define DS1302_MON 0x88//月
#define DS1302_DAY 0x8A//星期
#define DS1302_Y 0x8C//年
#define DS1302_WP 0x8E//写入保护

//初始化定义日期时间数组
//对应年月 日 时 分 秒 星期
unsigned char DS1302_Time[]={25,4,15,20,59,55,2};

//十进制转BCD进制函数
unsigned char HEX_BCD(unsigned char n){
	return n/10*16+n%10;
}
//BCD进制转十进制函数
unsigned char BCD_HEX(unsigned char n){
	return n/16*10+n%16;
}

//DS1302的初始化函数
void DS1302_Init(){
	DS1302_CE=0;
	DS1302_SCLK=0;
}

//DS1302的写入函数
//第一个参数Command是命令字,第二个参数Data是数据
void DS1302_WriteByte(unsigned char Command,Data){
	unsigned char i;//循环,移位计数
	DS1302_CE=1;//改变为高电平,写入
	for(i=0;i<8;i++){//循环8次,写入寄存器
		DS1302_IO=Command&(0x01<<i);//低位在前,获取Command的最低位,左移获得当前位
		DS1302_SCLK=1;//改变高电位,写入寄存器
		DS1302_SCLK=0;//改变低电位,准备写入寄存器
	}
	for(i=0;i<8;i++){//循环8次,写8个数据
		DS1302_IO=Data&(0x01<<i);//低位在前,获取Command的最低位,左移获得当前位
		DS1302_SCLK=1;//改变高电位,写入数据
		DS1302_SCLK=0;//改变低电位,准备写入数据
	}
	DS1302_CE=0;//恢复,初始化
}

//DS1302的读入函数
//第一个参数Command是命令字
unsigned char DS1302_ReadByte(unsigned char Command){
	unsigned char i,Data=0x00;//循环,移位计数;数据
	Command|=0x01;//清空,使最后一位为1;
	DS1302_CE=1;//改变为高电平,写入
	for(i=0;i<8;i++){//循环8次,读寄存器
		DS1302_IO=Command&(0x01<<i);//低位在前,获取Command的最低位,左移获得当前位
		DS1302_SCLK=0;//改变低电位,准备读寄存器
		DS1302_SCLK=1;//改变高电位,读寄存器
	}
	for(i=0;i<8;i++){
		DS1302_SCLK=1;//改变高电位,读数据
		DS1302_SCLK=0;//改变低电位,准备读数据
		if(DS1302_IO==1){Data=Data|(0x01<<i);}//低位在前,初始化Data的最低位为1,左移初始化当前位为1
	}
	DS1302_CE=0;//恢复,初始化
	DS1302_IO=0;//恢复,初始化
	return Data;//返回值为当前数据
}

//设定日期时间函数
void DS1302_SetTime(){
	//数据必须为BCD,因此转换
	DS1302_WriteByte(DS1302_WP,0x00);//关闭写入保护
	DS1302_WriteByte(DS1302_Y,HEX_BCD(DS1302_Time[0]));//写入年
	DS1302_WriteByte(DS1302_MON,HEX_BCD(DS1302_Time[1]));//写入月
	DS1302_WriteByte(DS1302_D,HEX_BCD(DS1302_Time[2]));//写入日
	DS1302_WriteByte(DS1302_H,HEX_BCD(DS1302_Time[3]));//写入时
	DS1302_WriteByte(DS1302_M,HEX_BCD(DS1302_Time[4]));//写入分
	DS1302_WriteByte(DS1302_S,HEX_BCD(DS1302_Time[5]));//写入秒
	DS1302_WriteByte(DS1302_DAY,HEX_BCD(DS1302_Time[6]));//写入星期
	DS1302_WriteByte(DS1302_WP,0x80);//打开写入保护
}

//改变定义并读取日期时间函数
void DS1302_ReadTime(){
	//数据必须为十进制,因此转换
	unsigned char Temp;//定义临时变量
	Temp=DS1302_ReadByte(DS1302_Y);
	DS1302_Time[0]=BCD_HEX(Temp);//读年
	Temp=DS1302_ReadByte(DS1302_MON);
	DS1302_Time[1]=BCD_HEX(Temp);//读月
	Temp=DS1302_ReadByte(DS1302_D);
	DS1302_Time[2]=BCD_HEX(Temp);//读日
	Temp=DS1302_ReadByte(DS1302_H);
	DS1302_Time[3]=BCD_HEX(Temp);//读时
	Temp=DS1302_ReadByte(DS1302_M);
	DS1302_Time[4]=BCD_HEX(Temp);//读分
	Temp=DS1302_ReadByte(DS1302_S);
	DS1302_Time[5]=BCD_HEX(Temp);//读秒
	Temp=DS1302_ReadByte(DS1302_DAY);
	DS1302_Time[6]=BCD_HEX(Temp);//读日期
}

DS1302.h

#ifndef __DS1302_H__
#define __DS1302_H__

//用户调用函数:
extern unsigned char DS1302_Time[];//声明数组
unsigned char DS1302_ReadByte(unsigned char Command);//声明读数据函数
void DS1302_WriteByte(unsigned char Command,Data);//声明写数据函数
void DS1302_Init();//声明初始化函数
void DS1302_ReadTime();//声明读日期时间函数
void DS1302_SetTime();//声明设置日期时间函数
unsigned char BCD_HEX(unsigned char n);//声明BCD转十进制
unsigned char HEX_BCD(unsigned char n);//声明十进制转BCD
#endif

 代码解析与教程:

 LCD1602模块
  • 包含源代码与头文件,不需要知道怎么实现的会用即可,后续使用,直接将头文件和源代码拿过来用即可;使用格式:(参考江科大的视频素材)

  • LCD1602相关重要知识:
    • LCD1602有两上下两行显示屏,每行各有16个小显示屏,如上图中的LCD_ShowString(1,3,"Hello"),第一个参数是第一行还是第二行,第2个参数是对应第几行的第几个小显示屏,最后一个是输出的东西,同理,到LCD_ShowNum(1,9,123,3)里,前三个和前面一样,最后一个参数是显示的位数,不够就在前面补0,例如输入1,参数为4,显示就是0001,输入23,参数为3,显示就是023

  • 上图是LCD1602的开发原理模块图,由图可知,P10-P17是控制显示屏的重点;像矩阵那样,S1,由P17和P13控制,同理,其他按键一样,由于开发板限制,我们一般使用列遍历来控制按键,例如:S1,S5,S9,S13他们四个共用P13,所以将其为1列,将P13设定为1,P17设定为0,S1就会亮,其他按键同理。
 DS1302模块
  • 包含源代码与头文件,需要知道怎么实现的,会用,后续使用,直接将头文件和源代码拿过来用即可;

  • 先看原理图:

  • 图中可以知道SCLK,IO,CE的地址,因此,在代码中定义地址:
//初始化定义引脚
sbit DS1302_SCLK=P3^6;
sbit DS1302_IO=P3^4;
sbit DS1302_CE=P3^5;

上面是读,下面是写,定义好地址后;如图,只有在CE=1(高电平)时,操作才能实现;SCLK上升就是从0变1,下降就是1变0;IO前半部分就是寄存器(不需要怎么理解),后半部分是数据。

        先看下面的读操作(相对简单):首先CE=1;SCLK从头上升下降到尾,全程写入寄存器和数据;IO的R/W=0,就是写;最后一个固定为1;
        再看上面的读操作(相对简单):首先CE=1;SCLK从头上升下降到中间(读寄存器),然后下降上升开始转换成读数据;IO的R/W=1,就是读;最后一个固定为1;



RTC就是IO总体部分,序号1是Read读的地址,序号2是Write写的地址,第一列是秒,第二列是分,以此类推看图中英文确定作用,WP是写入保护;Address/Command Byte就是IO元素部分,在读写操作时,低位在前。这部分又叫寄存器地址,或者命令符,来看相关代码:
//DS1302的写入函数
//第一个参数Command是命令字,第二个参数Data是数据
void DS1302_WriteByte(unsigned char Command,Data){
	unsigned char i;//循环,移位计数
	DS1302_CE=1;//改变为高电平,写入
	for(i=0;i<8;i++){//循环8次,写入寄存器
		DS1302_IO=Command&(0x01<<i);//低位在前,获取Command的最低位,左移获得当前位
		DS1302_SCLK=1;//改变高电位,写入寄存器
		DS1302_SCLK=0;//改变低电位,准备写入寄存器
	}
	for(i=0;i<8;i++){//循环8次,写8个数据
		DS1302_IO=Data&(0x01<<i);//低位在前,获取Command的最低位,左移获得当前位
		DS1302_SCLK=1;//改变高电位,写入数据
		DS1302_SCLK=0;//改变低电位,准备写入数据
	}
	DS1302_CE=0;//恢复,初始化
}

//DS1302的读入函数
//第一个参数Command是命令字
unsigned char DS1302_ReadByte(unsigned char Command){
	unsigned char i,Data=0x00;//循环,移位计数;数据
	Command|=0x01;//清空,使最后一位为1;
	DS1302_CE=1;//改变为高电平,写入
	for(i=0;i<8;i++){//循环8次,读寄存器
		DS1302_IO=Command&(0x01<<i);//低位在前,获取Command的最低位,左移获得当前位
		DS1302_SCLK=0;//改变低电位,准备读寄存器
		DS1302_SCLK=1;//改变高电位,读寄存器
	}
	for(i=0;i<8;i++){
		DS1302_SCLK=1;//改变高电位,读数据
		DS1302_SCLK=0;//改变低电位,准备读数据
		if(DS1302_IO==1){Data=Data|(0x01<<i);}//低位在前,初始化Data的最低位为1,左移初始化当前位为1
	}
	DS1302_CE=0;//恢复,初始化
	DS1302_IO=0;//恢复,初始化
	return Data;//返回值为当前数据
}

由上可知,SCLK每次变化电平,就会进行数据的读写操作,但由于数据是先进先出的,所以低位在前。

  • 宏定义日期时间地址,提高代码可读性,便于修改和维护,代码:
//宏定义日期,和写入保护地址
#define DS1302_S 0x80//秒
#define DS1302_M 0x82//分
#define DS1302_H 0x84//时
#define DS1302_D 0x86//日
#define DS1302_MON 0x88//月
#define DS1302_DAY 0x8A//星期
#define DS1302_Y 0x8C//年
#define DS1302_WP 0x8E//写入保护
  • 其余代码,注释很详细:
//初始化定义日期时间数组
//对应年月 日 时 分 秒 星期
unsigned char DS1302_Time[]={25,4,15,20,59,55,2};

//十进制转BCD码函数
unsigned char HEX_BCD(unsigned char n){
	return n/10*16+n%10;
}
//BCD码转十进制函数
unsigned char BCD_HEX(unsigned char n){
	return n/16*10+n%16;
}

//DS1302的初始化函数
void DS1302_Init(){
	DS1302_CE=0;
	DS1302_SCLK=0;
}

数组是初始化日期时间;

转换进制函数,以及BCD码是什么:

  • 配合上述所有知识,数组,读写操作,进制转换,宏定义等知识点;设置日期时间,改变定义读日期时间代码:
//设定日期时间函数
void DS1302_SetTime(){
	//数据必须为BCD,因此转换
	DS1302_WriteByte(DS1302_WP,0x00);//关闭写入保护
	DS1302_WriteByte(DS1302_Y,HEX_BCD(DS1302_Time[0]));//写入年
	DS1302_WriteByte(DS1302_MON,HEX_BCD(DS1302_Time[1]));//写入月
	DS1302_WriteByte(DS1302_D,HEX_BCD(DS1302_Time[2]));//写入日
	DS1302_WriteByte(DS1302_H,HEX_BCD(DS1302_Time[3]));//写入时
	DS1302_WriteByte(DS1302_M,HEX_BCD(DS1302_Time[4]));//写入分
	DS1302_WriteByte(DS1302_S,HEX_BCD(DS1302_Time[5]));//写入秒
	DS1302_WriteByte(DS1302_DAY,HEX_BCD(DS1302_Time[6]));//写入星期
	DS1302_WriteByte(DS1302_WP,0x80);//打开写入保护
}

//改变定义并读取日期时间函数
void DS1302_ReadTime(){
	//数组内容为十进制,因此转换
	unsigned char Temp;//定义临时变量
	Temp=DS1302_ReadByte(DS1302_Y);
	DS1302_Time[0]=BCD_HEX(Temp);//读年
	Temp=DS1302_ReadByte(DS1302_MON);
	DS1302_Time[1]=BCD_HEX(Temp);//读月
	Temp=DS1302_ReadByte(DS1302_D);
	DS1302_Time[2]=BCD_HEX(Temp);//读日
	Temp=DS1302_ReadByte(DS1302_H);
	DS1302_Time[3]=BCD_HEX(Temp);//读时
	Temp=DS1302_ReadByte(DS1302_M);
	DS1302_Time[4]=BCD_HEX(Temp);//读分
	Temp=DS1302_ReadByte(DS1302_S);
	DS1302_Time[5]=BCD_HEX(Temp);//读秒
	Temp=DS1302_ReadByte(DS1302_DAY);
	DS1302_Time[6]=BCD_HEX(Temp);//读日期
}

效果视频: 

DS1302时钟

 注:该代码是本人自己所写,可能不够好,不够简便,欢迎大家指出我的不足之处。如果遇见看不懂的地方,可以在评论区打出来,进行讨论,或者联系我。上述内容全是我自己理解的,如果你有别的想法,或者认为我的理解不对,欢迎指出!!!如果可以,可以点一个免费的赞支持一下吗?谢谢各位彦祖亦菲!!!!!