stm32f103的RTC驱动程序

版权声明:本文为博主原创文章,转载请注明出处 https://blog.csdn.net/jickjiang/article/details/81385127

具体问题

这段时间在弄自己的一个《宝宝的气象站》的项目,使用了ESP8266的WiFi模块,使其能够将数据传输到OneNET后台并显示。网络通信使用的是OneNET提供的EDP通信组件,但是RTC遇到了问题。从NTP服务器获取的时间与本地RTC获取的时间有差异。NTP使用的是1970年1月1日作为起始时间点。本地RTC的起始点是2000年1月1日。这样就需要将NTP获取的秒减去两个时间的间隔。

代码

mip_rtc.h

/**
*****************************************************************************
* @文  件: mip_rtc.h 
* @作  者: 00Jackey
* @版  本: V1.0.0
* @日  期: 8-May-2018
* @描  述: RTC驱动接口文件
******************************************************************************
* @修改记录:
*   2018/05/08:初始版本
*   2018/08/02: 修改起始日期
*
******************************************************************************
**/

#ifndef _MIP_RTC_H_
#define _MIP_RTC_H_

#ifdef _cplusplus
    extern "C" {
#endif

//C库
#include <stdint.h>

//宏定义
#define USE_EXT_RCC     1

#define MIN_TOTAL_SEC   (60)                                                // 每一分的秒总数         
#define HOUR_TOTAL_SEC  (60*60)                                             // 每一时的秒总数
#define DAY_TOTAL_SEC   (24*60*60)                                          // 每一天的秒总数

#define NTP_RTC_DIFF    (10957*DAY_TOTAL_SEC)                               // 1970.1.1 ~ 2000.1.1 间隔秒数

//时间结构体
typedef struct {
    uint16_t year;  /* 1..4095 */
    uint8_t  month; /* 1..12 */
    uint8_t  mday;  /* 1..31 */
    uint8_t  wday;  /* 0..6, Sunday = 0*/
    uint8_t  hour;  /* 0..23 */
    uint8_t  min;   /* 0..59 */
    uint8_t  sec;   /* 0..59 */
}RTC_TIME_STRUCT;

//接口函数
void RTCx_init(void);
void RTCx_set(RTC_TIME_STRUCT curTime);
void RTCx_get(RTC_TIME_STRUCT *pCurTime);
void RTCx_cnt2Struct(uint32_t cnt, RTC_TIME_STRUCT *t);
void RTCx_struct2Cnt(const RTC_TIME_STRUCT t, uint32_t* pCnt);


#ifdef _cplusplus
    }
#endif

#endif

mip_rtc.c

/**
*****************************************************************************
* @文  件: mip_rtc.c 
* @作  者: 00Jackey
* @版  本: V1.0.0
* @日  期: 8-May-2018
* @描  述: RTCx驱动文件
******************************************************************************
* @修改记录:
*   2018/05/08:初始版本
*    
*
******************************************************************************
* @说  明:地球绕日运行周期即一个回归年是365天5小时48分46秒(合365.24219天),公历把
*       一年定为365天,所以计算起来,每400年会多出97天,也就是说,要设置97个闰年。
*       而每4年设一个闰年,400年会有100个闰年,就多出3个闰年。为了解决这个问题,除了
*       规定“能被4整除”这个条件外,还规定“凡是整百的年份,要能被400整除”的才算闰年。
*       这样规定下来,1700、1800、1900都不是闰年,而1600、2000则是闰年。
*       如此规定,刚好满足了400年中设97个闰年的客观规律。
**/

//接口头文件
#include "mip_rtc.h"

//硬件头文件
#include "hardware.h"

//宏定义
#define RTC_WRITE_FLAG  0xA5A5                                              // 随机数,用于指示备份数据是否曾经备份过   
#define FIRST_YEAR      2000                                                // 日历开始年份
#define FIRST_MONTH     1                                                   // 日历开始月
#define FIRST_MDAY      1                                                   // 日历开始日
#define FIRST_WDAY      6                                                   // 0 = 星期日


//静态函数
static int32_t RTCx_getDayInteval(int32_t year_start, int32_t month_start, int32_t day_start,\
                                  int32_t year_end, int32_t month_end, int32_t day_end);

//静态变量
const int16_t mon_yday[][13] =
{
    /* Normal years.  */
    { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
    /* Leap years.  */
    { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
};

const static RTC_TIME_STRUCT sDefaultTime = 
{
    .year = FIRST_YEAR,
    .month = FIRST_MONTH,
    .mday = FIRST_MDAY,
    .wday = FIRST_WDAY,
    .hour = 0,
    .min = 0,
    .sec = 0
};


//判断一个年份是否为闰年,是就返回1,不是就返回0
int isLeapYear(int year)
{
    return( (year%4 == 0 && year%100 != 0) || (year%400 == 0) );
}

//获取一年的天数
int getDaysForYear(int year)
{
    return (isLeapYear(year)?366:365);
}

//根据秒数计算日期
void getDate(int second, int* year, int* month, int* day)
{
    int days = second / DAY_TOTAL_SEC;
    int curYear = FIRST_YEAR;
    int leftDays = days;

    //calc year
    int daysCurYear = getDaysForYear(curYear);
    while (leftDays >= daysCurYear)
    {
        leftDays -= daysCurYear;
        curYear++;
        daysCurYear = getDaysForYear(curYear);
    }
    *year = curYear;

    //calc month and day
    int isLeepYear = isLeapYear(curYear);
    for (int i = 1; i < 13; i++)
    {
        if (leftDays < mon_yday[isLeepYear][i])
        {
            *month = i;
            *day = leftDays - mon_yday[isLeepYear][i-1] + 1;
            break;
        }
    }
}

//计算时间
void getTime(int seconds, int* hour, int* minute, int* second)
{
    int leftSeconds = seconds % DAY_TOTAL_SEC;
    *hour = leftSeconds / HOUR_TOTAL_SEC;
    *minute = (leftSeconds % HOUR_TOTAL_SEC) / MIN_TOTAL_SEC;
    *second = leftSeconds % MIN_TOTAL_SEC;
}


/*
******************************************************************************
*   函 数 名: RTCx_cnt2Struct
*   功能说明: 将 RTC 计数器格式时间转换为日月年格式时间
*   形    参: cnt: RTC 计数器格式时间,单位为秒,它将会转换为日月年格式时间
*             *t:  保存RTC 计数器格式时间转换出来的日月年格式时间   
*   返 回 值: 无
******************************************************************************
*/
void RTCx_cnt2Struct(uint32_t cnt, RTC_TIME_STRUCT *pTime)
{
    int tYear,tMonth,tDay,tHour,tMin,tSec;

    getDate(cnt,&tYear,&tMonth,&tDay);
    getTime(cnt,&tHour,&tMin,&tSec);

    pTime->year = tYear;
    pTime->month = tMonth;
    pTime->mday = tDay;

    pTime->hour = tHour;
    pTime->min = tMin;
    pTime->sec = tSec;
}

/*
******************************************************************************
*   函 数 名: RTCx_struct2Cnt
*   功能说明: 将日月年结构时间转换为 RTC 计数器格式时间
*   形    参: *t,指向日月年结构的时间
*   返 回 值: RTC 计数器格式时间
******************************************************************************
*/
void RTCx_struct2Cnt(const RTC_TIME_STRUCT uTime, uint32_t* pCnt)
{
    int32_t tDay = 0,tSec = 0;

    tDay = RTCx_getDayInteval(FIRST_YEAR,FIRST_MONTH,FIRST_MDAY,
                              uTime.year,uTime.month,uTime.mday);

    tSec = tDay * DAY_TOTAL_SEC;
    *pCnt = tSec;
}

/*
******************************************************************************
*   函 数 名: RTCx_getDayInteval
*   功能说明: 获取时间间隔 2-1 = 1,网上抄的,具体细节待处理
*   形    参: 起始年月日 , 结束年月日
*   返 回 值: 天的差值
******************************************************************************
*/
int32_t RTCx_getDayInteval(int32_t year_start, int32_t month_start, int32_t day_start,\
                                  int32_t year_end, int32_t month_end, int32_t day_end)
{
    int32_t y2, m2, d2;
    int32_t y1, m1, d1;

    m1 = (month_start + 9) % 12;
    y1 = year_start - m1/10;
    d1 = 365*y1 + y1/4 - y1/100 + y1/400 + (m1*306 + 5)/10 + (day_start - 1);

    m2 = (month_end + 9) % 12;
    y2 = year_end - m2/10;
    d2 = 365*y2 + y2/4 - y2/100 + y2/400 + (m2*306 + 5)/10 + (day_end - 1);

    return (d2-d1);
}

/*
******************************************************************************
*   函 数 名: RTCx_config
*   功能说明: 配置 RTC
*   形    参: 无
*   返 回 值: 0:成功 1:失败
******************************************************************************
*/
int8_t RTCx_config(void)
{
    uint8_t errCnt = 0;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);// 使能 PWR 和 BKP 时钟 
    PWR_BackupAccessCmd(ENABLE);                                            // 使能 BKP Domain 的访问                                                                        
    RCC_LSEConfig(RCC_LSE_ON);                                              // 使能外部时钟 LSE (32.768KHz)

    while ((RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)&& (errCnt < 250)){ // 等待外部时钟 LSE 就绪
        errCnt++;
        Delay_mSec(10);
    }
    if(errCnt >= 250)
        return 1;                                                           //初始化时钟失败,晶振有问题

    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);                                 // 选择外部时钟 LSE 为 RTC 时钟
    RCC_RTCCLKCmd(ENABLE);                                                  // 使能 RTC 时钟
    RTC_WaitForSynchro();                                                   // 等待 RTC 寄存器同步
    RTC_WaitForLastTask();                                                  // 等待上次写入 RTC 寄存器完成
    RTC_SetPrescaler(32767);                                                // 设置 RTC 间隔为1秒 计算公式为 RTC period = RTCCLK / RTC_PR = (32.768 KHz) / (32767 + 1)   
    RTC_WaitForLastTask();                                                  // 等待上次写入 RTC 寄存器完成

    return 0;
}

/*
******************************************************************************
*   函 数 名: RTC_setCounter
*   功能说明: 将 RTC 计数器格式时间写入 RTC 计数器寄存器
*   形    参: time_set: 将要写入的 RTC 计数器格式时间
*   返 回 值: 无
******************************************************************************
*/
void RTC_setCounter(uint32_t time_set)
{ 
    RTC_SetCounter(time_set);                                               // 设置 RTC 计数器寄存器
    RTC_WaitForLastTask();                                                  // 等待上次写入 RTC 寄存器完成
}


/*
******************************************************************************
*   函 数 名: RTCx_setTime
*   功能说明: RTC设置时间
*   形    参: curTime:时间参数
*   返 回 值: 无
******************************************************************************
*/
void RTCx_setTime(RTC_TIME_STRUCT curTime)
{
    uint32_t cnt = 0;
    RTC_TIME_STRUCT ts;

    RTCx_struct2Cnt(curTime,&cnt);                                      // 将日月年格式时间转换为 RTC 计数器格式时间 
    RTCx_cnt2Struct(cnt, &ts);                                          // 通过 RTC 计数器格式时间得到日月年格式时间的其它变量的值,如星期几   
    RTC_setCounter(cnt);    
}

/*
******************************************************************************
*   函 数 名: RTCx_get
*   功能说明: RTC获取时间
*   形    参: pCurTime:获取的时间
*   返 回 值: 无
******************************************************************************
*/
void RTCx_get(RTC_TIME_STRUCT *pCurTime)
{
    uint32_t cnt = 0;
    cnt = RTC_GetCounter();
    RTCx_cnt2Struct(cnt, pCurTime);
}

/*
******************************************************************************
*   函 数 名: RTCx_init
*   功能说明: RTC初始化
*   形    参: 无
*   返 回 值: 无
******************************************************************************
*/
void RTCx_init(void)
{
    if(1== RTCx_config()){
        return ;                                                            //时钟异常就退出           
    }

    if(BKP_ReadBackupRegister(BKP_DR1) != RTC_WRITE_FLAG){
        RTCx_setTime(sDefaultTime);   
        BKP_WriteBackupRegister(BKP_DR1, RTC_WRITE_FLAG);                   // 往备份数据写入 RCC_BACKUP_DATA,用于指示备份区域数据是否有效
    }

    RTC_WaitForSynchro();                                                   // 等待 RTC 寄存器同步
    RCC_ClearFlag();                                                        // 清除 RCC 复位标志(RCC reset flags)
    RTC_ExitConfigMode();                                                   // 退出配置模式 
}

OneNET网络校时

/*
******************************************************************************
*   函 数 名: CLOCK_Task
*   功能说明: 网络校时
*   形    参: 无
*   返 回 值: 无
******************************************************************************
*/
void CLOCK_Task(void *pvParameter)
{

#if(NET_TIME_EN == 1)
    RTC_TIME_STRUCT uTime;
    uint32_t second = 0, second_pre = 0, err_count = 0;                     //second是实时时间,second_pre差值比较。err_count获取计时
    _Bool get_net_time = 1;
#endif

    while(1)
    {

#if(NET_TIME_EN == 1)
        if(get_net_time)                                                    //需要获取时间
        {

            if(++err_count >= 6000)                                         //十分钟还获取不到则重新获取
            {
                err_count = 0;
                net_device_info.net_time = 0;
                onenet_info.net_work = 0;
                NET_DEVICE_ReConfig(0);
                onenet_info.connect_ip = 0;
            }

            if(net_device_info.net_time)
            {
                second = RTC_GetCounter() + NTP_RTC_DIFF;

                if(((net_device_info.net_time <= second + 300) && (net_device_info.net_time >= second - 300)) || (second <= (600 + NTP_RTC_DIFF)))
                {                                                                   //如果在±5分钟内,则认为时间正确
                    RTC_SetCounter(net_device_info.net_time - NTP_RTC_DIFF + 4);    //设置RTC时间,加4是补上大概的时间差

                    get_net_time = 0;
                    err_count = 0;
                }
            }
        }

        second = RTC_GetCounter();                                          //获取秒值

        if(second > second_pre)
        {
            second_pre = second;
            RTCx_get(&uTime);

            if(uTime.hour == 0 && uTime.min == 0 && uTime.sec == 0)         //每天0点时,更新一次时间
            {
                get_net_time = 1;
                net_device_info.net_time = 0;
                onenet_info.net_work = 0;
                NET_DEVICE_ReConfig(0);
                onenet_info.connect_ip = 0;
            }
        }
#endif

        Delay_nTick(20);                                                    //挂起任务100ms

    }

}

猜你喜欢

转载自blog.csdn.net/jickjiang/article/details/81385127