STC12C5A60S2软件模式SPI读取DS1302时钟实时显示在1602

SPI总线的概念

SPI接口全称“serial peripheral Interface”,意为串行外围接口
SPI接口是在CPU外围低速之间进行数据传输,在主器件的移位脉冲下,数据按位传输,高位在前,低位在后,为全双工通信
SPI以主从方式工作,这种模式通常有一个主器件和一个或多个器件,一般包含4根线。

MOSI——主器件数据输出,从器件数据输入
MISO——主器件数据输入,从器件数据输出
SCLK——时钟信号,由主器件产生
CS/SS——从器件使能信号,由主器件控制
请添加图片描述

在SPI应用中也有三线制的SPI,实现半双工通信
请添加图片描述

SPI总线接口连接
请添加图片描述
SPI协议的详细介绍我这里有野火STM32在SPI部分帮忙整理的资料,可以看一下,写得很详细。
链接:https://share.weiyun.com/m4fl87Up
密码:pkjadc

什么是实时时钟

RTC:Real Time Clock

我们知道单片机内部有定时器,但定时器只能表示一个时间段,这不是唯一的时间。比如我们应用单片机写一个交通灯;当我们应用单片机来表示一个时间点,这是唯一的,表示单片机需要做一些必须到某一个时间才能做的事情,所以需要用到RTC,如应用单片机写一个定时炸弹,这时我们就需要实时的时钟计时。使用实时的实时计时还有一个好处就是能够减少时间的误差。
关于RTC,在钟表、电子日历、手表、智能手环等都有实际应用。
在STM32F103系列的MCU中,已集成有RTC外设,是一个掉电后还能继续运行的定时器,从定时器角度来说,相当于定时器外设。它十分简单,只有很纯粹的中断和触发功能。

DS1302介绍

请添加图片描述

用一句话总结DS1302:内部存着一个时间点信息(年月日时分星期几)可以读写
DS1302内部还有31*8RAM用于临时存储数据;
工作电压范围在2.0V-5.5V之间。当工作在低功耗模式下(2.0V)时,消耗不少于300nA。;
DS1302传输时钟和内存数据有单字节或多字节(突发模式);
有8-pin DIP或8-pin SOIC封装可选;3线SPI通信接口,兼容TTL(5V)电平,兼容DS1202。
请添加图片描述

管脚说明:
Vcc2:双电源配置中的主电源
X1、X2:与标准的32.768KHZ晶振相连
GND:电源地
CE:使能,CE信号在读写必须保持高电平
I/O:数据
SCLK:时钟
Vcc1:低功率工作单电源和低功率备用电源

请添加图片描述
请添加图片描述
DS1302和单片机的IO连接只需3根线:CE数据传输使能、SCLK串行时钟输入端、I/O串行数据端,同时3个引脚分别接4.7K上拉电阻。

关于钟表里的晶振为什么是32.768KHZ的晶振?
钟表运行需要需要一个精准的节拍器,机械钟表依靠的是机械摆,电阻钟表依靠的就是晶振了。晶振能够提供精准稳定的节拍,常用的晶振是32.768KHZ晶振,也就是每秒跳动32768次,对于二进制运行的电子表、计算机来说,32768是一个整数。
对于二进制运行的电子钟表里,有一个16位的计数器,十进制的32768刚好是16位二进制的“1000 0000 0000 0000”。钟表里的晶振每秒跳动32768次就是每秒加32768个1,这样在16位加1速度就被减慢到秒1次。
用32768晶振还有一个好处,晶振的误差体现到秒是上面只有1/32768。
那么为什么不选择更加高频或者低频的晶振呢?
综合成本考虑,32.768KHZ晶振已经是属于低频产品,再低体积会增大,和它搭配的负载电容也会增大,不适合小型化的钟表;如果选择更高频率的晶振对于数字电路来说,工作频率越高,就意味着更高的电力消耗,32768这个频率已经能满足精度和成本要求,也能满足低功耗要求,所有没必要使用到更高的频率。
在数字电路中,晶振决定电路的生与死、好与坏,也会影响到系统 稳定性。

DS1302寄存器

请添加图片描述
请添加图片描述

秒寄存器(81h、80h):bit7定义为时钟暂停标志(CH),当该位置为1时,时钟震荡停止,DS1302处于低功耗状态;当置0时,时钟开始运行。
小时寄存器(85h、84h):bit7定义是运行于12小时模式/24小时模式,当为1时选择12小时模式,此时bit5为AM/PM位,在24小时模式时为小时数据位。
控制寄存器(8Fh、8Eh):bit7是写保护位(WP)其他7位均为0.在任何对时钟或者RAM读写操作之前,WP必须为0.当WP为1时,不能对任何时钟日历寄存器或RAM进行读写操作。
突发模式:突发模式是指一次传输多个字节的时钟信号或RAM数据。

Ds1302附加31字节静态RAM

请添加图片描述

时序图和控制指令

请添加图片描述
命令字是启动数据传输的控制指令
bit7必须使逻辑1,如果是0则禁止对DS1302写入
bit6在逻辑0时规定为时钟/日历日历数据,为1时规定为RAM数据
bit1-bit5表示输入/输出指定的寄存器
bit0在逻辑0时为写操作,逻辑1时为读操作

请添加图片描述

参考例程

/**********************************************************
 *功能:STC12C5A60S2单片机在LCD1602液晶显示DS1302时钟日历
 *备注:按照DS1302接口定义用杜邦线连接
 *版本:V1.0开源
 *
 *
 *作者:小途
 *编写时间:2022年9月16日
 ***********************************************************/

#include <STC12C5A60S2.h>
#include <intrins.h>

#define uchar unsigned char
#define uint unsigned int

//宏定义
#define MAIN_Fosc		11059200UL	//宏定义主时钟HZ
#define LCD1602_DB P0	//LCD1602数据总线
#define DS1302_W_ADDR 0x80  //写时钟日历寄存器起始地址
#define DS1302_R_ADDR 0x81	//读时钟日历寄存器起始地址

//时钟日历暂存数组,秒、分、时、日、月、周、年 初值为= 22年9月15日 周四 23:59:00
uchar TimeData[7]={00, 59, 23, 15, 9, 4, 22};

//LCD1602端口定义
sbit LCD1602_RS = P3^5;	 //RS端
sbit LCD1602_RW = P3^6;	 //RW端
sbit LCD1602_EN = P3^4;	 //EN端

//DS1302端口定义
sbit TSCLK = P1^0; //时钟
sbit TIO = P1^1;   //数据
sbit TRST = P1^2;  //使能

/*=================================================
*函数名称:Delay_Ms
*函数功能:STC12自适应主时钟毫秒级延时函数
*说明:需定义MAIN_Fosc(晶振主频单位HZ)
*输入:ms:要延时的毫秒
=================================================*/
void Delay_Ms(uint ms)
{
    uint i;
	 do{
	      i = MAIN_Fosc / 96000; 
		  while(--i);   //96T per loop
     }while(--ms);
}

/*=================================================
*函数名称:DS1302_W_Byte
*函数功能:DS1302写一字节数据
*输入:dat:要写入的数据
=================================================*/
void DS1302_W_Byte(uchar dat)
{
	uchar i;
	for(i = 0; i < 8; i++) //每次写1bit,写8次
	{
		TSCLK = 0;		   //拉低时钟总线
		TIO = dat & 0x01;  //从一字节最低位开始写
		TSCLK = 1;		   //拉高时钟总线,DS1302把数据读走
		dat >>= 1;		   //数据右移一位 
	}	
}
/*=================================================
*函数名称:DS1302_R_Byte
*函数功能:DS1302读一字节
*输出:dat:读取的数据
=================================================*/
uchar DS1302_R_Byte()
{
	uchar i, dat;
	for(i = 0; i < 8; i++)  //每次写1bit,写8次
	{
		TSCLK = 0;			//拉低时钟总线,DS1302把数据放到数据总线上
		dat >>= 1; 			//数据右移一位,数据从最低位开始读 
		if(TIO)	dat |= 0x80;//读取数据
		TSCLK = 1;			//拉高时钟总线
	}
	return dat;				//返回读取的数据
}
/*=================================================
*函数名称:DS1302_W_DAT
*函数功能:写DS1302数据一次写2个字节
*说明:先写命令后写数据
*调用:DS1302_W_Byte()
*输入:cmd:需要写的命令 ,dat:需要些的数据
=================================================*/
void DS1302_W_DAT(uchar cmd, uchar dat)
{
	TRST = 0;			 //拉低使能端
	TSCLK = 0;			 //拉低数据总线
	TRST = 1;			 //拉高使能端,开始写数据
	DS1302_W_Byte(cmd);	 //写命令
	DS1302_W_Byte(dat);	 //写数据
}
/*=================================================
*函数名称:DS1302_R_DAT
*函数功能:读DS1302数据
*说明:先写入命令字节后读出对应数据
*调用:	DS1302_W_Byte();DS1302_R_Byte();
*输入:	cmd:需要写的命令
*输出:	dat:读出的数据
=================================================*/
uchar DS1302_R_DAT(uchar cmd)
{
	uchar dat;
	TRST = 0;			 	//拉低使能端
	TSCLK = 0;				//拉低数据总线
	TRST = 1;				//拉高使能端,开始写数据
	DS1302_W_Byte(cmd);		//写命令
	dat = DS1302_R_Byte();	//读出数据
	return dat;				//返回读出数据
}

/*=================================================
*函数名称:DS1302_Clear_WP
*函数功能:清除DS1302写保护
*说明:先写入命令0x8e(写控制寄存器)接着向该寄存器写0
*调用:DS1302_W_DAT()
=================================================*/
void DS1302_Clear_WP()
{
	DS1302_W_DAT(0x8e,0x00);  //把控制寄存器WP位置0
}
/*=================================================
*函数名称:DS1302_Clear_WP
*函数功能:设置DS1302写保护
*说明:先写入命令0x8e(写控制寄存器)接着向该寄存器写0x80
*调用:DS1302_W_DAT()
=================================================*/
void DS1302_Set_WP()
{	
	DS1302_W_DAT(0x8e,0x80); //把控制寄存器WP位置1
	TRST = 0;				 //拉低使能端
	TSCLK = 0;				 //拉低数据总线
} 
/*=================================================
*函数名称:Set_DS1302_Time
*函数功能:设置DS1302时钟日历数据
*说明:把时钟日历暂存数组TimeData数据转换为BCD码并
	   写入到DS1302时钟日历寄存器中
*调用:DS1302_Clear_WP();DS1302_W_DAT();DS1302_Set_WP();
*输入:addr:需要写入寄存器的地址 ,TimeData数组:时钟日历初始值
=================================================*/
void Set_DS1302_Time(uchar addr)
{
	uchar i, j;
	DS1302_Clear_WP();		//清除写保护
	for(i = 0; i < 7; i++)	//写入7个字节的时钟初始值
	{
		j = TimeData[i]/10;	 //BCD码转换
		TimeData[i] %= 10;	 //BCD码转换
		TimeData[i] += j*16; //BCD码转换
		DS1302_W_DAT(addr, TimeData[i]); //先写DS1302时钟日历起始地址,再写数据
		addr += 2;	 //时钟日历寄存器地址+2转向下一个寄存器
	}
	DS1302_Set_WP(); //开起写保护		
}
/*=================================================
*函数名称:Read_DS1302_Time
*函数功能:读取DS1302时钟数据
*说明:	读取DS1302时钟数据 返回数据存入时钟日历暂存
		数组TimeData(数据格式BCD码)
*调用:DS1302_Clear_WP();DS1302_R_DAT();DS1302_Set_WP();
*输入:	addr:需要读取时钟日历寄存器的起始地址
=================================================*/
void Read_DS1302_Time(uchar addr)
{
	uchar i;
	DS1302_Clear_WP();    	//清楚些保护
	for(i = 0; i < 7; i++)	//从DS1302读取7个字节的时钟日历数据
	{
		TimeData[i] = DS1302_R_DAT(addr);//先写入要读取数据的寄存器起始地址,再读出数据存入TimeData数组
		addr += 2;						 //时钟日历寄存器地址+2转向下一个寄存器
	}
	DS1302_Set_WP();   //开起写保护
} 


/*=================================================
*函数名称:Read_Busy
*函数功能:判断1602液晶忙,并等待
=================================================*/
void Read_Busy()
{
	uchar busy;
	LCD1602_DB = 0xff;//复位数据总线
	LCD1602_RS = 0;	  //拉低RS
	LCD1602_RW = 1;	  //拉高RW读
	do
	{
		LCD1602_EN = 1;//使能EN
		busy = LCD1602_DB;//读回数据
		LCD1602_EN = 0;	 //拉低使能以便于下一次产生上升沿
	}while(busy & 0x80); //判断状态字BIT7位是否为1,为1则表示忙,程序等待
}
/*=================================================
*函数名称:LCD1602_Write_Cmd
*函数功能:写LCD1602命令
*调用:Read_Busy();
*输入:cmd:要写的命令
=================================================*/
void LCD1602_Write_Cmd(uchar cmd)
{
	Read_Busy();	 //判断忙,忙则等待
	LCD1602_RS = 0;
	LCD1602_RW = 0;	//拉低RS、RW操作时序情况1602课件下中文使用说明基本操作时序章节
	LCD1602_DB = cmd;//写入命令
	LCD1602_EN = 1;	 //拉高使能端 数据被传输到LCD1602内
	LCD1602_EN = 0;	 //拉低使能以便于下一次产生上升沿
}
/*=================================================
*函数名称:LCD1602_Write_Dat
*函数功能:写LCD1602数据
*调用:Read_Busy();
*输入:dat:需要写入的数据
=================================================*/
void LCD1602_Write_Dat(uchar dat)
{
	Read_Busy();
	LCD1602_RS = 1;
	LCD1602_RW = 0;
	LCD1602_DB = dat;
	LCD1602_EN = 1;
	LCD1602_EN = 0;
}
/*=================================================
*函数名称:LCD1602_Dis_OneChar
*函数功能:在指定位置显示一个字符
*调用:LCD1602_Write_Cmd(); LCD1602_Write_Dat();	
*输入:x:要显示的横坐标取值0-40,y:要显示的行坐标取值0-1(0为第一行,1为第二行)
		dat:需要显示的数据以ASCLL形式显示
=================================================*/
void LCD1602_Dis_OneChar(uchar x, uchar y,uchar dat)
{
	if(y)	x |= 0x40;
	x |= 0x80;
	LCD1602_Write_Cmd(x);
	LCD1602_Write_Dat(dat);		
}

//显示一个字节字符
void DisplayOneStr(uchar X,uchar Y,uchar DData)
{
	LCD1602_Dis_OneChar (X++,Y,DData/16 + '0');
	LCD1602_Dis_OneChar (X,Y,DData%16 + '0');
}

/*=================================================
*函数名称:LCD1602_Dis_Str
*函数功能:在指定位置显示字符串
*调用:LCD1602_Write_Cmd(); LCD1602_Write_Dat();
*输入:x:要显示的横坐标取值0-40,y:要显示的行坐标取值0-1(0为第一行,1为第二行)
		*str:需要显示的字符串
=================================================*/
void LCD1602_Dis_Str(uchar x, uchar y, uchar *str)
{
	if(y) x |= 0x40;
	x |= 0x80;
	LCD1602_Write_Cmd(x);
	while(*str != '\0')
	{
		LCD1602_Write_Dat(*str++);
	}
}
/*=================================================
*函数名称:Init_LCD1602
*函数功能:1602初始化
*调用:	LCD1602_Write_Cmd();
=================================================*/
void Init_LCD1602()
{
	LCD1602_Write_Cmd(0x38); //	设置16*2显示,5*7点阵,8位数据接口
	LCD1602_Write_Cmd(0x0c); //开显示
	LCD1602_Write_Cmd(0x06); //读写一字节后地址指针加1
	LCD1602_Write_Cmd(0x01); //清除显示
}

void main()
{
	uchar TestStr[] = {"Date:"};
	uchar str[] = {"Time:"};
	Init_LCD1602();                         //1602初始化
	Set_DS1302_Time(DS1302_W_ADDR);         //写入时钟日历寄存器起始地址再设置时钟日历初始值
	LCD1602_Dis_Str(0, 0, &TestStr[0]);	    //显示字符串
	LCD1602_Dis_Str(0, 1, &str[0]);	        //显示字符串
	
	while(1)
    {
		Read_DS1302_Time(DS1302_R_ADDR); 		//先写入时钟日历寄存器起始地址再读出时钟日历写入到TimeData数组中
		DisplayOneStr (5,0,TimeData[6]); 		//年
		LCD1602_Dis_OneChar (7,0,'-');		 	//-
		DisplayOneStr (8,0,TimeData[4]); 		//月
		LCD1602_Dis_OneChar (10,0,'-');		 	//-
		DisplayOneStr (11,0,TimeData[3]);		//日
		LCD1602_Dis_OneChar (13,0,'-');		 	//-
		DisplayOneStr (14,0,TimeData[5]);		//星期
		DisplayOneStr (6,1,TimeData[2]); 		//时
		LCD1602_Dis_OneChar (8,1,':');		 	//:
		DisplayOneStr (9,1,TimeData[1]); 		//分
		LCD1602_Dis_OneChar (11,1,':');		 	//:
		DisplayOneStr (12,1,TimeData[0]);		//秒
		Delay_Ms(1000);					 		//延时1秒
    }
}

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小明n.n

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值