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