【Proteus8.17仿真】 STM32仿真 0.96OLED 屏幕显示ds1302实时时间
前言
在嵌入式系统开发中,实时时钟(RTC)和显示模块是常见的功能需求。今天我们来分享如何使用STM32F103C8T6(蓝桥杯常用芯片)驱动DS1302实时时钟芯片,并在OLED屏幕上显示时间信息。本文包含完整的代码实现和详细的原理讲解,适合嵌入式初学者和爱好者参考学习。
DS1302 概述
DS1302是美国Dallas公司(后被Maxim Integrated,现为ADI公司一部分)推出的一款低成本、低功耗的实时时钟芯片。它能够提供秒、分、时、日、月、年等日历信息,并且具有闰年自动调整功能(有效至2100年)。
由于其接口简单、成本低廉,在早期的电子设计、学习套件(如Arduino)、以及一些对时间精度要求不高的消费类产品中得到了非常广泛的应用。
—
主要特点
-
实时时钟功能:
- 提供秒、分、时、日、月、年和星期信息。
- 月末日期自动调整,包括闰年补偿(有效至2100年)。
- 可选择12小时制或24小时制。
-
低功耗:
- 在备用电源(如纽扣电池)模式下,功耗极低,典型值小于300nA,能保证在主电源断开后时钟长时间持续运行。
-
内置RAM:
- 提供31字节的额外静态RAM,可用于存储用户自定义的配置参数或数据。
-
串行接口:
- 采用简单的三线串行接口(
CE
、I/O
、SCLK
),与微控制器(如51单片机、AVR、Arduino等)连接非常方便,占用I/O口少。
- 采用简单的三线串行接口(
-
双电源供电:
- 支持主电源和备用电源(如3V锂电池)双电源输入,具有电源切换和控制电路,在主电源失效时自动切换到备用电源。
-
宽电压工作:
- 主电源工作电压范围宽: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供电。
内部结构与核心部件
- 移位寄存器: 负责与微控制器进行串行通信。
- 控制逻辑: 解析微控制器发送的命令字节,决定是读操作还是写操作,以及目标是时钟寄存器还是RAM。
- 时钟/日历寄存器组: 存储秒、分、时等时间日期信息的BCD码。
- 静态RAM: 31字节的用户数据存储区。
- 振荡器与分频器: 外接32.768kHz晶振,经过内部分频链,产生1Hz的秒信号。
- 电源控制电路: 管理主电源和备用电源的自动切换。
通信协议(时序)
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 标准库 代码)
代码实现细节:
#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_usvoid 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 GPIOAvoid 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);elseGPIO_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); // 读命令需要将地址最低位置1dat = 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
}
实现效果
需要 源码工程的小伙伴在 评论区留言。