嵌入式开发学习(STC51-14-时钟)

内容

在数码管上显示时间,时分秒,格式为"XX-XX-XX";

DS1302时钟芯片介绍

简介

DS1302是DALLAS公司推出的涓流充电时钟芯片,内含有一个实时时钟/日历和31字节静态RAM,通过简单的串行接口与单片机进行通信;

实时时钟/日历电路提供秒、分、时、日、周、月、年的信息,每月的天数和闰年的天数可自动调整;

时钟操作可通过AM/PM指示决定采用24或12小时格式;

DS1302与单片机之间能简单地采用同步串行的方式进行通信,仅需用到三根通信线:RES复位、I/O数据线、SCLK串行时钟;

时钟/RAM的读/写数据以一个字节或多达31个字节的字符组方式通信;

DS1302工作时功耗很低保持数据和时钟信息时功率小于1mW;

结构

物理结构

1,VCC2:主电源引脚

2,X1、X2:DS1302外部晶振引脚,通常需外接32.768K晶振

3,GND:电源地

4,CE:使能引脚,也是复位引脚(新版本功能变)

5,I/O:串行数据引脚,数据输出或者输入都从这个引脚

6,SCLK:串行时钟引脚

7,VCC1:备用电源

程序结构

DS1302有一个控制寄存器、12 个日历、时钟寄存器和31个RAM

控制寄存器

控制寄存器用于存放DS1302的控制命令字,DS1302的RST引脚回到高电平后写入的第一个字节就为控制命令;

它用于对DS1302读写过程进行控制;

格式如下:

1、第7位永远都是1;

2、第6位,1表示RAM,寻址内部存储器地址;0表示CK,寻址内部寄存器;

3、第5到第1位,为RAM或者寄存器的地址;

4、最低位,高电平表示RD,即下一步操作将要"读";低电平表示W,即下一步操作将要"写";(与AT24C02寄存器类似,这点要理解好)

比如读秒寄存器命令为1000 0001,反之写秒寄存器命令为1000 0000

日历/时钟寄存器

DS1302共有12个寄存器,其中有7个与日历、时钟相关,存放的数据为BCD码形式;

格式如下:

  • 秒寄存器:低四位为秒的个位,高的次三位为秒的十位;最高位CH为DS1302的运行标志,当CH=0时,DS1302内部时钟运行,反之CH=1时停止;
  • 小时寄存器:最高位为12/24小时的格式选择位,该位为1时表示12小时格式;当设置为12小时显示格式时,第5位的高电平表示下午(PM);而当设置为24小时格式时,第5位为具体的时间数据;
  • 写保护寄存器:当该寄存器最高位WP为1时,DS1302只读不写,所以要在往DS1302写数据之前确保WP为0;
  • 慢充电寄存器(涓细电流充电)寄存器:我们知道,当DS1302掉电时,可以马上调用外部电源保护时间数据,该寄存器就是配置备用电源的充电选项的;其中高四位(4个TCS)只有在1010的情况下才能使用充电选项;低四位的情况与DS1302内部电路有关;

使用

BCD码

在日历/时钟寄存器中一般都是以BCD码存放数据;

BCD码是通过4位二进制码来表示1位十进制中的0~9这10个数码;

如下所示:

所以从DS1302中读取出来的时钟数据均为BCD码格式,需转换为我们习惯的10进制;

DS1302的读写时序

在控制指令字输入后的下一个SCLK时钟的上升沿时,数据被写入DS1302,数据输入从低位(位0)开始;

同样,在紧跟8位的控制指令字后的下一个SCLK脉冲的下降沿读出DS1302的数据,读出数据时从低位0位到高位7;

其时序图如下所示:

上图就是DS1302的三个时序:复位时序,单字节写时序,单字节读时序;

  • CE(RST):复位时序,即在RST引脚产生一个正脉冲,在整个读写期间,RST要保持高电平,一次字节读写完毕之后,要注意把RST返回低电平准备下次读写周期;
  • 单字节读时序:注意读之前还是要先对寄存器写命令,从最低位开始写;可以看到,写数据是在SCLK的上升沿实现,而读数据在SCLK的下降沿实现;所以,在单字节读时序中,写命令的第八个上升沿结束后紧接着的第八个下降沿就将要读寄存器的第一位数据读到数据线上了,这个就是DS1302操作中最特别的地方;当然读出来的数据也是最低位开始;
  • 单字节写时序:两个字节的数据配合16个上升沿将数据写入即可;

使用步骤

1 将数据写入DS1302的寄存器,设置它当前的时间的格式;

2 使DS1302开始运作,DS1302时钟会按照设置情况运转,再用单片机将其寄存器内的数据读出;

3 再用液晶显示,就是我们常说的简易电子钟;

原理图

从上图中可知,DS1302芯片的控制管脚SCLK、I/O、CE接至单片机P3.6、P3.4、P3.5上;

芯片的X1、X2管脚处外接了一个32.768KHZ晶振,为时钟运行提供一个稳定的时钟频率,C2和C3为旁路电容,目的是消除晶振起振时产生的电感干扰;

如果有外部电源还能将电源接入第8脚VCC1;

思路

按照时序图编写DS1302的初始化和读写程序;

读取时间并显示在数码管上;

(读取数据时注意将BCD格式转换为十进制,其实就是,把十进制数取个十百单位时的,除10余10操作,换成除16余16)

编码

User

main.c

c 复制代码
/*
 * @Description: 在数码管上显示时间,时分秒,格式为"XX-XX-XX"
 */
#include "public.h"
#include "smg.h"
#include "ds1302.h"

void main()
{
	u8 time_buf[8];

	// 设置初始时间
	// 原本初始化时间2000年01月01日星期六0点0分0秒
	//---存储顺序是秒分时日月周年,存储格式是用BCD码---//
	gDS1302_TIME[0] = 0x06; // 秒
	gDS1302_TIME[1] = 0x06; // 分
	gDS1302_TIME[2] = 0x06; // 时
	// gDS1302_TIME[3] = {}; // 日
	// gDS1302_TIME[4] = {}; // 月
	// gDS1302_TIME[5] = {}; // 周
	// gDS1302_TIME[6] = {}; // 年

	ds1302_init(); // 初始化DS1302

	while (1)
	{
		ds1302_read_time();
		time_buf[0] = gsmg_code[gDS1302_TIME[2] / 16];
		time_buf[1] = gsmg_code[gDS1302_TIME[2] & 0x0f];
		time_buf[2] = 0x40;
		time_buf[3] = gsmg_code[gDS1302_TIME[1] / 16];
		time_buf[4] = gsmg_code[gDS1302_TIME[1] & 0x0f];
		time_buf[5] = 0x40;
		time_buf[6] = gsmg_code[gDS1302_TIME[0] / 16];
		time_buf[7] = gsmg_code[gDS1302_TIME[0] & 0x0f];
		smg_display(time_buf, 1);
	}
}

Public

public.h

c 复制代码
#ifndef _public_H
#define _public_H

#include "reg52.h"

typedef unsigned int u16; // 对系统默认数据类型进行重定义
typedef unsigned char u8;

void delay_10us(u16 ten_us);
void delay_ms(u16 ms);

#endif

public.c

c 复制代码
#include "public.h"

/**
 * @description: 延时函数,ten_us=1时,大约延时10us
 * @param {u16} ten_us 延时倍数
 * @return {*}
 */
void delay_10us(u16 ten_us)
{
	while (ten_us--)
		;
}

/**ms延时函数,ms=1时,大约延时1ms***
 * @param {u16} ms 延时倍数
 * @return {*}
 */
void delay_ms(u16 ms)
{
	u16 i, j;
	for (i = ms; i > 0; i--)
		for (j = 110; j > 0; j--)
			;
}

App/ds1302

ds1302.h

c 复制代码
#ifndef _ds1302_H
#define _ds1302_H

#include "public.h"

// 管脚定义
sbit DS1302_RST = P3 ^ 5; // 复位管脚
sbit DS1302_CLK = P3 ^ 6; // 时钟管脚
sbit DS1302_IO = P3 ^ 4;  // 数据管脚

// 变量声明
extern u8 gDS1302_TIME[7]; // 存储时间

// 函数声明
void ds1302_init(void);
void ds1302_read_time(void);

#endif

ds1302.c

c 复制代码
#include "ds1302.h"
#include "intrins.h"

//---DS1302写入和读取时分秒的地址命令---//
//---秒分时日月周年 最低位读写位;-------//
u8 gREAD_RTC_ADDR[7] = {0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d};
u8 gWRITE_RTC_ADDR[7] = {0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c};

//---DS1302时钟初始化2000年01月01日星期六0点0分0秒---//
//---存储顺序是秒分时日月周年,存储格式是用BCD码---//
u8 gDS1302_TIME[7] = {0x00, 0x00, 0x00, 0x01, 0x06, 0x01, 0x00};

/**
 * @description: DS1302写单字节
 * @param {u8} addr 地址/命令
 * @param {u8} dat 要写入的数据
 * @return {*}
 */
void ds1302_write_byte(u8 addr, u8 dat)
{
	u8 i = 0;

	DS1302_RST = 0;
	_nop_();
	DS1302_CLK = 0; // CLK低电平
	_nop_();
	DS1302_RST = 1; // RST由低到高变化
	_nop_();

	for (i = 0; i < 8; i++) // 循环8次,每次写1位,先写低位再写高位
	{
		DS1302_IO = addr & 0x01;
		addr >>= 1;
		DS1302_CLK = 1;
		_nop_();
		DS1302_CLK = 0; // CLK由低到高产生一个上升沿,从而写入数据
		_nop_();
	}
	for (i = 0; i < 8; i++) // 循环8次,每次写1位,先写低位再写高位
	{
		DS1302_IO = dat & 0x01;
		dat >>= 1;
		DS1302_CLK = 1;
		_nop_();
		DS1302_CLK = 0;
		_nop_();
	}
	DS1302_RST = 0; // RST拉低
	_nop_();
}

/**
 * @description: DS1302读单字节
 * @param {u8} addr 地址/命令
 * @return {u8} 读取的数据
 */
u8 ds1302_read_byte(u8 addr)
{
	u8 i = 0;
	u8 temp = 0;
	u8 value = 0;

	DS1302_RST = 0;
	_nop_();
	DS1302_CLK = 0; // CLK低电平
	_nop_();
	DS1302_RST = 1; // RST由低到高变化
	_nop_();

	for (i = 0; i < 8; i++) // 循环8次,每次写1位,先写低位再写高位
	{
		DS1302_IO = addr & 0x01;
		addr >>= 1;
		DS1302_CLK = 1;
		_nop_();
		DS1302_CLK = 0; // CLK由低到高产生一个上升沿,从而写入数据
		_nop_();
	}
	for (i = 0; i < 8; i++) // 循环8次,每次读1位,先读低位再读高位
	{
		temp = DS1302_IO;
		value = (temp << 7) | (value >> 1); // 先将value右移1位,然后temp左移7位,最后或运算
		DS1302_CLK = 1;
		_nop_();
		DS1302_CLK = 0;
		_nop_();
	}
	DS1302_RST = 0; // RST拉低
	_nop_();
	DS1302_CLK = 1; // 对于实物中,P3.4口没有外接上拉电阻的,此处代码需要添加,使数据口有一个上升沿脉冲。
	_nop_();
	DS1302_IO = 0;
	_nop_();
	DS1302_IO = 1;
	_nop_();
	return value;
}

/**
 * @description: DS1302初始化时间
 * @return {*}
 */
void ds1302_init(void)
{
	u8 i = 0;
	ds1302_write_byte(0x8E, 0X00);
	for (i = 0; i < 7; i++)
	{
		ds1302_write_byte(gWRITE_RTC_ADDR[i], gDS1302_TIME[i]);
	}
	ds1302_write_byte(0x8E, 0X80);
}

/**
 * @description: DS1302读取时间
 * @return {*}
 */
void ds1302_read_time(void)
{
	u8 i = 0;
	for (i = 0; i < 7; i++)
	{
		gDS1302_TIME[i] = ds1302_read_byte(gREAD_RTC_ADDR[i]);
	}
}

App/smg

smg.h

c 复制代码
#ifndef _smg_H
#define _smg_H

#include "public.h"

#define SMG_A_DP_PORT P0 // 使用宏定义数码管段码口

// 定义数码管位选信号控制脚
sbit LSA = P2 ^ 2;
sbit LSB = P2 ^ 3;
sbit LSC = P2 ^ 4;

extern u8 gsmg_code[17]; // 使"共阴极数码管显示0~F的段码数据"这个变量定义为外部可用

void smg_display(u8 dat[], u8 pos);

#endif

smg.c

c 复制代码
#include "smg.h"

// 共阴极数码管显示0~F的段码数据
u8 gsmg_code[17] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71};

/**
 * @description: 动态数码管显示函数
 * @param {u8} dat 要显示的数据
 * @param {u8} pos 从左开始第几个位置开始显示,范围1-8
 * @return {*}
 */
void smg_display(u8 dat[], u8 pos)
{
	u8 i = 0;
	u8 pos_temp = pos - 1;

	for (i = pos_temp; i < 8; i++)
	{
		switch (i) // 位选
		{
		case 0:
			LSC = 1;
			LSB = 1;
			LSA = 1;
			break;
		case 1:
			LSC = 1;
			LSB = 1;
			LSA = 0;
			break;
		case 2:
			LSC = 1;
			LSB = 0;
			LSA = 1;
			break;
		case 3:
			LSC = 1;
			LSB = 0;
			LSA = 0;
			break;
		case 4:
			LSC = 0;
			LSB = 1;
			LSA = 1;
			break;
		case 5:
			LSC = 0;
			LSB = 1;
			LSA = 0;
			break;
		case 6:
			LSC = 0;
			LSB = 0;
			LSA = 1;
			break;
		case 7:
			LSC = 0;
			LSB = 0;
			LSA = 0;
			break;
		}
		SMG_A_DP_PORT = dat[i - pos_temp]; // 传送段选数据
		delay_10us(100);							  // 延时一段时间,等待显示稳定
		SMG_A_DP_PORT = 0x00;						  // 消影
	}
}

编译和结果

按F7编译,无错误,生成.hex文件,使用pz-isp将hex文件下载到单片机

结果:成功显示时间,并正常计时

相关推荐
西岸行者6 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意6 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码6 天前
嵌入式学习路线
学习
毛小茛6 天前
计算机系统概论——校验码
学习
babe小鑫6 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms6 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下6 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。6 天前
2026.2.25监控学习
学习
im_AMBER6 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J6 天前
从“Hello World“ 开始 C++
c语言·c++·学习