打印
[应用相关]

STM32嵌入式开发时钟模块

[复制链接]
355|10
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
前言
在本篇文章中,我们将介绍如何在STM32F103C8T6上集成DS1302实时时钟芯片。我们将讨论DS1302的硬件连接和软件驱动的实现,以及如何获取并显示当前的时间和日期信息。通过学习本篇文章,读者将掌握如何在STM32F103C8T6嵌入式系统中使用DS1302实时时钟芯片,为自己的嵌入式应用添加时间和日期功能。

一、DS1302是什么?

DS1302是一款实时时钟芯片,可用于各种电子设备中的时间和日期记录。它具有低功耗和高精度等特点,并提供了内部RAM和定时器功能。DS1302通过SPI(串行外设接口)或I2C(双线串行总线)与主控制器相连,可以通过主控制器进行配置和读取时间数据。这款芯片通常用于电子钟表、计时器、温度计等设备中。



引脚连接表
二、使用步骤
1.DS1302.c
代码如下(示例):

#include "ds1302.h"
#include "OLED.h"
struct TIMEData TimeData;
uint8_t read_time[7];

void ds1302_gpio_init(void) // CE,SCLK端口初始化
{
        GPIO_InitTypeDef GPIO_InitStructure;
        RCC_APB2PeriphClockCmd(CE_GPIO_CLK, ENABLE);
        GPIO_InitStructure.GPIO_Pin = CE_GPIO_PIN;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
        GPIO_Init(CE_GPIO_PORT, &GPIO_InitStructure);         // 初始化GPIOB.12
        GPIO_ResetBits(CE_GPIO_PORT, CE_GPIO_PIN);

        GPIO_InitStructure.GPIO_Pin = SCLK_GPIO_PIN;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
        GPIO_Init(SCLK_GPIO_PORT, &GPIO_InitStructure);         // 初始化GPIOC.12
        GPIO_ResetBits(SCLK_GPIO_PORT, SCLK_GPIO_PIN);
}

void ds1302_DATAOUT_init(void) // 配置双向I/O端口为输出态
{
        GPIO_InitTypeDef GPIO_InitStructure;
        RCC_APB2PeriphClockCmd(DATA_GPIO_CLK, ENABLE);

        GPIO_InitStructure.GPIO_Pin = DATA_GPIO_PIN;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_Init(DATA_GPIO_PORT, &GPIO_InitStructure); // 初始化GPIOB.14
        GPIO_ResetBits(DATA_GPIO_PORT, DATA_GPIO_PIN);
}

void ds1302_DATAINPUT_init() // 配置双向I/O端口为输入态
{
        GPIO_InitTypeDef GPIO_InitStructure;
        RCC_APB2PeriphClockCmd(DATA_GPIO_CLK, ENABLE);
        GPIO_InitStructure.GPIO_Pin = DATA_GPIO_PIN; // PB.14 DATA
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init(DATA_GPIO_PORT, &GPIO_InitStructure); // 初始化GPIOB.14
}

void ds1302_write_onebyte(u8 data) // 向DS1302发送一字节数据
{
        ds1302_DATAOUT_init();
        u8 count = 0;
        SCLK_L;
        for (count = 0; count < 8; count++)
        {
                SCLK_L;
                if (data & 0x01)
                {
                        DATA_H;
                }
                else
                {
                        DATA_L;
                }                // 先准备好数据再发送
                SCLK_H; // 拉高时钟线,发送数据
                data >>= 1;
        }
}

void ds1302_wirte_rig(u8 address, u8 data) // 向指定寄存器地址发送数据
{
        u8 temp1 = address;
        u8 temp2 = data;
        CE_L;
        SCLK_L;
        Delay_us(1);
        CE_H;
        Delay_us(2);
        ds1302_write_onebyte(temp1);
        ds1302_write_onebyte(temp2);
        CE_L;
        SCLK_L;
        Delay_us(2);
}

void ds1302_wirte(u8 address) // 向指定寄存器地址发送数据
{
        u8 temp1 = address;
        CE_L;
        SCLK_L;
        Delay_us(1);
        CE_H;
        Delay_us(2);
        ds1302_write_onebyte(temp1);
        CE_L;
        SCLK_L;
        Delay_us(2);
}

u8 ds1302_read_rig(u8 address) // 从指定地址读取一字节数据
{
        u8 temp3 = address;
        u8 count = 0;
        u8 return_data = 0x00;
        CE_L;
        SCLK_L;
        Delay_us(3);
        CE_H;
        Delay_us(3);
        ds1302_write_onebyte(temp3);
        ds1302_DATAINPUT_init(); // 配置I/O口为输入
        Delay_us(2);
        for (count = 0; count < 8; count++)
        {
                Delay_us(2); // 使电平持续一段时间
                return_data >>= 1;
                SCLK_H;
                Delay_us(4); // 使高电平持续一段时间
                SCLK_L;
                Delay_us(14); // 延时14us后再去读取电压,更加准确
                if (GPIO_ReadInputDataBit(DATA_GPIO_PORT, DATA_GPIO_PIN))
                {
                        return_data = return_data | 0x80;
                }
        }
        Delay_us(2);
        CE_L;
        DATA_L;
        return return_data;
}

void ds1302_init(void)
{
        ds1302_wirte_rig(0x00, 0x8e); // 关闭写保护
        ds1302_wirte_rig(0x80, 0x00); // seconds00秒
        ds1302_wirte_rig(0x82, 0x31); // minutes31分
        ds1302_wirte_rig(0x84, 0x09); // hours9时
        ds1302_wirte_rig(0x86, 0x18); // date18日
        ds1302_wirte_rig(0x88, 0x11); // months11月
        ds1302_wirte_rig(0x8a, 0x06); // days星期6
        ds1302_wirte_rig(0x8c, 0x23); // year2023年
        ds1302_wirte_rig(0x00, 0x80); // 开启写保护
}

void time_set(uint8_t* time)
{
        ds1302_wirte_rig(0x00, 0x8e); // 关闭写保护
        ds1302_wirte_rig(0x80, time[5]); // seconds00秒
        ds1302_wirte_rig(0x82, time[4]); // minutes31分
        ds1302_wirte_rig(0x84, time[3]); // hours9时
        ds1302_wirte_rig(0x86, time[2]); // date18日
        ds1302_wirte_rig(0x88, time[1]); // months11月
        ds1302_wirte_rig(0x8c, time[0]); // year2023年
        ds1302_wirte_rig(0x00, 0x80); // 关闭写保护
}

void ds1302_read_time(void)
{
        read_time[0] = ds1302_read_rig(0x81); // 读秒
        read_time[1] = ds1302_read_rig(0x83); // 读分
        read_time[2] = ds1302_read_rig(0x85); // 读时
        read_time[3] = ds1302_read_rig(0x87); // 读日
        read_time[4] = ds1302_read_rig(0x89); // 读月
        read_time[5] = ds1302_read_rig(0x8B); // 读星期
        read_time[6] = ds1302_read_rig(0x8D); // 读年
}

void ds1302_read_realTime(void)
{
        ds1302_read_time(); // BCD码转换为10进制
        TimeData.second = (read_time[0] >> 4) * 10 + (read_time[0] & 0x0f);
        TimeData.minute = ((read_time[1] >> 4) & (0x07)) * 10 + (read_time[1] & 0x0f);
        TimeData.hour = (read_time[2] >> 4) * 10 + (read_time[2] & 0x0f);
        TimeData.day = (read_time[3] >> 4) * 10 + (read_time[3] & 0x0f);
        TimeData.month = (read_time[4] >> 4) * 10 + (read_time[4] & 0x0f);
        TimeData.week = read_time[5];
        TimeData.year = (read_time[6] >> 4) * 10 + (read_time[6] & 0x0f) + 2000;
}

2.DS1302.h
代码如下(示例):

#ifndef __DS1302_H
#define __DS1302_H

#include "stm32f10x.h"                  // 包含STM32F10x系列微控制器的设备头文件
#include "Delay.h"                      // 包含延时函数库

// 定义SCLK(串行时钟)引脚相关的宏
#define SCLK_GPIO_PORT GPIOB            // SCLK引脚所在的GPIO端口
#define SCLK_GPIO_PIN GPIO_Pin_12       // SCLK引脚编号
#define SCLK_GPIO_CLK RCC_APB2Periph_GPIOB // SCLK引脚所在端口的时钟

// 定义DATA(数据)引脚相关的宏
#define DATA_GPIO_PORT GPIOB            // DATA引脚所在的GPIO端口
#define DATA_GPIO_PIN GPIO_Pin_13       // DATA引脚编号
#define DATA_GPIO_CLK RCC_APB2Periph_GPIOB // DATA引脚所在端口的时钟

// 定义CE(芯片使能)引脚相关的宏
#define CE_GPIO_PORT GPIOB              // CE引脚所在的GPIO端口
#define CE_GPIO_PIN GPIO_Pin_14         // CE引脚编号
#define CE_GPIO_CLK RCC_APB2Periph_GPIOB // CE引脚所在端口的时钟

// 定义操作CE, SCLK, 和 DATA引脚高低电平的宏
#define CE_L GPIO_ResetBits(CE_GPIO_PORT, CE_GPIO_PIN)                 // 拉低CE引脚
#define CE_H GPIO_SetBits(CE_GPIO_PORT, CE_GPIO_PIN)                 // 拉高CE引脚
#define SCLK_L GPIO_ResetBits(SCLK_GPIO_PORT, SCLK_GPIO_PIN) // 拉低SCLK引脚
#define SCLK_H GPIO_SetBits(SCLK_GPIO_PORT, SCLK_GPIO_PIN)         // 拉高SCLK引脚
#define DATA_L GPIO_ResetBits(DATA_GPIO_PORT, DATA_GPIO_PIN) // 拉低DATA引脚
#define DATA_H GPIO_SetBits(DATA_GPIO_PORT, DATA_GPIO_PIN)         // 拉高DATA引脚

// 创建一个用于存储时间日期数据的结构体
struct TIMEData {
        uint16_t year;  // 年份
        uint8_t month;  // 月份
        uint8_t day;    // 日期
        uint8_t hour;   // 小时
        uint8_t minute; // 分钟
        uint8_t second; // 秒
        uint8_t week;   // 星期
};

extern struct TIMEData TimeData; // 声明一个全局变量TimeData,用于存储当前的时间和日期信息

// 函数声明,以下函数用于初始化和操作DS1302
void ds1302_gpio_init(void);                 // 初始化DS1302的GPIO引脚
void ds1302_write_onebyte(u8 data);          // 向DS1302发送一字节的数据
void ds1302_wirte_rig(u8 address, u8 data);  // 向DS1302的指定寄存器写入一字节的数据
u8 ds1302_read_rig(u8 address);              // 从DS1302的指定寄存器读取一字节的数据
void ds1302_init(void);                      // DS1302的初始化函数
void ds1302_DATAOUT_init(void);              // 配置DATA引脚为输出模式
void ds1302_DATAINPUT_init(void);            // 配置DATA引脚为输入模式
void ds1302_read_time(void);                 // 从DS1302读取实时时间(BCD码)
void ds1302_read_realTime(void);             // 将从DS1302读取的BCD码转换为十进制数据
void time_set(uint8_t* time);                // 设置DS1302的时间

#endif // __DS1302_H

3.main.c
代码如下(示例):

#include "stm32f10x.h"                                //STM32头文件
#include "DS1302.h"                                        //时钟的头文件
#include "oled.h"                                        //OLED屏头文件

uint8_t Time[6] = {0x23, 0x05, 0x10, 0x12, 0x00, 0x00};   // 年, 月, 日, 时, 分, 秒



int main(void)
{       
    ds1302_gpio_init();                                //时钟模块初始化
    OLED_Init();                                        //OLED屏初始化
    OLED_Clear();                                        //连接完成后清屏
//  time_set(Time);                                 //时间设定注释是为了锁定时间

    while(1)
        {       
        OLED_Clear();                                        //连接完成后清屏
        OLED_ShowNum(23, 16, TimeData.year, 4, OLED_8X16);  // 年份
        OLED_ShowChar(55, 16, '-', OLED_8X16);             // 分隔符
        OLED_ShowNum(63, 16, TimeData.month, 2, OLED_8X16); // 月份
        OLED_ShowChar(79, 16, '-', OLED_8X16);             // 分隔符
        OLED_ShowNum(87, 16, TimeData.day, 2, OLED_8X16);  // 日期

        OLED_ShowNum(31, 32, TimeData.hour, 2, OLED_8X16);   // 小时
        OLED_ShowChar(47, 32, ':', OLED_8X16);              // 冒号
        OLED_ShowNum(55, 32, TimeData.minute, 2, OLED_8X16); // 分钟
        OLED_ShowChar(71, 32, ':', OLED_8X16);              // 冒号
        OLED_ShowNum(79, 32, TimeData.second, 2, OLED_8X16); // 秒
        OLED_Update();                                                         //数据更新
    }

}

总结
STM32F103C8T6是一款常用的嵌入式微控制器,而DS1302是一款实时时钟集成电路。在将DS1302与STM32F103C8T6集成在一起时,可以通过以下步骤进行操作:

1.连接电路:将DS1302的引脚与STM32F103C8T6的引脚连接起来。常见的连接方式包括连接DS1302的CLK引脚到STM32F103C8T6的串行时钟引脚(SCK),将DS1302的RST引脚连接到STM32F103C8T6的复位引脚(RST),将DS1302的DAT引脚连接到STM32F103C8T6的串行数据引脚(SDA)。

2.编写代码:利用STM32F103C8T6的开发环境,编写代码实现与DS1302的通信。可以使用SPI或软件模拟SPI进行通信。首先,需要初始化SPI或模拟SPI,然后通过SPI或模拟SPI发送指令和数据给DS1302。指令包括读写时钟数据、读写控制寄存器等。可以通过查询DS1302的数据手册获取相应的指令。

3.读取时间:通过发送读取时钟数据的指令,可以从DS1302读取当前的时间。时间数据可以以二进制或BCD格式返回。可以根据需求进行相应的转换和处理,例如将BCD格式转换为十进制格式。

4.设置时间:通过发送写入时钟数据的指令,可以向DS1302设置新的时间。可以将新的时间数据以二进制或BCD格式发送给DS1302。

5.其他功能:DS1302还提供其他一些功能,例如定时器和RAM。通过相应的指令可以设置和读取这些功能。

总结来说,将DS1302与STM32F103C8T6集成在一起可以实现实时时钟的功能。通过合适的连接电路和编写代码,可以读取和设置DS1302的时间数据,以及使用其他附加功能。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/m0_63037616/article/details/144810625

使用特权

评论回复
沙发
公羊子丹| | 2025-1-5 08:01 | 只看该作者
楼主分享得很详细,代码也注释得很清楚,新手学习起来很友好!点赞!

使用特权

评论回复
板凳
周半梅| | 2025-1-5 08:01 | 只看该作者
STM32配合DS1302真是经典组合,感谢楼主的教程!刚好手上有块板子可以试试!

使用特权

评论回复
地板
帛灿灿| | 2025-1-5 08:01 | 只看该作者
有没有什么办法用更高效的方式初始化DS1302啊?感觉写寄存器有点繁琐!

使用特权

评论回复
5
童雨竹| | 2025-1-5 08:02 | 只看该作者
这个OLED显示时间的效果挺不错的,能不能再分享一下OLED的驱动代码呢?

使用特权

评论回复
6
万图| | 2025-1-5 08:02 | 只看该作者
我按照楼主的方法试了一下,时间能正常读取,但秒数偶尔会跳动异常,有没有遇到过类似问题?

使用特权

评论回复
7
Wordsworth| | 2025-1-5 08:02 | 只看该作者
楼主有没有试过用其他实时钟,比如DS3231?不知道跟DS1302比起来哪个更好用?

使用特权

评论回复
8
Pulitzer| | 2025-1-5 08:02 | 只看该作者
感谢分享!我想用LCD代替OLED显示时间,代码需要改很多吗?

使用特权

评论回复
9
Bblythe| | 2025-1-5 08:03 | 只看该作者
很棒的教程!不过如果想用I2C通信而不是模拟SPI,DS1302支持吗?

使用特权

评论回复
10
Uriah| | 2025-1-5 08:03 | 只看该作者
刚接触嵌入式,看了楼主的帖子感觉学习起来轻松了不少,赞一个!

使用特权

评论回复
11
Clyde011| | 2025-1-5 08:03 | 只看该作者
如果能加上温度显示就更好了,感觉做成温湿度时钟小项目也不错!

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

10

主题

20

帖子

0

粉丝