【Proteus8.17仿真】 STM32仿真 0.96OLED 屏幕显示ds1302实时时间

前言

在嵌入式系统开发中,实时时钟(RTC)和显示模块是常见的功能需求。今天我们来分享如何使用STM32F103C8T6(蓝桥杯常用芯片)驱动DS1302实时时钟芯片,并在OLED屏幕上显示时间信息。本文包含完整的代码实现和详细的原理讲解,适合嵌入式初学者和爱好者参考学习。

DS1302 概述

DS1302是美国Dallas公司(后被Maxim Integrated,现为ADI公司一部分)推出的一款低成本、低功耗的实时时钟芯片。它能够提供秒、分、时、日、月、年等日历信息,并且具有闰年自动调整功能(有效至2100年)。

由于其接口简单、成本低廉,在早期的电子设计、学习套件(如Arduino)、以及一些对时间精度要求不高的消费类产品中得到了非常广泛的应用。

---

主要特点

  1. 实时时钟功能

    • 提供秒、分、时、日、月、年和星期信息。
    • 月末日期自动调整,包括闰年补偿(有效至2100年)。
    • 可选择12小时制或24小时制。
  2. 低功耗

    • 在备用电源(如纽扣电池)模式下,功耗极低,典型值小于300nA,能保证在主电源断开后时钟长时间持续运行。
  3. 内置RAM

    • 提供31字节的额外静态RAM,可用于存储用户自定义的配置参数或数据。
  4. 串行接口

    • 采用简单的三线串行接口(CEI/OSCLK),与微控制器(如51单片机、AVR、Arduino等)连接非常方便,占用I/O口少。
  5. 双电源供电

    • 支持主电源和备用电源(如3V锂电池)双电源输入,具有电源切换和控制电路,在主电源失效时自动切换到备用电源。
  6. 宽电压工作

    • 主电源工作电压范围宽:2.0V 至 5.5V。

引脚定义与功能

DS1302通常有8引脚DIP或SOIC封装。

引脚编号 引脚名称 功能描述
1 Vcc2 主电源输入引脚。
2 X1 32.768kHz晶振输入引脚1。
3 X2 32.768kHz晶振输入引脚2。
4 GND 接地
5 CE 片选使能引脚(有时也标记为RST)。高电平有效,在开始读写数据前必须将其置为高电平。
6 I/O 双向数据线引脚,用于数据的串行输入和输出。
7 SCLK 串行时钟输入引脚。数据在时钟的上升沿或下降沿被读写。
8 Vcc1 备用电源输入引脚(如接3V纽扣电池)。

注意:Vcc1和Vcc2的供电关系是,当Vcc2 > Vcc1 + 0.2V时,芯片由Vcc2供电;当Vcc2 < Vcc1时,芯片自动切换到由Vcc1供电。


内部结构与核心部件

  1. 移位寄存器: 负责与微控制器进行串行通信。
  2. 控制逻辑: 解析微控制器发送的命令字节,决定是读操作还是写操作,以及目标是时钟寄存器还是RAM。
  3. 时钟/日历寄存器组: 存储秒、分、时等时间日期信息的BCD码。
  4. 静态RAM: 31字节的用户数据存储区。
  5. 振荡器与分频器: 外接32.768kHz晶振,经过内部分频链,产生1Hz的秒信号。
  6. 电源控制电路: 管理主电源和备用电源的自动切换。

通信协议(时序)

DS1302使用一种简单的同步串行协议,所有数据传输都发生在CE引脚为高电平期间。

  • 命令字节: 每次数据传输都必须以一个8位的命令字节开始。

    • Bit 7: 必须为1。
    • Bit 6: 1表示对RAM进行操作,0表示对时钟/日历寄存器进行操作。
    • Bit 5 - Bit 1: 指定要读写的寄存器/RAM地址。
    • Bit 0: 1表示读操作,0表示写操作。
  • 数据传输

    • 数据在SCLK上升沿被写入DS1302。
    • 数据在SCLK下降沿从DS1302读出。
    • 每个时钟周期传输1位数据,最低有效位(LSB)先行

    DS1302时序图

---

应用领域

  • Arduino/Raspberry Pi/51/32等开发板的时钟模块
  • 数字万用表、数字温度计等仪器仪表
  • 电话、传真机等办公设备
  • 便携式设备、电池供电的系统
  • 智能电表
  • 汽车电子(如行车记录仪)

实现准备

proteus 仿真图一份(如下)

实现代码(基于stm32f103c8t6 标准库 代码)

代码实现细节:

c 复制代码
#include "stm32f10x.h"                  // Device header

//#include "usart.h"
#include "delay.h"
#include "KEY.h"
#include "oled.h"
//#include "beep.h"
#include "bsp_dht11.h"
#include "adc0832.h" 
#include "stdio.h" 

//#include "ds1302.h"

char time[7]={25, 4, 25, 12, 30, 0, 5};
char readtime[7]={25, 4, 25, 12, 30, 0, 5};
u8 war[32];
u8 time1[32];
//温度湿度阈值
//u8 dht11_data[4] = {20, 70, 20, 40};
u8 Tmin=20;
u8 Tmax=40;
u8 Hmin=30;
u8 Hmax=70;
u16 count=0;

DHT11_Data_TypeDef DHT11_Data;

#define Delay_us delay_us

void Beep_Init()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	 GPIO_InitStructure.GPIO_Pin =GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7 ;
	 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	 GPIO_Init(GPIOB, &GPIO_InitStructure);

	GPIO_ResetBits(GPIOB,GPIO_Pin_5);
}

void Beep_Run(unsigned char x)
{
		if(x==1)
		{
			GPIO_SetBits(GPIOB,GPIO_Pin_5);
		
		}else{
			GPIO_ResetBits(GPIOB,GPIO_Pin_5);
			//GPIO_SetBits(GPIOB,GPIO_Pin_5);
		
		
		}
			

			

}


#define DS1302_SCLK_PIN    GPIO_Pin_7
#define DS1302_IO_PIN      GPIO_Pin_6
#define DS1302_CE_PIN      GPIO_Pin_5
#define DS1302_GPIO        GPIOA

void DS1302_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // SCLK和CE配置为推挽输出
    GPIO_InitStructure.GPIO_Pin = DS1302_SCLK_PIN | DS1302_CE_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(DS1302_GPIO, &GPIO_InitStructure);
    
    // IO配置为浮空输入(初始状态)
    GPIO_InitStructure.GPIO_Pin = DS1302_IO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(DS1302_GPIO, &GPIO_InitStructure);
    
    // 初始状态
    GPIO_ResetBits(DS1302_GPIO, DS1302_SCLK_PIN);
    GPIO_ResetBits(DS1302_GPIO, DS1302_CE_PIN);
}


// 设置IO方向为输出
static void DS1302_IO_Output(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = DS1302_IO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(DS1302_GPIO, &GPIO_InitStructure);
}

// 设置IO方向为输入
static void DS1302_IO_Input(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = DS1302_IO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(DS1302_GPIO, &GPIO_InitStructure);
}

// 向DS1302写入一个字节
static void DS1302_WriteByte(uint8_t dat)
{
    uint8_t i;
    DS1302_IO_Output();
    
    for(i=0; i<8; i++)
    {
        if(dat & 0x01)
            GPIO_SetBits(DS1302_GPIO, DS1302_IO_PIN);
        else
            GPIO_ResetBits(DS1302_GPIO, DS1302_IO_PIN);
        
        GPIO_SetBits(DS1302_GPIO, DS1302_SCLK_PIN);
        Delay_us(5);
        GPIO_ResetBits(DS1302_GPIO, DS1302_SCLK_PIN);
        Delay_us(5);
        
        dat >>= 1;
    }
}

// 从DS1302读取一个字节
static uint8_t DS1302_ReadByte(void)
{
    uint8_t i, dat = 0;
    DS1302_IO_Input();
    
    for(i=0; i<8; i++)
    {
        dat >>= 1;
        if(GPIO_ReadInputDataBit(DS1302_GPIO, DS1302_IO_PIN))
            dat |= 0x80;
            
        GPIO_SetBits(DS1302_GPIO, DS1302_SCLK_PIN);
        Delay_us(5);
        GPIO_ResetBits(DS1302_GPIO, DS1302_SCLK_PIN);
        Delay_us(5);
    }
    
    return dat;
}

// DS1302命令定义
#define DS1302_SECOND       0x80
#define DS1302_MINUTE       0x82
#define DS1302_HOUR         0x84
#define DS1302_DAY          0x86
#define DS1302_MONTH        0x88
#define DS1302_WEEK         0x8A
#define DS1302_YEAR         0x8C
#define DS1302_WP           0x8E    // 写保护
#define DS1302_BURST        0xBE


// 向DS1302寄存器写入数据
void DS1302_WriteReg(uint8_t reg, uint8_t dat)
{
    GPIO_SetBits(DS1302_GPIO, DS1302_CE_PIN);
	Delay_us(5);
    DS1302_WriteByte(reg);      // 写入寄存器地址
    DS1302_WriteByte(dat);      // 写入数据
    GPIO_ResetBits(DS1302_GPIO, DS1302_CE_PIN);
	Delay_us(5);
}

// 从DS1302寄存器读取数据
uint8_t DS1302_ReadReg(uint8_t reg)
{
    uint8_t dat;
    GPIO_SetBits(DS1302_GPIO, DS1302_CE_PIN);
	Delay_us(5);
    DS1302_WriteByte(reg | 0x01);  // 读命令需要将地址最低位置1
    dat = DS1302_ReadByte();
    GPIO_ResetBits(DS1302_GPIO, DS1302_CE_PIN);
	Delay_us(5);
    return dat;
}

// BCD码转十进制
static uint8_t BCD2DEC(uint8_t bcd)
{
    return (bcd>>4)*10 + (bcd&0x0F);
}

// 十进制转BCD码
static uint8_t DEC2BCD(uint8_t dec)
{
    return ((dec/10)<<4) + (dec%10);
}

// 设置时间
void DS1302_SetTime(uint8_t hour, uint8_t min, uint8_t sec)
{
    // 关闭写保护
    DS1302_WriteReg(DS1302_WP, 0x00);
    
    // 写入时间
    DS1302_WriteReg(DS1302_SECOND, DEC2BCD(sec));
    DS1302_WriteReg(DS1302_MINUTE, DEC2BCD(min));
    DS1302_WriteReg(DS1302_HOUR, DEC2BCD(hour));
    
    // 开启写保护
    DS1302_WriteReg(DS1302_WP, 0x80);
}

// 读取时间
void DS1302_GetTime(uint8_t *hour, uint8_t *min, uint8_t *sec)
{
    *sec = BCD2DEC(DS1302_ReadReg(DS1302_SECOND) & 0x7F);
    *min = BCD2DEC(DS1302_ReadReg(DS1302_MINUTE));
    *hour = BCD2DEC(DS1302_ReadReg(DS1302_HOUR));
}

typedef struct {
    uint8_t year;
    uint8_t month;
    uint8_t day;
    uint8_t week;
    uint8_t hour;
    uint8_t minute;
    uint8_t second;
} DS1302_DateTime;

// 设置完整日期时间
void DS1302_SetDateTime(DS1302_DateTime *dt)
{
    // 关闭写保护
    DS1302_WriteReg(DS1302_WP, 0x00);
    
    // 写入时间日期
    DS1302_WriteReg(DS1302_SECOND, DEC2BCD(dt->second)& 0x7F);
    DS1302_WriteReg(DS1302_MINUTE, DEC2BCD(dt->minute));
    DS1302_WriteReg(DS1302_HOUR, DEC2BCD(dt->hour));
    DS1302_WriteReg(DS1302_DAY, DEC2BCD(dt->day));
    DS1302_WriteReg(DS1302_MONTH, DEC2BCD(dt->month));
    DS1302_WriteReg(DS1302_WEEK, DEC2BCD(dt->week));
    DS1302_WriteReg(DS1302_YEAR, DEC2BCD(dt->year));
    
    // 开启写保护
    DS1302_WriteReg(DS1302_WP, 0x80);
}

// 读取完整日期时间
void DS1302_GetDateTime(DS1302_DateTime *dt)
{
    dt->second = BCD2DEC(DS1302_ReadReg(DS1302_SECOND) & 0x7F);
    dt->minute = BCD2DEC(DS1302_ReadReg(DS1302_MINUTE));
    dt->hour = BCD2DEC(DS1302_ReadReg(DS1302_HOUR));
    dt->day = BCD2DEC(DS1302_ReadReg(DS1302_DAY));
    dt->month = BCD2DEC(DS1302_ReadReg(DS1302_MONTH));
    dt->week = BCD2DEC(DS1302_ReadReg(DS1302_WEEK));
    dt->year = BCD2DEC(DS1302_ReadReg(DS1302_YEAR));
}

int main(void)
{
		unsigned char  getkey=0,keymode=0;
    uint16_t tds=0;
    delay_init();	     //延时初始化

		//蜂鸣器初始化

			Beep_Init();

		//oled初始化
    OLED_Init();
	;
	 Key_Init();
	 
	DS1302_GPIO_Init();
	
	
	DS1302_DateTime dt;
    
    // 设置初始时间
    dt.year = 23;
    dt.month = 5;
    dt.day = 15;
    dt.week = 1;  // 周一
    dt.hour = 12;
    dt.minute = 0;
    dt.second = 0;

		
    OLED_Clear();
    delay_ms(20);
	
		OLED_ShowString(0, 0,"STM32-App time",16);
		//设置时间
		//DS1302_Init(); // 初始化DS1302

    // 写入初始时间
    //DS1302_WriteTime(time); // 2025年4月25日12点
		//TIME_Set();

	
    while(1)
    {		 
	
			DS1302_GetDateTime(&dt);
			sprintf((char *)war, "%02d-%02d-%02d", dt.year,dt.month,dt.day);
			OLED_ShowString(0, 3,war,8);
			sprintf((char *)time1, "%02d-%02d-%02d",dt.hour,dt.minute,dt.second);
			OLED_ShowString(0, 4,time1,8);

			}//while line
}

实现效果

需要 源码工程的小伙伴在 评论区留言。

相关推荐
10001hours3 小时前
(基于江协科技)51单片机入门:8.DS1302
科技·嵌入式硬件·51单片机
充哥单片机设计3 小时前
【STM32项目开源】基于STM32的工地环境监测系统
stm32·单片机·嵌入式硬件
速易达网络4 小时前
树莓派点亮LED灯
单片机·嵌入式硬件
straw_hat.4 小时前
PCB学习——STM32F103VET6-STM32主控部分
stm32·嵌入式硬件·学习
hazy1k6 小时前
K230基础-显示画面
stm32·单片机·嵌入式硬件·k230
A9better12 小时前
嵌入式开发学习日志31——stm32之外部中断与定时器中断的差别与选择
stm32·单片机·学习
沐欣工作室_lvyiyi12 小时前
基于物联网的个人健康管理系统(论文+源码)
单片机·物联网·毕业设计·健康管理
蒋楼丶13 小时前
stm32和Zynq的中断抢占机制
stm32·单片机·嵌入式硬件
xyx-3v13 小时前
已知三极管的类型(NPN/PNP)和基极引脚,如何区分集电极(c)和发射极(e)
单片机·嵌入式硬件·学习