这段时间在弄自己的一个《宝宝的气象站》的项目,使用了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
//宏定义
#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
}
}