stm32f103 RTC 实时时钟实验

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:以下是本篇文章正文内容,下面案例可供参考

一、RTC是什么?

RTC时钟,全称为Real-Time Clock(实时时钟),用于提供精确时间信息。RTC时钟是一个独立的定时器,它包含一个连续计数的计数器,在相应的软件配置下,可以提供时钟日历的功能(F1没有更高级别的单片机有这个功能)。通过寄存器配置可以产生1HZ的时钟信号作为精确的时间基准,计数器根据晶体振荡器的分频后的时钟信号进行递增,从而追踪时间的流逝。
简单点来说就是:
通过配置后RTC的计数器每隔1秒加一。
计数器不受单片机复位和断电影响(注意:需要适当的算法,和VBAT电源,下文有详细讲解)。

二、RTC配置步骤

/* 使能电源时钟 // 使能备份时钟 /
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
/
取消备份区写保护 /
在这里插入图片描述
/
开启外部低速振荡器 /
在这里插入图片描述在这里插入图片描述
/
等待LSE准备好 */在这里插入图片描述

/* 选择LSE,作为RTC时钟 */
在这里插入图片描述
在这里插入图片描述

/* 使能RTC时钟 /
在这里插入图片描述
/
等待RTC寄存器操作完成 *
在这里插入图片描述
在这里插入图片描述

/* 等待RTC寄存器同步 */
在这里插入图片描述
允许秒中断 */ 按照需求配置
允许闹钟中断 / 之后再次/ 等待RTC寄存器操作完成 /检查 RTOFF位在这里插入图片描述
/
允许配置 /相关位清置1
RTC控制寄存器低位(RTC_CRL)
此位必须由软件置’1’以进入配置模式,从而允许向RTC_CNT、RTC_ALR或RTC_PRL寄存器
写入数据。只有当此位在被置’1’并重新由软件清’0’后,才会执行写操作。
在这里插入图片描述
/
LSE频率为32.768Khz /配置预分频
在这里插入图片描述
在这里插入图片描述
/
配置更新 / 与允许配置相对应 相关位清零
/
等待RTC寄存器操作完成 */
将上面步骤转化为代为:
简单介绍一下功能,配置RTC为1HZ,启用秒中断和时钟中断;

void rtc_init(void)
{
    RCC->APB1ENR |= 0x01<<28;   /* PWREN    使能电源时钟 */
    RCC->APB1ENR |= 0x01<<27;   /* BKPEN    使能备份时钟 */
    PWR->CR |= 0x01<<8;         /* DBP      取消备份区写保护 */
    RCC->BDCR |= 1;             /* 开启外部低速振荡器 */
    while(!(RCC->BDCR & 0x02)); /* 等待LSE准备好 */
    RCC->BDCR &=~(3<<8);        /* 清零8/9位 方便下一步选择LSE,作为RTC时钟,因为选择时钟用了两位*/
    RCC->BDCR|=1<<8;            /* 选择LSE,作为RTC时钟 */
    RCC->BDCR |= 1<<15;         /* 使能RTC时钟 */
    while(!(RTC->CRL & (1 << 5)));/* 等待RTC寄存器操作完成 */
    while(!(RTC->CRL & (1 << 3)));/* 等待RTC寄存器同步 */
    RTC->CRH |= 1;              /* SECF = 1, 允许秒中断 */
    RTC->CRH |= 1 << 1;         /* ALRF = 1, 允许闹钟中断 */
    RTC->CRL |=1<<4;            /* 允许配置 */
    RTC->PRLH =0;               /*配置分频系数*/
    RTC->PRLL =32767;           /*配置分频系数*/
    RTC->CRL &= ~1;             /* 配置更新 */
    while(!(RTC->CRL & (1 << 5)));/* 等待RTC寄存器操作完成 */
    sys_nvic_init(0, 0, RTC_IRQn, 2);   /* 优先级设置 */
}
/**
 * @brief       设置NVIC(包括分组/抢占优先级/子优先级等)
 * @param       pprio: 抢占优先级(PreemptionPriority)
 * @param       sprio: 子优先级(SubPriority)
 * @param       ch: 中断编号(Channel)
 * @param       group: 中断分组
 *   @arg       0, 组0: 0位抢占优先级, 4位子优先级
 *   @arg       1, 组1: 1位抢占优先级, 3位子优先级
 *   @arg       2, 组2: 2位抢占优先级, 2位子优先级
 *   @arg       3, 组3: 3位抢占优先级, 1位子优先级
 *   @arg       4, 组4: 4位抢占优先级, 0位子优先级
 * @note        注意优先级不能超过设定的组的范围! 否则会有意想不到的错误
 * @retval      无
 */
void sys_nvic_init(uint8_t pprio, uint8_t sprio, uint8_t ch, uint8_t group)
{
    uint32_t temp;
    sys_nvic_priority_group_config(group);  /* 设置分组 */
    temp = pprio << (4 - group);
    temp |= sprio & (0x0f >> group);
    temp &= 0xf;                            /* 取低四位 */
    NVIC->ISER[ch / 32] |= 1 << (ch % 32);  /* 使能中断位(要清除的话,设置ICER对应位为1即可) */
    NVIC->IP[ch] |= temp << 4;              /* 设置响应优先级和抢断优先级 */
}

三、正点原子寄存器库

.h文件

#ifndef __RTC_H
#define __RTC_H

#include "./SYSTEM/sys/sys.h"


/* 时间结构体, 包括年月日周时分秒等信息 */
typedef struct
{
    uint8_t hour;       /* 时 */
    uint8_t min;        /* 分 */
    uint8_t sec;        /* 秒 */
    /* 公历年月日周 */
    uint16_t year;      /* 年 */
    uint8_t  month;     /* 月 */
    uint8_t  date;      /* 日 */
    uint8_t  week;      /* 周 */
} _calendar_obj;
extern _calendar_obj calendar;      /* 时间结构体 */


/* 静态函数 */
static uint8_t rtc_is_leap_year(uint16_t year); /* 判断当前年份是不是闰年 */
static long rtc_date2sec(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec);   /* 将年月日时分秒转换成秒钟数 */

/* 接口函数 */
uint8_t rtc_init(void);     /* 初始化RTC */
void rtc_get_time(void);    /* 获取RTC时间信息 */
uint16_t rtc_read_bkr(uint32_t bkrx);               /* 读取后备寄存器 */
void rtc_write_bkr(uint32_t bkrx, uint16_t data);   /* 写后备寄存器 */ 
uint8_t rtc_get_week(uint16_t year, uint8_t month, uint8_t day);    /* 根据年月日获取星期几 */
uint8_t rtc_set_time(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec);   /* 设置时间 */
uint8_t rtc_set_alarm(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec);  /* 设置闹钟时间 */

#endif

.c文件
值得注意的是
在STM32微控制器中,晶振(晶体振荡器)通常用于提供时钟信号。一旦晶振起振,它会持续振荡,直到断电。如果不断电,即使进行复位操作,晶振本身还是会保持振荡状态。但是,微控制器内部的时钟电路可能会因为复位而受到影响。
因此需要再备份区设置一个标志(LSE已经起振)。复位后如果检测到备份区的标志,就不对LSE和RTC进行操作,不然每次复位都会对计数产生影响。
在25℃是晶振大概需要2.5s起振。
在这里插入图片描述
晶振的起振过程
请添加图片描述

/**
 * @brief       RTC写入后备区域SRAM
 * @param       bkrx : 后备区寄存器编号,范围:0~41
 * @param       data : 要写入的数据,16位长度
 * @retval      无
 */
void rtc_write_bkr(uint32_t bkrx, uint16_t data)
{
    uint32_t temp = 0;
    PWR->CR |= 1 << 8;  /* 取消备份区写保护 */
    temp = BKP_BASE + 4 + bkrx * 4;
    (*(uint16_t *)temp) = data;
}

/**
 * @brief       RTC读取后备区域SRAM
 * @param       bkrx : 后备区寄存器编号,范围:0~41
 * @retval      读取到的值
 */
uint16_t rtc_read_bkr(uint32_t bkrx)
{
    uint32_t temp = 0;
    temp = BKP_BASE + 4 + bkrx * 4;
    return (*(uint16_t *)temp); /* 返回读取到的值 */
}

/**
 * @brief       RTC初始化
 *   @note
 *              默认尝试使用LSE,当LSE启动失败后,切换为LSI.
 *              通过BKP寄存器0的值,可以判断RTC使用的是LSE/LSI:
 *              当BKP0==0X5050时,使用的是LSE
 *              当BKP0==0X5051时,使用的是LSI
 *              注意:切换LSI/LSE将导致时间/日期丢失,切换后需重新设置.
 *
 * @param       无
 * @retval      0,成功
 *              1,进入初始化模式失败
 */
uint8_t rtc_init(void)
{
    /* 检查是不是第一次配置时钟 */
    uint16_t bkpflag = 0;
    uint16_t retry = 200;
    uint32_t tempreg = 0;
    uint32_t clockfreq = 0;

    RCC->APB1ENR |= 1 << 28;            /* 使能电源时钟 */
    RCC->APB1ENR |= 1 << 27;            /* 使能备份时钟 */
    PWR->CR |= 1 << 8;                  /* 取消备份区写保护 */

    bkpflag = rtc_read_bkr(0);          /* 读取BKP0的值 */
    if (bkpflag != 0X5050)              /* 之前使用的不是LSE */
    {
        RCC->BDCR |= 1 << 0;            /* 开启外部低速振荡器 */
        
        while (retry && ((RCC->BDCR & 0X02) == 0))  /* 等待LSE准备好 */
        {
            retry--;
            delay_ms(5);
        }
        
        tempreg = RCC->BDCR;            /* 读取BDCR的值 */
        tempreg &= ~(3 << 8);           /* 清零8/9位 */

        if (retry == 0)                 /* 开启LSE失败? */
        {
            RCC->CSR |= 1 << 0;         /* LSI使能 */
        
            while (!(RCC->CSR & 0x02)); /* 等待LSI就绪 */
            
            tempreg |= 1 << 9;          /* LSE开启失败,启动LSI. */
            clockfreq = 40000 - 1;      /* LSI频率约40Khz(参考F103数据手册说明) */
            rtc_write_bkr(0, 0X5051);   /* 标记已经初始化过了,使用LSI */
        }
        else
        {
            tempreg |= 1 << 8;          /* 选择LSE,作为RTC时钟 */
            clockfreq = 32768 - 1;      /* LSE频率为32.769Khz */
            rtc_write_bkr(0, 0X5050);   /* 标记已经初始化过了,使用LSE */
        }

        tempreg |= 1 << 15;             /* 使能RTC时钟 */
        RCC->BDCR = tempreg;            /* 重新设置BDCR寄存器 */

        while (!(RTC->CRL & (1 << 5))); /* 等待RTC寄存器操作完成 */

        while (!(RTC->CRL & (1 << 3))); /* 等待RTC寄存器同步 */

        RTC->CRH |= 1 << 0;             /* SECF = 1, 允许秒中断 */
        RTC->CRH |= 1 << 1;             /* ALRF = 1, 允许闹钟中断 */

        while (!(RTC->CRL & (1 << 5))); /* 等待RTC寄存器操作完成 */

        RTC->CRL |= 1 << 4;             /* 允许配置 */
        RTC->PRLH = 0X0000;
        RTC->PRLL = clockfreq;          /* 时钟周期设置(有待观察,看是否跑慢了?)理论值:32767 */
        RTC->CRL &= ~(1 << 4);          /* 配置更新 */

        while (!(RTC->CRL & (1 << 5))); /* 等待RTC寄存器操作完成 */

        if (bkpflag != 0X5051)          /* BKP0的内容既不是0X5050,也不是0X5051,说明是第一次配置,需要设置时间日期. */
        {
            rtc_set_time(2020, 4, 22, 6, 59, 55);   /* 设置时间 */
        }
    }
    else     /* 系统继续计时 */
    {
        retry = 30;     /* 避免卡死 */
        
        while ((!(RTC->CRL & (1 << 3)) && retry))   /* 等待RTC寄存器同步 */
        {
            delay_ms(5);
            retry--;
        }

        retry = 100;    /* 检测LSI/LSE是否正常工作 */
        
        tempreg = RTC->DIVL;            /* 读取DIVL寄存器的值 */
        while (retry)
        {
            delay_ms(5);
            retry--;
            
            if (tempreg != RTC->DIVL)   /* 对比DIVL和tempreg, 如果有差异, 则退出 */
            {
                break;                  /* DIVL != tempreg, 说明RTC在计数, 说明晶振没问题 */
            }
        }
        if (retry == 0)
        {
            rtc_write_bkr(0, 0XFFFF);   /* 标记错误的值 */
            RCC->BDCR = 1 << 16;        /* 复位BDCR */
            delay_ms(10);
            RCC->BDCR = 0;              /* 结束复位 */
            return 1;                   /* 初始化失败 */
        }
        else
        {
            RTC->CRH |= 0X01;           /* 允许秒中断 */

            while (!(RTC->CRL & (1 << 5))); /* 等待RTC寄存器操作完成 */
        }
    }

    sys_nvic_init(0, 0, RTC_IRQn, 2);   /* 优先级设置 */
    rtc_get_time();  /* 更新时间 */
    return 0;
}
/**
 * @brief       RTC时钟中断
 *   @note      秒钟中断服务函数,顺带处理闹钟标志
 *              根据RTC_CRL寄存器的 SECF 和 ALRF 位区分是哪个中断
 * @param       无
 * @retval      无
 */
void RTC_IRQHandler(void)
{
    if (RTC->CRL & (1 << 0))    /* SECF = 1, 秒钟中断 */
    {
        rtc_get_time();         /* 更新时间 */
        RTC->CRL &= ~(1 << 0);  /* SECF = 0, 清秒钟中断 */
        //printf("sec:%d\r\n", calendar.sec);   /* 打印秒钟 */
    }
    
    /* 顺带处理闹钟标志 */
    if (RTC->CRL & (1 << 1))    /* ALRF = 1, 闹钟标志 */
    {
        RTC->CRL &= ~(1 << 1);  /* ALRF = 0, 清闹钟标志 */

        /* 输出闹钟时间 */
        printf("Alarm Time:%d-%d-%d %d:%d:%d\n", calendar.year, calendar.month, calendar.date, calendar.hour, calendar.min, calendar.sec);
    }

    RTC->CRL &= ~(1 << 2);          /* OWF = 0, 清除溢出中断标志 */

    while (!(RTC->CRL & (1 << 5))); /* 等待RTC寄存器操作完成, 即等待RTOFF == 1 */
}

四、HAL库底层配置

算法都是相同的,我用HAL库只实现到实现RTC计时的功能

#ifndef __RTC_H__
#define __RTC_H__


#include "sys.h"
#include "time.h"

void rtc_init(void);
uint16_t rtc_read_bkr(uint8_t bkrx);
void rtc_write_bkr(uint8_t bkrx, uint16_t data);

#endif
#include "rtc.h"
#include "stdio.h"

RTC_HandleTypeDef rtc_handle = {0};
void rtc_init(void)
{
    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_RCC_BKP_CLK_ENABLE();
    HAL_PWR_EnableBkUpAccess();
    
    rtc_handle.Instance = RTC;
    rtc_handle.Init.AsynchPrediv = 32767;
    rtc_handle.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
    HAL_RTC_Init(&rtc_handle);
}

void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc)
{
    __HAL_RCC_RTC_ENABLE();
    
    RCC_OscInitTypeDef osc_initstruct = {0};
    RCC_PeriphCLKInitTypeDef periphclk_initstruct = {0};
    
    osc_initstruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
    osc_initstruct.LSEState = RCC_LSE_ON;
    osc_initstruct.PLL.PLLState = RCC_PLL_NONE;
    HAL_RCC_OscConfig(&osc_initstruct);

    periphclk_initstruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
    periphclk_initstruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
    HAL_RCCEx_PeriphCLKConfig(&periphclk_initstruct);
    
    HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 2, 2);
    HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
}

void RTC_Alarm_IRQHandler(void)
{

}

uint16_t rtc_read_bkr(uint8_t bkrx)
{
    uint32_t data = 0;
    data = HAL_RTCEx_BKUPRead(&rtc_handle, bkrx);
    return (uint16_t)data;
}

void rtc_write_bkr(uint8_t bkrx, uint16_t data)
{
    HAL_RTCEx_BKUPWrite(&rtc_handle, bkrx, data);
}

下面是将时间戳换算成时间的相关算法


/**
 * @brief       判断年份是否是闰年
 *   @note      月份天数表:
 *              月份   1  2  3  4  5  6  7  8  9  10 11 12
 *              闰年   31 29 31 30 31 30 31 31 30 31 30 31
 *              非闰年 31 28 31 30 31 30 31 31 30 31 30 31
 * @param       year : 年份
 * @retval      0, 非闰年; 1, 是闰年;
 */
static uint8_t rtc_is_leap_year(uint16_t year)
{
    /* 闰年规则: 四年闰百年不闰,四百年又闰 */
    if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

/**
 * @brief       设置时间, 包括年月日时分秒
 *   @note      以1970年1月1日为基准, 往后累加时间
 *              合法年份范围为: 1970 ~ 2105年
 * @param       syear : 年份
 * @param       smon  : 月份
 * @param       sday  : 日期
 * @param       hour  : 小时
 * @param       min   : 分钟
 * @param       sec   : 秒钟
 * @retval      0, 成功; 1, 失败;
 */
uint8_t rtc_set_time(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec)
{
    uint32_t seccount = 0;

    seccount = rtc_date2sec(syear, smon, sday, hour, min, sec); /* 将年月日时分秒转换成总秒钟数 */

    /* 设置时钟 */
    RCC->APB1ENR |= 1 << 28;    /* 使能电源时钟 */
    RCC->APB1ENR |= 1 << 27;    /* 使能备份时钟 */
    PWR->CR |= 1 << 8;          /* 取消备份区写保护 */
    /* 上面三步是必须的! */
    
    RTC->CRL |= 1 << 4;         /* 允许配置 */
    RTC->CNTL = seccount & 0xffff;
    RTC->CNTH = seccount >> 16;
    RTC->CRL &= ~(1 << 4);      /* 配置更新 */

    while (!(RTC->CRL & (1 << 5))); /* 等待RTC寄存器操作完成 */

    rtc_get_time();             /* 设置完之后更新一下数据 */
    return 0;
}

/**
 * @brief       设置闹钟, 具体到年月日时分秒
 *   @note      以1970年1月1日为基准, 往后累加时间
 *              合法年份范围为: 1970 ~ 2105年
 * @param       syear : 年份
 * @param       smon  : 月份
 * @param       sday  : 日期
 * @param       hour  : 小时
 * @param       min   : 分钟
 * @param       sec   : 秒钟
 * @retval      0, 成功; 1, 失败;
 */
uint8_t rtc_set_alarm(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec)
{
    uint32_t seccount = 0;

    seccount = rtc_date2sec(syear, smon, sday, hour, min, sec); /* 将年月日时分秒转换成总秒钟数 */

    /* 设置时钟 */
    RCC->APB1ENR |= 1 << 28;    /* 使能电源时钟 */
    RCC->APB1ENR |= 1 << 27;    /* 使能备份时钟 */
    PWR->CR |= 1 << 8;          /* 取消备份区写保护 */
    /* 上面三步是必须的! */
    RTC->CRL |= 1 << 4;         /* 允许配置 */
    RTC->ALRL = seccount & 0xffff;
    RTC->ALRH = seccount >> 16;
    RTC->CRL &= ~(1 << 4);      /* 配置更新 */

    while (!(RTC->CRL & (1 << 5))); /* 等待RTC寄存器操作完成 */

    return 0;
}

/**
 * @brief       得到当前的时间
 *   @note      该函数不直接返回时间, 时间数据保存在calendar结构体里面
 * @param       无
 * @retval      无
 */
void rtc_get_time(void)
{
    static uint16_t daycnt = 0;
    uint32_t seccount = 0;
    uint32_t temp = 0;
    uint16_t temp1 = 0;
    const uint8_t month_table[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};   /* 平年的月份日期表 */

    seccount = RTC->CNTH;       /* 得到计数器中的值(秒钟数) */
    seccount <<= 16;
    seccount += RTC->CNTL;

    temp = seccount / 86400;    /* 得到天数(秒钟数对应的) */

    if (daycnt != temp)         /* 超过一天了 */
    {
        daycnt = temp;
        temp1 = 1970;           /* 从1970年开始 */

        while (temp >= 365)
        {
            if (rtc_is_leap_year(temp1))    /* 是闰年 */
            {
                if (temp >= 366)
                {
                    temp -= 366;    /* 闰年的秒钟数 */
                }
                else
                {
                    break;
                }
            }
            else
            {
                temp -= 365;    /* 平年 */
            }
            
            temp1++;
        }

        calendar.year = temp1;  /* 得到年份 */
        temp1 = 0;

        while (temp >= 28)      /* 超过了一个月 */
        {
            if (rtc_is_leap_year(calendar.year) && temp1 == 1)  /* 当年是不是闰年/2月份 */
            {
                if (temp >= 29)
                {
                    temp -= 29; /* 闰年的秒钟数 */
                }
                else
                {
                    break;
                }
            }
            else
            {
                if (temp >= month_table[temp1])
                {
                    temp -= month_table[temp1]; /* 平年 */
                }
                else
                {
                    break;
                }
            }

            temp1++;
        }

        calendar.month = temp1 + 1;     /* 得到月份 */
        calendar.date = temp + 1;       /* 得到日期 */
    }

    temp = seccount % 86400;            /* 得到秒钟数 */
    calendar.hour = temp / 3600;        /* 小时 */
    calendar.min = (temp % 3600) / 60;  /* 分钟 */
    calendar.sec = (temp % 3600) % 60;  /* 秒钟 */
    calendar.week = rtc_get_week(calendar.year, calendar.month, calendar.date); /* 获取星期 */
}

/**
 * @brief       将年月日时分秒转换成秒钟数
 *   @note      输入公历日期得到星期(起始时间为: 公元0年3月1日开始, 输入往后的任何日期, 都可以获取正确的星期)
 *              使用 基姆拉尔森计算公式 计算, 原理说明见此贴:
 *              https://www.cnblogs.com/fengbohello/p/3264300.html
 * @param       syear : 年份
 * @param       smon  : 月份
 * @param       sday  : 日期
 * @retval      0, 星期天; 1 ~ 6: 星期一 ~ 星期六
 */
uint8_t rtc_get_week(uint16_t year, uint8_t month, uint8_t day)
{
    uint8_t week = 0;

    if (month < 3)
    {
        month += 12;
        --year;
    }

    week = (day + 1 + 2 * month + 3 * (month + 1) / 5 + year + (year >> 2) - year / 100 + year / 400) % 7;
    return week;
}

/**
 * @brief       将年月日时分秒转换成秒钟数
 *   @note      以1970年1月1日为基准, 1970年1月1日, 0时0分0秒, 表示第0秒钟
 *              最大表示到2105年, 因为uint32_t最大表示136年的秒钟数(不包括闰年)!
 *              本代码参考只linux mktime函数, 原理说明见此贴:
 *              http://www.openedv.com/thread-63389-1-1.html
 * @param       syear : 年份
 * @param       smon  : 月份
 * @param       sday  : 日期
 * @param       hour  : 小时
 * @param       min   : 分钟
 * @param       sec   : 秒钟
 * @retval      转换后的秒钟数
 */
static long rtc_date2sec(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec)
{
    uint32_t Y, M, D, X, T;
    signed char monx = smon;/* 将月份转换成带符号的值, 方便后面运算 */

    if (0 >=  (monx -= 2))  /* 1..12 -> 11,12,1..10 */
    { 
        monx += 12;         /* Puts Feb last since it has leap day */
        syear -= 1;
    }
    
    Y = (syear - 1) * 365 + syear / 4 - syear / 100 + syear / 400;  /* 公元元年1到现在的闰年数 */
    M = 367 * monx / 12 - 30 + 59;
    D = sday - 1;
    X = Y + M + D - 719162;                         /* 减去公元元年到1970年的天数 */
    T = ((X * 24 + hour) * 60 + min) * 60 + sec;    /* 总秒钟数 */
    return T;
}

总结

参考了正点原子的历程,和官方历程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值