STM32驱动DS1302

DS1302 概述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

时序图分析

在这里插入图片描述
理解说明:
      先说单字节写时序,首先,字节序开始。SCLK = 低电平,RST = 0,接着RST = 1,这里要注意datasheet中的时间,不过对于72Mhz MCU,IO翻转速度也能够满足。开始后,IO就要准备发送的命令寄存器了,先发送低位再发送高位可以看出,IO总是先准备好数据,接着SCLK上升沿,送入数据。 每次都是先发送命令寄存器,再去发送8bits数据。写入完毕后,SCLK = 0,RST = 0,表示字节序结束。
读时序:
      首先,字节序开始。SCLK = 0,RST = 0,RST = 1。在SCLK的时钟信号下,IO发送给DS1302的字节序,低位先发送,高位最后,这里和写字节序同理,IO先准备好数据,然后在SCLK的上升沿时候送入数据。==Warning:==读字节序,是下降沿读取,而且在写字节序的最后一个时钟脉冲的下降沿,已经是读取第一位了,可以看出总共有15个时钟脉冲,这与写时序16个脉冲还有点区别。最后在下降沿下,依次读取8bits数据。
      如果有不足之处,请欢迎改正。

读写时序

      可以看出,向控制寄存器0x8E地址写入0x00(取消写保护),然后再从0x8E地址读取出来为0x00,表示取消了写保护,此时我们可以向DS1302的可写寄存器进行写入值了。以下是逻辑分析仪抓取的波形。注意:发送、接收数据都是低位先发送,低位先接收。
写保护寄存器读写操作:
在这里插入图片描述

读取星期寄存器:0x8B
在这里插入图片描述

读取小时寄存(0x85)器数据在这里插入图片描述

寄存器表

在这里插入图片描述

重点寄存器描述

时钟暂停

在这里插入图片描述

AM-PM/12-24方式

在这里插入图片描述

写保护寄存器

在这里插入图片描述
比如,想要自己设置时间的时候或者朝RAM中写入东西前,bit7必须为0

慢速充电(Trickle charge)寄存器

这部分建议看手册,这部分可以通过设置寄存器来达到给备用电池充电的目的,电池可以是超级电容,也可以是微型锂电池。

其它部分

晶振和电源

在这里插入图片描述

BCD码转十进制计算

8421 BCD码是最基本和最常用的BCD码,它和四位自然二进制码相似,各位的权值为8、4、2、1,故称为有权BCD码。和四位自然二进制码不同的是,它只选用了四位二进制码中前10组代码,即用0000~1001分别代表它所对应的十进制数,余下的六组代码不用。
DS1302使用的是8421 BCD码,十进制范围是0~9。
在这里插入图片描述

举例:秒寄存器转为10进制值
第一种方法:

dstime.get_time.second = second/16*10+second%16;

第二种方法:

 dstime.get_time.second = ((second & 0x70)>>4)*10 + (second & 0x0F);

简单点说就是,取出一个字节的高4位作为十进制的十位,取出字节中的低4位作为十进制的个位,然后相加组合成秒,同样,其它的日期也是同理。

完整的程序

平台和配置:

芯片:STM32F103C8T6
IDE:STM32Cubemx,CLion
下载:ST-Link V2
逻辑分析仪:某宝30块8通道,软件:Logic2.4.7
注意事项:CLion中printf重定向和Keil中不一样问题!

main.c

#include "ds1302.h"
int main(void)
{
	ds1302_init();
	while(1)
	{
		print_times();
		HAL_Delay(500);
	}

}

ds1302.h

//
// Created by lxp on 2024/3/29.
//

#ifndef CLOCK_DS1302_H
#define CLOCK_DS1302_H
#include "stm32f1xx.h"

#define DS1302_GPIO  GPIOB
#define DS1302_DATA  GPIO_PIN_12
#define DS1302_CLK   GPIO_PIN_13
#define DS1302_RST   GPIO_PIN_14

#define DS1302_DATA_IN      { GPIOB->CRH &= 0xfff0ffff; GPIOB->CRH |= (uint32_t)(8<<16); }
#define DS1302_DATA_OUT     { GPIOB->CRH &= 0xfff0ffff; GPIOB->CRH |= (uint32_t)(3<<16); }

#define DS1302_DATA_LOW     HAL_GPIO_WritePin(DS1302_GPIO,DS1302_DATA,GPIO_PIN_RESET)
#define DS1302_DATA_HIGH    HAL_GPIO_WritePin(DS1302_GPIO,DS1302_DATA,GPIO_PIN_SET)

#define DS1302_CLK_LOW      HAL_GPIO_WritePin(DS1302_GPIO,DS1302_CLK,GPIO_PIN_RESET)
#define DS1302_CLK_HIGH     HAL_GPIO_WritePin(DS1302_GPIO,DS1302_CLK,GPIO_PIN_SET)

#define DS1302_RST_LOW      HAL_GPIO_WritePin(DS1302_GPIO,DS1302_RST,GPIO_PIN_RESET)
#define DS1302_RST_HIGH     HAL_GPIO_WritePin(DS1302_GPIO,DS1302_RST,GPIO_PIN_SET)
typedef struct _time{
    uint16_t year;
    uint8_t month;
    uint8_t day;
    uint8_t week;
    uint8_t hours;
    uint8_t minute;
    uint8_t second;
}_Time;

typedef struct _time_user{
    _Time set_time;
    _Time get_time;
}Time;

extern Time dstime;

//register addr
//写保护寄存器
#define WRITE_PROTECT  0x8e
#define WRITE_PROTECT_CLOSE     0x00
#define WRITE_PROTECT_OPEN      0x80

#define SECONDE_ADDR   0x80
#define MINUTE_ADDR    0x82
#define HOURS_ADDR     0x84
#define DAY_ADDR       0x86
#define MONTH_ADDR     0x88
#define WEEK_ADDR      0x8a
#define YEAR_ADDR      0x8c
#define CONTROL_ADDR   0x8e     //写保护, value: 0x00: 关闭写保护,0x80:打开写保护
#define CHARGE_ADDR    0x90     //充电寄存器
#define CLOCK_MUTI_WAY_ADDR  0xbe   //时钟多字节方式

#define  PAUSE_CLOCK   0x80     //时钟暂停
#define  RUNNING_CLOCK 0x00     //时钟运行
#define  NO_CHARGE     0x00     //禁止充电
//RAM addr
#define  RAM0_ADDR  0xc0


void delay(uint32_t time);
void ds1302_init(void);
void write_byte(uint8_t addr,uint8_t data);
uint8_t read_byte(uint8_t addr);
/**
 * @brief: 获取时间,存储在结构体中
 * @param: void
 * @return: none
 */
void get_time(void);
/**
* @brief: 打印时间
 * @param: none
 * @return: none
*/
void print_times(void);
/**
 * @brief: 设置时间
 * @param: char *
 * @return: none
 */
void set_time(void);

/**
 * @brief: 设置时间,把十进制数转换为DS1302各个寄存器对应的数值
 * @param year: 2000 ~ 2099
 * @param month: 1~12
 * @param day: 1~28/29,1~30,1~31
 * @param hours: 只支持24h, 0~23
 * @param minute: 0~59
 * @param second: 0~59
 * @return: none
 */
void set_time_params(uint16_t year,uint8_t month,uint8_t day,uint8_t week,uint8_t hours,uint8_t minute, uint8_t second);

#endif //CLOCK_DS1302_H

ds1302.c

//
// Created by lxp on 2024/3/29.

//
#include "ds1302.h"
#include "uart.h"
#include <stdio.h>
#include <string.h>
#include "retarget.h"
//日期: 年,月,日,时,分,秒
//2024年3月31日23:23:00
uint8_t set_time_value[7] = {0x24,0x03,0x31,0x33,0x23,0x00};
uint8_t day_ping_nia[12] = {31,28,31,30,31,30,31,31,30,31,30,31};    //平年365天
uint8_t day_run_nia[12]  = {31,29,31,30,31,30,31,31,30,31,30,31};    //闰年366天

//存储时间
Time dstime;

/**
 * @brief: ds1302 init
 * @param: none
 * @return: none
*/
void ds1302_init(void)
{
    GPIO_InitTypeDef  ds1302_gpio_init;
    uint8_t  i = 0;
    uint8_t write_protect_bit = 0;
    uint8_t ram0 = 0;
    //GPIO 初始化
    __HAL_RCC_GPIOB_CLK_ENABLE();
    ds1302_gpio_init.Pin = DS1302_CLK | DS1302_DATA | DS1302_RST;
    ds1302_gpio_init.Mode = GPIO_MODE_OUTPUT_PP;
    ds1302_gpio_init.Pull = GPIO_PULLUP;
    ds1302_gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(DS1302_GPIO,&ds1302_gpio_init);
    //复位
    DS1302_DATA_LOW;
    DS1302_RST_LOW;
    DS1302_CLK_LOW;

    //关闭芯片写保护位
    write_byte(CONTROL_ADDR,WRITE_PROTECT_CLOSE);
    //判断写保护位是否为关闭状态
    write_protect_bit = read_byte(CONTROL_ADDR);
    if(write_protect_bit == 0x00)
    {
//        printf("ds1302 is ok!\r\n");
    }
    else
    {
//        printf("ds1302 is error!\r\n");
    }
//    printf("rece write protect value = %02x.\r\n",write_protect_bit);
    //检测RAM0是否可以使用
    write_byte(RAM0_ADDR,0xfa);

    ram0 = read_byte(RAM0_ADDR);
    if(ram0 == 0xfa)
    {
       printf("ds1302 ram is ok!.\r\n");
    }
   printf("ram0 = %02x.\r\n",ram0);
    //判断DS1302是否正在运行
//    if(read_byte(SECONDE_ADDR) & 0x80)
//    {
        //设置时间
//        set_time_params(2024,5,31,3,23,59,55);
    set_time_params(2024,4,17,3,11,59,55);
//    }
}

/**
 * @brief: 向指定地址写入一个字节的数据
 * @param addr
 * @param data
 */
void write_byte(uint8_t addr,uint8_t data)
{
    uint8_t i = 0;
    //时序开始
    DS1302_RST_LOW;
    DS1302_CLK_LOW;
    delay(1);
    DS1302_RST_HIGH;
    //设置DATA pin为输出
    DS1302_DATA_OUT;
    addr = addr & 0xfe; //写方式,最后一位为0
    //发送命令及其地址字节
    for (i = 0; i < 8; i++)
    {
         //准备好数据,先发送低位数据
         if(addr & 0x01)
         {
             DS1302_DATA_HIGH;
         }
         else
         {
             DS1302_DATA_LOW;
         }
         //时序,从机上升沿接收到数据
         DS1302_CLK_LOW;
         DS1302_CLK_HIGH;
         //右移一位
         addr = addr >> 1;
    }
    //发送数据字节
    for (i = 0;i < 8;i++)
    {
        if(data & 0x01)
            DS1302_DATA_HIGH;
        else
            DS1302_DATA_LOW;
        DS1302_CLK_LOW;
        DS1302_CLK_HIGH;
        data = data >> 1;
    }
    //时序结束操作
    DS1302_CLK_LOW;
    DS1302_RST_LOW;
}

/**
 * @brief: 从指定地址读取一个字节的数据
 * @param addr
 * @return
 */
uint8_t read_byte(uint8_t addr)
{
    uint8_t rec_data = 0x00;
    uint8_t i = 0;
    //时序开始操作
    DS1302_RST_LOW;
    DS1302_CLK_LOW;
    delay(1);
    DS1302_RST_HIGH;
    //设置DATA pin为输出模式
    DS1302_DATA_OUT;
    //读方式,寄存器最后一位为1
    addr = addr | 0x01;
    //读时序,先发送要读的地址
    for (i = 0; i < 8; i++)
    {
        DS1302_CLK_LOW;
        if(addr & 0x01)
            DS1302_DATA_HIGH;
        else
            DS1302_DATA_LOW;

        DS1302_CLK_HIGH;
        addr = addr >> 1;


    }
    //设置DATA pin为输入模式
    DS1302_DATA_IN;
    for (i = 0; i < 8; i++)
    {
        DS1302_CLK_LOW;
        //rec_data = rec_data >> 1;
        if(HAL_GPIO_ReadPin(DS1302_GPIO,DS1302_DATA))
        {
            //rec_data = rec_data | 0x80;
            rec_data = rec_data | (1 << i);
        }
        else
        {
            rec_data = rec_data & (~(1 << i));
        }
        DS1302_CLK_HIGH;
    }
    //结束时序
    DS1302_CLK_LOW;
    DS1302_RST_LOW;
    return rec_data;
}

//初略延时
void delay(uint32_t time)
{
    uint32_t t = 0;
    while(time--)
    {
        t++;
    }
    t = 0;
}

/**
 * @brief: 设置时间,把十进制数转换为DS1302各个寄存器对应的数值
 * @param year: 2000 ~ 2099
 * @param month: 1~12
 * @param day: 1~28/29,1~30,1~31
 * @param hours: 只支持24h, 0~23
 * @param minute: 0~59
 * @param second: 0~59
 * @return: none
 */
void set_time_params(uint16_t year,uint8_t month,uint8_t day,uint8_t week,uint8_t hours,uint8_t minute, uint8_t second)
{
    memset(&dstime,0,sizeof(Time));
    year -= 2000;
    dstime.set_time.year    = year/10*16+year%10;
    dstime.set_time.month   = month/10*16+month%10;
    dstime.set_time.day     = day/10*16+day%10;
    dstime.set_time.week    = week/10*16+week%10;
    dstime.set_time.hours   = hours/10*16+hours%10;
//    dstime.set_time.hours = 0x33;
    dstime.set_time.minute  = minute/10*16+minute%10;
    dstime.set_time.second  = second/10*16+second%10;

//    printf("%d-%d-%d : %d-%d-%d \r\n",year,month,day,hours,minute,second);
//    printf("year   = %02x \r\n",dstime.set_time.year);
//    printf("month  = %02x \r\n",dstime.set_time.month);
//    printf("day    = %02x \r\n",dstime.set_time.day);
//    printf("hours  = %02x \r\n",dstime.set_time.hours);
//    printf("minute = %02x \r\n",dstime.set_time.minute);
//    printf("second = %02x \r\n",dstime.set_time.second);
    set_time();
}

/**
 * @brief: 设置时间
 * @param: none
 * @return: none
 */
void set_time(void)
{
    //关闭时钟
    write_byte(SECONDE_ADDR,PAUSE_CLOCK);
    //设置年,月,日,小时,分钟,秒
    write_byte(YEAR_ADDR,dstime.set_time.year);
    write_byte(MONTH_ADDR,dstime.set_time.month);
    write_byte(DAY_ADDR,dstime.set_time.day);
    write_byte(WEEK_ADDR,dstime.set_time.week);
    write_byte(HOURS_ADDR,dstime.set_time.hours);   //24h方式
    write_byte(MINUTE_ADDR,dstime.set_time.minute);
    write_byte(SECONDE_ADDR,dstime.set_time.second);
    //设置禁止充电,因为我使用的是纽扣电池
    write_byte(CHARGE_ADDR,NO_CHARGE);
}

/**
 * @brief: 获取时间,存储在结构体中
 * @param: void
 * @return: none
 */
void get_time(void)
{
    uint16_t  year = 0;
    uint8_t month = 0,day = 0,week = 0,hours = 0,minute = 0,second = 0;
    //获取年,月,日,时,分,秒
    year   =  read_byte(YEAR_ADDR);
    month  = read_byte(MONTH_ADDR);
    day    = read_byte(DAY_ADDR);
    week   = read_byte(WEEK_ADDR);
    hours  = read_byte(HOURS_ADDR);
    minute = read_byte(MINUTE_ADDR);
    second = read_byte(SECONDE_ADDR);
    /*
    printf("second = 0x%02x\r\n",second);
    printf("minute = 0x%02x\r\n",minute);
    printf("hours = 0x%02x\r\n",hours);
    printf("day = 0x%02x\r\n",day);
    printf("month = 0x%02x\r\n",month);
    printf("year = 0x%02x\r\n",year);
    */
    //转换为十进制数值
//    dstime.get_time.year   = 2000 + (year >> 4)*10 + (year & 0x0F);
//    dstime.get_time.month  = ((month & 0x10)>>4)*10 + (month & 0x0F);
//    dstime.get_time.day    = ((day & 0x30)>>4)*10 + (day & 0x0F);
//    dstime.get_time.week   = week & 0x0F;
//    dstime.get_time.hours  = ((hours & 0x20)>>5)*10 + ((hours & 0x10)>>4)*10 + (hours & 0x0F);
//    dstime.get_time.minute = ((minute & 0x70)>>4)*10 + (minute & 0x0F);
//    dstime.get_time.second = ((second & 0x70)>>4)*10 + (second & 0x0F);

    //另一种计算方法
    dstime.get_time.year   = 2000 + year/16*10+year%16;
    dstime.get_time.month  = month/16*10+month%16;
    dstime.get_time.day    = day/16*10+day%16;
    dstime.get_time.week   = week/16*10+week%16;
    dstime.get_time.hours  = (hours/16*10)+hours%16;
    dstime.get_time.minute = minute/16*10+minute%16;
    dstime.get_time.second = second/16*10+second%16;
}



/**
* @brief: 打印时间
 * @param: none
 * @return: none
*/
void print_times(void)
{
    get_time();
//    printf("%d-%d-%d : %d-%d-%d : %d \r\n",dstime.get_time.year,dstime.get_time.month,dstime.get_time.day,dstime.get_time.hours,dstime.get_time.minute,dstime.get_time.second,dstime.get_time.week);
}

其它知识拓展

根据年月日返回星期几
/**
 * @brief: 输入年月日,返回今天是周几,使用蔡勒公式
 * @param year
 * @param month
 * @param day
 * @return: 周几: 1~7
 */
int return_week_day(unsigned int year, unsigned int month, unsigned int day)
{
    int week = 0;
    unsigned int y = 0, c = 0, m = 0, d = 0;
    if (month == 1 || month == 2) {
        c = (year - 1) / 100;
        y = (year - 1) % 100;
        m = month + 12;
        d = day;
    } else {
        c = year / 100;
        y = year % 100;
        m = month;
        d = day;
    }
    week = y + y / 4 + c / 4 - 2 * c + 26 * ( m + 1 ) / 10 + d - 1; //蔡勒公式
    week = week >= 0 ? ( week % 7 ) : ( week % 7 + 7 ); //week为负时取模
    if ( week == 0 ) { //星期日不作为一周的第一天
        week = 7;
    }
    return week;
}
根据年、月,返回当前月有多少天
/**
 * @brief: 根据年、月,返回当前月多少天
 * @param year:
 * @param month: 1~12
 * @return: 返回当前月多少天
 */
uint8_t get_days_of_month(int year, int month)
{
    uint8_t days = 0;	//该年该月天数
    switch (month)
    {
        case 1:
        case 3:
        case 5:
        case 7:
        case 8:
        case 10:
        case 12:
            days = 31;
            break;
        case 4:
        case 6:
        case 9:
        case 11:
            days = 30;
            break;
        case 2:
            days = 28;	//平年
            if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) //闰年,29天
            {
                days = 29;
            }
            break;
    }
    return days;
}

遇到的问题

问题1

       在对秒寄存器进行读操作,数据输出:00 ,00,01,01,02,02,03,03,04,04,08,08,09,09,0a …
此处是以十六进制输出的,任务内代码如下:

void StartDefaultTask(void *argument)
{
  /* USER CODE BEGIN 5 */
  uint8_t second = 0;
  for(;;)
  {
        second = read_byte(SECONDE_ADDR_R);
        printf("second = %02x.\r\n",second);
        HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
        osDelay(pdMS_TO_TICKS(1000));
  }
  /* USER CODE END 5 */
}

在这里插入图片描述

    //设置DATA pin为输入模式
    DS1302_DATA_IN;
    for (i = 0; i < 8; i++)
    {
        DS1302_CLK_LOW;
        if(HAL_GPIO_ReadPin(DS1302_GPIO,DS1302_DATA))
        {
            rec_data = rec_data | 0x80;
        }
        else
        {
            rec_data = rec_data & 0x7f;
        }

        DS1302_CLK_HIGH;
        rec_data = rec_data >> 1;  //问题在于这个地方!!!

    }

      可以看出,每次的第一位右移了8次(移没了!),第二位右移了7次。秒为1时候,低位先发为0x80 >> 8 = 0,问题就出现在这里,右移的次数不对!!!

改正后代码:

    //设置DATA pin为输入模式
    DS1302_DATA_IN;
    for (i = 0; i < 8; i++)
    {
        DS1302_CLK_LOW;
        rec_data = rec_data >> 1;
        if(HAL_GPIO_ReadPin(DS1302_GPIO,DS1302_DATA))
        {
            rec_data = rec_data | 0x80;
        }
        else
        {
            rec_data = rec_data & 0x7f;
        }

        DS1302_CLK_HIGH;

    }

改进后的方法2,使用置位、清除某位来操作:

//设置DATA pin为输入模式
    DS1302_DATA_IN;
    for (i = 0; i < 8; i++)
    {
        DS1302_CLK_LOW;
        if(HAL_GPIO_ReadPin(DS1302_GPIO,DS1302_DATA))
        {
            rec_data = rec_data | (1 << i);
        }
        else
        {
            rec_data = rec_data & (~(1 << i));
        }
        DS1302_CLK_HIGH;
    }

结果正常了。
在这里插入图片描述

问题2

问题描述: 时间在24小时模式,走到23-59-59秒,变成了24-00-00,日期没有任何变化也不进位。看到手册里面有写到闰年校准功能,并且对于小于31天的月份自动调整,这肯定和手册写的功能是相悖的。
我就是这个地方看的疏忽了,理解上出现了偏差,我认为的,在24h模式下,bit4为第一个10小时标志位,bit5为第二个10小时标志位,比如表示>20小时的时间,bit4、bit5都要为1,所以,我初始化时间为23h,朝这个寄存器写入0x33(这是一个非法值!!!),然后芯片虽然小时部分能跑,但日期部分已经无法正常工作了!!!写入0x23后,DS1302就正常工作了。困扰了我几天的问题,竟然因为这一小点的疏忽毁于一旦。。。
在这里插入图片描述
找问题过程:
人家手册寄存器值的范围就是112,或者023。怎么可能出现24?苦思冥想,手册看了一遍又一遍,最后还是没解决,最后还是在群里问,然后大佬解答说我写入23h,写入的值是0x33引起的。确实手册里面说:bit4是10标志位,bit5是20标志位。我想着23h不就是0x33嘛,这也体现了我的疏忽、不仔细,0x33这个算是一个非法值,写入后,都不能正常工作了!!!写入0x23后立马正常了。。。啊啊啊,困扰了我几天的问题,竟然因为这一小点的疏忽毁于一旦。。。
反思:由于写入时序、读取时序都是我自己写的,并且调通了,我想着反正锻炼自己,就把日期转换十进制转bcd码,读出的BCD码转为十进制,这个部分我也写了。重点:就是这部分出现的问题:因为理解的偏差,写入小时的位我理解错了!自己的转换写的挺麻烦,通过移位和位运算来实现的。而别人的是通过乘除、取余运算来实现的,看起来简洁许多。

  • 33
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值