理论部分的知识构建:

DS1302时钟芯片介绍——实时时钟

对比一下之前的一个知识点:

对比项 定时器 (Timer) 实时时钟 (RTC)
精度 高,可达微秒级 较低,多为秒级
计时范围 长(年月日时分秒)
掉电保持 是(有电池)
典型用途 产生 PWM、延时 显示时间、日历

DS1302 就是 RTC

  • 专门用来计时的芯片

  • 有独立的 32.768kHz 晶振

  • 有电池备份,掉电后继续走时

如图在单片机上的位置:

补充:

VCC2是单片机内部启动时钟的电源(正常理解)

为什么还有一个VCC1呢,他是用来校准时钟的,当时钟不工作的时候,为了确保它下次启用的时候是时间还是和现在的时间一样。

例如,我们手机不用了的时候我们会把他关掉黑屏(VCC2不供电了),但是里面的时间显示还是要继续的那么(VCC1就是去让时钟的时间跟实际时间是一样的——校准)。

晶振的作用:

晶振就像手表的"摆锤"或"石英"
没有它,时钟芯片就无法"走动"

DS1302 芯片需要接一个 32.768kHz 的晶振(X1、X2 引脚之间)。

为什么是 32.768kHz?

  • 32.768kHz = 32768 Hz

  • 32768 = 2¹⁵

  • 经过 15 级分频,正好得到 1 Hz(每秒一次)

特点:

晶振内部是一小块石英晶体,它具有压电效应。

石英晶体有一个特性:

它有一个固有的机械共振频率。当外加电信号的频率等于晶体的固有频率时,会产生共振,振荡幅度最大,最稳定。

BCD 码:

什么是 BCD 码?

BCD码(Binary-Coded Decimal):

  • 用 4 位二进制表示 1 位十进制数

  • 43 的 BCD 码 = 0100 0011

十进制 43
  4 → 0100
  3 → 0011
  合并 → 0100 0011 = 0x43

 为什么 DS1302 用 BCD 码?

DS1302 内部寄存器存的是 BCD 码:

  • 秒寄存器:0x59 表示 59 秒

  • 分寄存器:0x59 表示 59 分

  • 时寄存器:0x23 表示 23 时

这样做的优点:

  • 直接对应数码管显示(不用转换)

  • 每个字节的高4位是十位,低4位是个位

规律

  • 写地址 = 偶数

  • 读地址 = 写地址 + 1

实践部分和代码分析

ds1302.c

/*	# 	DS1302代码片段说明
	1. 	本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
	2. 	参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
		中对单片机时钟频率的要求,进行代码调试和修改。
*/								
#include "ds1302.h"
#include "intrins.h"
//
void Write_Ds1302(unsigned  char temp) //写一个字节
{
	unsigned char i;
	for (i=0;i<8;i++)     	
	{ 
		SCK = 0;
		SDA = temp&0x01;
		temp>>=1; 
		SCK=1;
	}
}   

//
void Write_Ds1302_Byte( unsigned char address,unsigned char dat ) //写命令+数据    
{
 	RST=0;	_nop_();
 	SCK=0;	_nop_();
 	RST=1; 	_nop_();  
 	Write_Ds1302(address);	
 	Write_Ds1302(dat);		
 	RST=0; 
}

//
unsigned char Read_Ds1302_Byte ( unsigned char address )//读一个字节
{
 	unsigned char i,temp=0x00;
 	RST=0;	_nop_();
 	SCK=0;	_nop_();
 	RST=1;	_nop_();
 	Write_Ds1302(address);
 	for (i=0;i<8;i++) 	
 	{		
		SCK=0;
		temp>>=1;	
 		if(SDA)
 		temp|=0x80;	
 		SCK=1;
	} 
 	RST=0;	_nop_();
 	SCK=0;	_nop_();
	SCK=1;	_nop_();
	SDA=0;	_nop_();
	SDA=1;	_nop_();
	return (temp);			
}


以上部分知道功能就行,不需要深挖



核心部分

BCD 码转换(核心!)

void Set_RTC(unsigned char *pucRTC)
{
    unsigned char temp;
    
    Write_Ds1302_Byte(0x8E, 0x00);  // 关闭写保护
    
    // pucRTC[0] = 时(23)
    temp = ((pucRTC[0]/10)<<4) | (pucRTC[0]%10);
    // 23/10 = 2 → 左移4位 → 0010 0000
    // 23%10 = 3 → 0000 0011
    // 或运算 → 0010 0011 = 0x23(BCD码)
    Write_Ds1302_Byte(0x84, temp);  // 写入小时寄存器
    
    // pucRTC[1] = 分(59)
    temp = ((pucRTC[1]/10)<<4) | (pucRTC[1]%10);
    // 59/10=5 → 0101 0000
    // 59%10=9 → 0000 1001
    // 或运算 → 0101 1001 = 0x59
    Write_Ds1302_Byte(0x82, temp);  // 写入分钟寄存器
    
    // pucRTC[2] = 秒(55)
    temp = ((pucRTC[2]/10)<<4) | (pucRTC[2]%10);
    // 55/10=5 → 0101 0000
    // 55%10=5 → 0000 0101
    // 或运算 → 0101 0101 = 0x55
    Write_Ds1302_Byte(0x80, temp);  // 写入秒寄存器
    
    Write_Ds1302_Byte(0x8E, 0x80);  // 打开写保护
}





void Get_RTC(unsigned char *pucRTC)
{
    unsigned char temp;
    
    temp = Read_Ds1302_Byte(0x85);  // 读小时
    // 假设读到 0x23
    pucRTC[0] = (temp>>4)*10 + (temp & 0x0F);
    // (0x23>>4) = 0x02 = 2
    // (0x23 & 0x0F) = 0x03 = 3
    // 2*10 + 3 = 23
    
    temp = Read_Ds1302_Byte(0x83);  // 读分钟
    pucRTC[1] = (temp>>4)*10 + (temp & 0x0F);
    
    temp = Read_Ds1302_Byte(0x81);  // 读秒
    pucRTC[2] = (temp>>4)*10 + (temp & 0x0F);
}

main.c

main.c
#include "init.h"
#include "seg.h"
#include "tim.h"
#include "ds1302.h"

//RTC
unsigned char pucRTC[3] = {23, 59, 55};

//Seg
unsigned char pucSeg_Buf[12], pucSeg_Code[8], pucSeg_Pos = 0;

//Timer
unsigned long ulms = 0;
unsigned int uiSeg_Dly = 0;
unsigned int uiRTC_Dly = 0;

void Seg_Proc(void);
void RTC_Proc(void);

void main(void)
{
	Cls_Peripheral();
	Timer0Init();
	EA = 1;
	Set_RTC(pucRTC);
    
	while(1)
	{
		Seg_Proc();
        RTC_Proc();
	}
	
}

void Seg_Proc(void)
{
	if(uiSeg_Dly < 200)
		return;
	
	uiSeg_Dly = 0;
	
	sprintf(pucSeg_Buf, "%02d %02d %02d", (unsigned int)pucRTC[0], (unsigned int)pucRTC[1], (unsigned int)pucRTC[2]);
	Seg_Tran(pucSeg_Buf, pucSeg_Code);
	
}

void RTC_Proc(void)
{
    if(uiRTC_Dly < 200)
		return;
	
	uiRTC_Dly = 0;
    
    Get_RTC(pucRTC);
}

void Time_0(void) interrupt 1
{
	ulms++;
	uiSeg_Dly++;
    uiRTC_Dly++;
	
	if(ulms % 2 == 0)
	{
		pucSeg_Pos = (pucSeg_Pos + 1)%8;
		Seg_Disp(pucSeg_Code, pucSeg_Pos);
	}
}

RTC_Proc —— 定时读 DS1302


void RTC_Proc(void)
{
    if(uiRTC_Dly < 200)
        return;
    
    uiRTC_Dly = 0;
    
    Get_RTC(pucRTC);      // 从 DS1302 读出当前时间
}

为什么 200ms 读一次?

  • 时间每秒才变一次,200ms 读一次足够

  • 减少对 DS1302 的访问频率

信息流:

硬件晶振 32.768kHz
    ↓
DS1302 内部计数器每秒+1
    ↓
寄存器(秒分时)自动更新(BCD码)
    ↓
主循环每200ms:
   Get_RTC() → 三线接口读寄存器
    ↓
   BCD码转十进制 → pucRTC[] 更新
    ↓
   sprintf 格式化 → pucSeg_Buf
    ↓
   Seg_Tran → 转成段码 → pucSeg_Code
    ↓
中断每2ms:
   pucSeg_Pos 切换
   Seg_Disp 送段码
    ↓
数码管显示 "23 59 55"
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐