1 引入RTC定时器
RTC定时器被叫做实时时钟(real time clock)。 CPU内部有很多定时器,像看门狗WDT,PWM定时器,高精度定时器Timer等等, 只在“启动”即“通电时”运行,断电时停止。当然,如果时钟不能连续跟踪时间,则必须手动设置。那么当关机后就没办法自动计数统计时间了。
定时器的本质就是计数器,有向上计数,也有向下计数。RTC有一个与主机单独分离的电源,如纽扣电池(备用电池),即使主机电源关闭,它也保持计数定时功能。这也是为什么我们手机关机后时间还能保持准确。再比如以前的老诺基亚手机,拆掉电池就时间不准了,因为rtc电源被切断了,无法在计数,RTC定时器的计数器会被清0,需要手动设置当前时间。
RTC一般都是用纽扣电池给外部晶振和电路供电。
!
2 RTC定时器原理
以IMX6U芯片的RTC定时器为例,I.MX6U 内部也有 个 RTC 模块,但是不叫作“RTC”,而是叫做“SNVS”。
RTC模块结构图如下:
SNVS 分为两个子模块:SNVS_HP 和 SNVS_LP,也就是高功耗域(SNVS_HP)和低功耗域(SNVS_LP),这两个域的电源来源如下:
SNVS_LP:专用的 always-powered-on 电源域,系统主电源和备用电源都可以为其供电。
SNVS_HP:系统(芯片)电源。
系统主电源断电以后 SNVS_HP 也会断电,但是在备用电源支持下,SNVS_LP 是不会断电的,而且 SNVS_LP 是和芯片复位隔离开的,因此 SNVS_LP 相关的寄存器的值会一直保存着, 也就是low Power Domain是不受系统电源影响。
上图各个序号含义如下:
①、VDD_HIGH_IN 是系统(芯片)主电源,这个电源会同时供给给 SNVS_HP 和 SNVS_LP。
②、VDD_SNVS_IN 是纽扣电池供电的电源,这个电源只会供给给 SNVS_LP,保证在系统主电源 VDD_HIGH_IN 掉电以后 SNVS_LP 会继续运行。
③、SNVS_HP 部分。
④、SNVS_LP 部分,此部分有个 SRTC,这个就是要使用的 RTC。
SRTC 需要外界提供一个 32.768KHz 的时钟,I.MX6U-ALPHA 核心板上的 32.768KHz 的晶振就是提供这个时钟的。
3 RTC定时器寄存器
SNVS_SRTCMR[14:0]代表SRTC计数器的高15位
SNVS_SRTCLR[31:15]代表SRTC计数器的低17位
注意:是以 1970 年 1 月 1 日0点0分0秒为起点,加上经过的总秒数即可得到现在的时间点。
SNVS_HPCOMR[31], NPSWA_EN位,非特权软件访问控制位,如果非特权软件要访问 SNVS 的话此位必须为 1。
SNVS_LPCR[0], SRTC_ENV位,使能 STC 计数器。
4 RTC裸机源码展示
NXP 官方 SDK 包是针对 I.MX6ULL 编写的,因此文件 MCIMX6Y2.h中的结构体 SNVS_Type 里面的寄存器是不全的,我们需要在其中加入本章实验所需要的寄存器,修改 SNVS_Type 为如下所示:
/*!
* @addtogroup SNVS_Peripheral_Access_Layer SNVS Peripheral Access Layer
* @{
*/
/** SNVS - Register Layout Typedef */
/* zuozhongkai 2018/12/13 */
typedef struct {
__IO uint32_t HPLR; /**< SNVS_HP Lock register, offset: 0x0 */
__IO uint32_t HPCOMR; /**< SNVS_HP Command register, offset: 0x4 */
__IO uint32_t HPCR; /**< SNVS_HP Control register, offset: 0x8 */
__IO uint32_t HPSICR; /**< SNVS_HP Control register, offset: 0x8 */
__IO uint32_t HPSVCR;
__IO uint32_t HPSR;
__IO uint32_t HPSVSR;
__IO uint32_t HPHACIVR;
__IO uint32_t HPHACR;
__IO uint32_t HPRTCMR;
__IO uint32_t HPRTCLR;
__IO uint32_t HPTAMR;
__IO uint32_t HPTALR;
__IO uint32_t LPLR;
__IO uint32_t LPCR;
__IO uint32_t LPMKCR;
__IO uint32_t LPSVCR;
__IO uint32_t LPTGFCR;
__IO uint32_t LPTDCR;
__IO uint32_t LPSR;
__IO uint32_t LPSRTCMR;
__IO uint32_t LPSRTCLR;
__IO uint32_t LPTAR;
__IO uint32_t LPSMCMR;
__IO uint32_t LPSMCLR;
}SNVS_Type;
bsp_rtc.h
#ifndef _BSP_RTC_H
#define _BSP_RTC_H
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : bsp_rtc.c
作者 : 左忠凯
版本 : V1.0
描述 : RTC驱动头文件。
其他 : 无
论坛 : www.wtmembed.com
日志 : 初版V1.0 2019/1/3 左忠凯创建
***************************************************************/
#include "imx6ul.h"
/* 相关宏定义 */
#define SECONDS_IN_A_DAY (86400) /* 一天86400秒 */
#define SECONDS_IN_A_HOUR (3600) /* 一个小时3600秒 */
#define SECONDS_IN_A_MINUTE (60) /* 一分钟60秒 */
#define DAYS_IN_A_YEAR (365) /* 一年365天 */
#define YEAR_RANGE_START (1970) /* 开始年份1970年 */
#define YEAR_RANGE_END (2099) /* 结束年份2099年 */
/* 时间日期结构体 */
struct rtc_datetime
{
unsigned short year; /* 范围为:1970 ~ 2099 */
unsigned char month; /* 范围为:1 ~ 12 */
unsigned char day; /* 范围为:1 ~ 31 (不同的月,天数不同).*/
unsigned char hour; /* 范围为:0 ~ 23 */
unsigned char minute; /* 范围为:0 ~ 59 */
unsigned char second; /* 范围为:0 ~ 59 */
};
/* 函数声明 */
void rtc_init(void);
void rtc_enable(void);
void rtc_disable(void);
unsigned int rtc_coverdate_to_seconds(struct rtc_datetime *datetime);
unsigned int rtc_getseconds(void);
void rtc_setdatetime(struct rtc_datetime *datetime);
void rtc_getdatetime(struct rtc_datetime *datetime);
#endif
bsp_rtc.c
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名 : bsp_rtc.c
作者 : 左忠凯
版本 : V1.0
描述 : RTC驱动文件。
其他 : 无
论坛 : www.wtmembed.com
日志 : 初版V1.0 2019/1/3 左忠凯创建
***************************************************************/
#include "bsp_rtc.h"
#include "stdio.h"
/*
* 描述:初始化RTC
*/
void rtc_init(void)
{
/*
* 设置HPCOMR寄存器
* bit[31] 1 : 允许访问SNVS寄存器,一定要置1
* bit[8] 1 : 此位置1,需要签署NDA协议才能看到此位的详细说明,
* 这里不置1也没问题
*/
SNVS->HPCOMR |= (1 << 31) | (1 << 8);
#if 0
struct rtc_datetime rtcdate;
rtcdate.year = 2018U;
rtcdate.month = 12U;
rtcdate.day = 13U;
rtcdate.hour = 14U;
rtcdate.minute = 52;
rtcdate.second = 0;
rtc_setDatetime(&rtcdate); //初始化时间和日期
#endif
rtc_enable(); //使能RTC
}
/*
* 描述: 开启RTC
*/
void rtc_enable(void)
{
/*
* LPCR寄存器bit0置1,使能RTC
*/
SNVS->LPCR |= 1 << 0;
while(!(SNVS->LPCR & 0X01));//等待使能完成
}
/*
* 描述: 关闭RTC
*/
void rtc_disable(void)
{
/*
* LPCR寄存器bit0置0,关闭RTC
*/
SNVS->LPCR &= ~(1 << 0);
while(SNVS->LPCR & 0X01);//等待关闭完成
}
/*
* @description : 判断指定年份是否为闰年,闰年条件如下:
* @param - year: 要判断的年份
* @return : 1 是闰年,0 不是闰年
*/
unsigned char rtc_isleapyear(unsigned short year)
{
unsigned char value=0;
if(year % 400 == 0)
value = 1;
else
{
if((year % 4 == 0) && (year % 100 != 0))
value = 1;
else
value = 0;
}
return value;
}
/*
* @description : 将时间转换为秒数
* @param - datetime: 要转换日期和时间。
* @return : 转换后的秒数
*/
unsigned int rtc_coverdate_to_seconds(struct rtc_datetime *datetime)
{
unsigned short i = 0;
unsigned int seconds = 0;
unsigned int days = 0;
unsigned short monthdays[] = {0U, 0U, 31U, 59U, 90U, 120U, 151U, 181U, 212U, 243U, 273U, 304U, 334U};
for(i = 1970; i < datetime->year; i++)
{
days += DAYS_IN_A_YEAR; /* 平年,每年365天 */
if(rtc_isleapyear(i)) days += 1;/* 闰年多加一天 */
}
days += monthdays[datetime->month];
if(rtc_isleapyear(i) && (datetime->month >= 3)) days += 1;/* 闰年,并且当前月份大于等于3月的话加一天 */
days += datetime->day - 1;
seconds = days * SECONDS_IN_A_DAY +
datetime->hour * SECONDS_IN_A_HOUR +
datetime->minute * SECONDS_IN_A_MINUTE +
datetime->second;
return seconds;
}
/*
* @description : 设置时间和日期
* @param - datetime: 要设置的日期和时间
* @return : 无
*/
void rtc_setdatetime(struct rtc_datetime *datetime)
{
unsigned int seconds = 0;
unsigned int tmp = SNVS->LPCR;
rtc_disable(); /* 设置寄存器HPRTCMR和HPRTCLR的时候一定要先关闭RTC */
/* 先将时间转换为秒 */
seconds = rtc_coverdate_to_seconds(datetime);
SNVS->LPSRTCMR = (unsigned int)(seconds >> 17); /* 设置高16位 */
SNVS->LPSRTCLR = (unsigned int)(seconds << 15); /* 设置地16位 */
/* 如果此前RTC是打开的在设置完RTC时间以后需要重新打开RTC */
if (tmp & 0x1)
rtc_enable();
}
/*
* @description : 将秒数转换为时间
* @param - seconds : 要转换的秒数
* @param - datetime: 转换后的日期和时间
* @return : 无
*/
void rtc_convertseconds_to_datetime(u64 seconds, struct rtc_datetime *datetime)
{
u64 x;
u64 secondsRemaining, days;
unsigned short daysInYear;
/* 每个月的天数 */
unsigned char daysPerMonth[] = {0U, 31U, 28U, 31U, 30U, 31U, 30U, 31U, 31U, 30U, 31U, 30U, 31U};
secondsRemaining = seconds; /* 剩余秒数初始化 */
days = secondsRemaining / SECONDS_IN_A_DAY + 1; /* 根据秒数计算天数,加1是当前天数 */
secondsRemaining = secondsRemaining % SECONDS_IN_A_DAY; /*计算天数以后剩余的秒数 */
/* 计算时、分、秒 */
datetime->hour = secondsRemaining / SECONDS_IN_A_HOUR;
secondsRemaining = secondsRemaining % SECONDS_IN_A_HOUR;
datetime->minute = secondsRemaining / 60;
datetime->second = secondsRemaining % SECONDS_IN_A_MINUTE;
/* 计算年 */
daysInYear = DAYS_IN_A_YEAR;
datetime->year = YEAR_RANGE_START;
while(days > daysInYear)
{
/* 根据天数计算年 */
days -= daysInYear;
datetime->year++;
/* 处理闰年 */
if (!rtc_isleapyear(datetime->year))
daysInYear = DAYS_IN_A_YEAR;
else /*闰年,天数加一 */
daysInYear = DAYS_IN_A_YEAR + 1;
}
/*根据剩余的天数计算月份 */
if(rtc_isleapyear(datetime->year)) /* 如果是闰年的话2月加一天 */
daysPerMonth[2] = 29;
for(x = 1; x <= 12; x++)
{
if (days <= daysPerMonth[x])
{
datetime->month = x;
break;
}
else
{
days -= daysPerMonth[x];
}
}
datetime->day = days;
}
/*
* @description : 获取RTC当前秒数
* @param : 无
* @return : 当前秒数
*/
unsigned int rtc_getseconds(void)
{
unsigned int seconds = 0;
seconds = (SNVS->LPSRTCMR << 17) | (SNVS->LPSRTCLR >> 15);
return seconds;
}
/*
* @description : 获取当前时间
* @param - datetime: 获取到的时间,日期等参数
* @return : 无
*/
void rtc_getdatetime(struct rtc_datetime *datetime)
{
//unsigned int seconds = 0;
u64 seconds;
seconds = rtc_getseconds();
rtc_convertseconds_to_datetime(seconds, datetime);
}