STM32 RTC 驱动代码(解决了使用HAL库函数导致的复位或者掉电后导致RTC年月日日期清零的问题)

问题背景:在RTC中断里面使用HAL库HAL_RTC_GetDate()和HAL_RTC_GetTime()来获取RTC时间日期。

源码如下图:

问题描述:单片机断电或者复位后的时分秒的时间可以接上,但年月日的日期就会被清零。如图:

导致问题的根本原因:

        通过查询 stm32f1xx_hal_rtc.c 里面的 HAL_RTC_GetDate()和HAL_RTC_GetTime()2个HAL库函数的源码

        在 HAL_RTC_GetDate()源码中,可以清楚看到日期是由DateToUpdate结构体获得的,然而通过逐步查询 DateToUpdate结构体来源发现DateToUpdate结构体和RTC计数器的值是没有关联的,所以单片机掉电或者复位后日期(年月日)就会清0 。

        在 HAL_RTC_GetTime()源码中,可以清楚看到时间是由RTC_ReadTimeCounter()获得的,也就是说时间是和RTC计数器的值关联的,所以单片机掉电或者复位后时间(时分秒)不会清0 ,依然可以正常走时。

解决办法:不用HAL库!!! 自己直接编写RTC相关的函数接口。

        定义日期时间结构体:

/* 时间结构体, 包括年月日周时分秒等信息 */
typedef struct
{
    uint16_t year;      /* 年 */
    uint8_t  month;     /* 月 */
    uint8_t  date;      /* 日 */
    uint8_t  week;      /* 周 */
    uint8_t hour;       /* 时 */
    uint8_t min;        /* 分 */
    uint8_t sec;        /* 秒 */
    uint32_t unix_time; /* 时间戳 */
} RTC_DateTimeTypeDef;

1.获取日期和时间(年月日 时分秒)接口函数 :

         RTC_GetTime(RTC_DateTimeTypeDef *calendar)

/**
功能:得到当前的时间(年月日 时分秒)。该函数不直接返回时间, 时间数据保存在calendar结构体里面
 */
void RTC_GetTime(RTC_DateTimeTypeDef *calendar)
{
    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_Get_Sec();
    calendar->unix_time = seccount;
    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_GetWeek(calendar->year, calendar->month, calendar->date); /* 获取星期 */
    
}

/*
获取RTC计数器CNT里面的值(既时间戳)
*/
uint32_t RTC_Get_Sec(void)
{
    uint16_t seccount_high1 = 0;
    uint16_t seccount_high2 = 0;
    uint16_t seccount_low = 0;
    uint32_t seccount = 0;
     /*摘抄于STM32 rtc库,优化可能在读CNT的时候出现了溢出*/
    seccount_high1 = RTC->CNTH; /* 得到计数器中的值高16位(秒钟数) */
    seccount_low = RTC->CNTL;   /* 得到计数器中的值低16位(秒钟数) */
    seccount_high2 = RTC->CNTH; /* 得到计数器中的值高16位(秒钟数) */
    if(seccount_high1 != seccount_high2) /*防止在获取秒钟低16位时,计数器刚好溢出导致获取的秒钟高16位就是错误的*/
    {
        seccount = (seccount_high2 << 16) | (RTC->CNTL & 0xFFFF );
    }
    else
    {
        seccount = (seccount_high1 << 16) | seccount_low;
    }
    return seccount;
}


/**
 * @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      起始时间为: 公元0年3月1日开始, 输入往后的任何日期, 都可以获取正确的星期
 *              使用 基姆拉尔森计算公式 计算, 原理说明见此贴:
 *              https://www.cnblogs.com/fengbohello/p/3264300.html
 * @param       syear : 年份
 * @param       smon  : 月份
 * @param       sday  : 日期
 * @retval      0, 星期天; 1 ~ 6: 星期一 ~ 星期六
 */
uint8_t RTC_GetWeek(uint16_t syear, uint8_t smonth, uint8_t sday)
{
    uint8_t week = 0;

    if (smonth < 3)
    {
        smonth += 12;
        --syear;
    }
    week = (sday + 1 + 2 * smonth + 3 * (smonth + 1) / 5 + syear + (syear >> 2) - syear / 100 + syear / 400) % 7;
    return week;
}

2.设置日期和时间(年月日 时分秒)接口函数:

        RTC_SetTime(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec) 

/**
 * @brief       设置时间, 包括年月日时分秒
 *   @note      以1970年1月1日为基准, 往后累加时间
 *              合法年份范围为: 1970 ~ 2105年
                HAL默认为年份起点为2000年
 * @param       syear : 年份
 * @param       smon  : 月份
 * @param       sday  : 日期
 * @param       hour  : 小时
 * @param       min   : 分钟
 * @param       sec   : 秒钟
 * @retval      0, 成功; 1, 失败;
 */
void RTC_SetTime(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec)   /* 设置时间 */
{
    uint32_t seccount = 0;
    seccount = RTC_Data2Sec(syear, smon, sday, hour, min, sec); /* 将年月日时分秒转换成总秒钟数 */
    __HAL_RCC_PWR_CLK_ENABLE(); /* 使能电源时钟 */
    __HAL_RCC_BKP_CLK_ENABLE(); /* 使能备份域时钟 */
    HAL_PWR_EnableBkUpAccess(); /* 取消备份域写保护 */
    /* 上面三步是必须的! */
    SET_BIT(RTC->CRL,RTC_CRL_CNF); /* 进入配置模式 */
//    RTC->CRL |= 1 << 4;         /* 进入配置模式 */ 
    RTC->CNTL = seccount & 0xFFFF;
    RTC->CNTH = seccount >> 16;
    CLEAR_BIT(RTC->CRL,RTC_CRL_CNF); /* 退出配置模式 */
//    RTC->CRL &= ~(1 << 4);      /* 退出配置模式 */
    while (!__HAL_RTC_ALARM_GET_FLAG(&gRTC_Handle, RTC_FLAG_RTOFF));       /* 等待RTC寄存器操作完成, 即等待RTOFF == 1 */
}

/**
 * @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_Data2Sec(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;
}

3.设置RTC闹钟接口函数:

RTC_SetAlarm(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec)


void RTC_SetAlarm(uint16_t syear, uint8_t smon, uint8_t sday, uint8_t hour, uint8_t min, uint8_t sec)
{
    uint32_t seccount = 0;
    seccount = RTC_Data2Sec(syear, smon, sday, hour, min, sec); /* 将年月日时分秒转换成总秒钟数 */    
    __HAL_RCC_PWR_CLK_ENABLE(); /* 使能电源时钟 */
    __HAL_RCC_BKP_CLK_ENABLE(); /* 使能备份域时钟 */
    HAL_PWR_EnableBkUpAccess(); /* 取消备份域写保护 */
    /* 设置闹钟 */
    SET_BIT(RTC->CRL,RTC_CRL_CNF); /* 进入配置模式 */
//    RTC->CRL |= 1 << 4;         /* 进入配置模式 */
    RTC->ALRL = seccount & 0xffff;
    RTC->ALRH = seccount >> 16;
    CLEAR_BIT(RTC->CRL,RTC_CRL_CNF); /* 退出配置模式 */
//    RTC->CRL &= ~(1 << 4);      /* 退出配置模式 */
    while (!__HAL_RTC_ALARM_GET_FLAG(&gRTC_Handle, RTC_FLAG_RTOFF));       /* 等待RTC寄存器操作完成, 即等待RTOFF == 1 */
    /* RTC 闹钟中断配置 */
    /* EXTI 配置 */
    HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 3, 3);
    /* 使能RTC闹钟中断 */
    HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值