ARM 实时时钟 RTC
一、何为实时时钟
(1) real time clock,真实时间,就是所谓的xx年x月x日x时x分x秒星期x.
(2) RTC是 SoC 中一个内部外设,RTC 有自己独立的晶振提供 RTC 时钟源(32.768KHz),内部有一些寄存器用来记录时间(年月日时分秒星期)。一般情况下为了在系统关机时时间仍然在走,还会给 RTC 提供一个电池供电。
二、S5PV210 实时时钟的结构框图
(1) 时间寄存器7个
(2) 闹钟发生器
三、闹钟发生器
(1) 可以定闹钟时间,到时间会产生 RTC alarm interrupt,通知系统闹钟定时到了。
(2) 闹钟定时是定的时间点,而 timer 定时是定的时间段。
四、S5PV210 实时时钟的主要寄存器
(1) INTP 中断挂起寄存器
(2) RTCCON RTC控制寄存器
(3) RTCALM ALMxxx 闹钟功能有关的寄存器
(4) BCDxxx 时间寄存器
五、BCD码
(1 )RTC 中所有的时间(年月日时分秒星期,包括闹钟)都是用 BCD 码编码的。
(2) BCD 码本质上是对数字的一种编码。用来解决这种问题:由 56 得到 0x56(或者反过来)。也就是说我们希望十进制的56,可以被编码成 56(这里的 56 不是十进制 56,而是两个数字 5 和 6 ).
(3) BCD 码的作用在于可以将十进制数拆成组成这个十进制数的各个数字的编码,变成编码后就没有位数的限制了。譬如我有一个很大的数123456789123456789,如果这个数纯粹当数字肯定超出了 int 的范围,计算机无法直接处理。要想让计算机处理这个数,计算机首先得能表达这个数,表达的方式就是先把这个数转成对应的 BCD 码(123456789123456789)
(4) BCD 码在计算机中可以用十六进制的形式来表示。也就是说十进制的56转成BCD码后是56,在计算机中用0x56来表达(暂时存储与运算)。
(5 ) 需要写 2 个函数,一个是 bcd 转十进制,一个是十进制转 bcd。当我们要设置时间时(譬如要设置为 23 分),我们需要将这个 23 转成 0x23 然后再赋值给相应的寄存器 BCDMIN;当我们从寄存器 BCDMIN 中读取一个时间时(譬如读取到的是 0x59),需要将之当作 BCD 码转成十进制再去显示(0x59 当作BCD码就是 59,转成十进制就是 59,所以显示就是 59 分)。
六、RTC编程实战1
1、设置时间与读取显示时间
(1) 为了安全,默认情况下 RTC 读写是禁止的,此时读写 RTC 的时间是不允许的;当我们要更改 RTC 时间时,应该先打开 RTC 的读写开关,然后再进行读写操作,操作完了后立即关闭读写开关。
(2) 读写 RTC 寄存器时,一定要注意 BCD 码和十进制之间的转换。
(3) 年的问题。S5PV210 中做了个设定,BCDYEAR 寄存器存的并不是完整的年数(譬如 2015 年),而是基于 2000 年的偏移量来存储的,譬如 2015 年实际存的就是 15(2015-2000).还有些 RTC 芯片是以 1970 年(貌似)为基点来记录的。
#include "rtc.h"
#define RTC_BASE (0xE2800000)
#define rINTP (*((volatile unsigned long *)(RTC_BASE + 0x30)))
#define rRTCCON (*((volatile unsigned long *)(RTC_BASE + 0x40)))
#define rTICCNT (*((volatile unsigned long *)(RTC_BASE + 0x44)))
#define rRTCALM (*((volatile unsigned long *)(RTC_BASE + 0x50)))
#define rALMSEC (*((volatile unsigned long *)(RTC_BASE + 0x54)))
#define rALMMIN (*((volatile unsigned long *)(RTC_BASE + 0x58)))
#define rALMHOUR (*((volatile unsigned long *)(RTC_BASE + 0x5c)))
#define rALMDATE (*((volatile unsigned long *)(RTC_BASE + 0x60)))
#define rALMMON (*((volatile unsigned long *)(RTC_BASE + 0x64)))
#define rALMYEAR (*((volatile unsigned long *)(RTC_BASE + 0x68)))
#define rRTCRST (*((volatile unsigned long *)(RTC_BASE + 0x6c)))
#define rBCDSEC (*((volatile unsigned long *)(RTC_BASE + 0x70)))
#define rBCDMIN (*((volatile unsigned long *)(RTC_BASE + 0x74)))
#define rBCDHOUR (*((volatile unsigned long *)(RTC_BASE + 0x78)))
#define rBCDDATE (*((volatile unsigned long *)(RTC_BASE + 0x7c)))
#define rBCDDAY (*((volatile unsigned long *)(RTC_BASE + 0x80)))
#define rBCDMON (*((volatile unsigned long *)(RTC_BASE + 0x84)))
#define rBCDYEAR (*((volatile unsigned long *)(RTC_BASE + 0x88)))
#define rCURTICCNT (*((volatile unsigned long *)(RTC_BASE + 0x90)))
#define rRTCLVD (*((volatile unsigned long *)(RTC_BASE + 0x94)))
/**********************************************************************************/
#define BIT_LOCATION_RTCCON_RTCEN_CONTROL (0b1 << 0)
#define RTCCON_RTC_CONTROL_ENABLE (0b1 << 0)
#define RTCCON_RTC_CONTROL_DISABLE (0b0 << 0)
//函数功能:把十进制 num 转成 bcd 码,譬如把 56 转成 0x56
static unsigned int num_2_bcd(unsigned int num)
{
//第一步,把56拆分成 5 和 6
//第二步,把 5 和 6组合成 0x56
return ( ((num / 10) << 4) | (num % 10));
}
//函数功能:把 bcd 码bcd,转成十进制,譬如把 0x56 转成 56
static unsigned int bcd_2_num(unsigned int bcd)
{
//第一步,把 0x56 拆分成 5 和 6
//第二步,把 5 和 6 组合成56
return ( ((bcd & 0xf0) >> 4) * 10 + (bcd & 0xf));
}
void rtc_set_time(const struct rtc_time *p)
{
//第一步,打开 RTC 读写开关
rRTCCON &= ~(BIT_LOCATION_RTCCON_RTCEN_CONTROL);
rRTCCON |= (RTCCON_RTC_CONTROL_ENABLE);
//第二步,写 RTC 时间寄存器
rBCDYEAR = num_2_bcd(p->year - 2000);
rBCDMON = num_2_bcd(p->month);
rBCDDATE= num_2_bcd(p->date);
rBCDHOUR = num_2_bcd(p->hour);
rBCDMIN = num_2_bcd(p->minute);
rBCDSEC = num_2_bcd(p->second);
rBCDDAY = num_2_bcd(p->day);
//最后一个,关上 RTC 的读写开关
rRTCCON &= ~(BIT_LOCATION_RTCCON_RTCEN_CONTROL);
rRTCCON |= (RTCCON_RTC_CONTROL_DISABLE);
}
void rtc_get_time(struct rtc_time *p)
{
//第一步,打开 RTC 读写开关
rRTCCON &= ~(BIT_LOCATION_RTCCON_RTCEN_CONTROL);
rRTCCON |= (RTCCON_RTC_CONTROL_ENABLE);
//第二步,读 RTC 时间寄存器
p->year = bcd_2_num(rBCDYEAR) + 2000;
p->month = bcd_2_num(rBCDMON);
p->date = bcd_2_num(rBCDDATE);
p->hour = bcd_2_num(rBCDHOUR);
p->minute = bcd_2_num(rBCDMIN);
p->second = bcd_2_num(rBCDSEC);
p->day = bcd_2_num(rBCDDAY);
//最后一个,关上 RTC 的读写开关
rRTCCON &= ~(BIT_LOCATION_RTCCON_RTCEN_CONTROL);
rRTCCON |= (RTCCON_RTC_CONTROL_DISABLE);
}
现象截图:
2、闹钟实验
#include "rtc.h"
#include "init.h"
#include "stdio.h"
#define RTC_BASE (0xE2800000)
#define rINTP (*((volatile unsigned long *)(RTC_BASE + 0x30)))
#define rRTCCON (*((volatile unsigned long *)(RTC_BASE + 0x40)))
#define rTICCNT (*((volatile unsigned long *)(RTC_BASE + 0x44)))
#define rRTCALM (*((volatile unsigned long *)(RTC_BASE + 0x50)))
#define rALMSEC (*((volatile unsigned long *)(RTC_BASE + 0x54)))
#define rALMMIN (*((volatile unsigned long *)(RTC_BASE + 0x58)))
#define rALMHOUR (*((volatile unsigned long *)(RTC_BASE + 0x5c)))
#define rALMDATE (*((volatile unsigned long *)(RTC_BASE + 0x60)))
#define rALMMON (*((volatile unsigned long *)(RTC_BASE + 0x64)))
#define rALMYEAR (*((volatile unsigned long *)(RTC_BASE + 0x68)))
#define rRTCRST (*((volatile unsigned long *)(RTC_BASE + 0x6c)))
#define rBCDSEC (*((volatile unsigned long *)(RTC_BASE + 0x70)))
#define rBCDMIN (*((volatile unsigned long *)(RTC_BASE + 0x74)))
#define rBCDHOUR (*((volatile unsigned long *)(RTC_BASE + 0x78)))
#define rBCDDATE (*((volatile unsigned long *)(RTC_BASE + 0x7c)))
#define rBCDDAY (*((volatile unsigned long *)(RTC_BASE + 0x80)))
#define rBCDMON (*((volatile unsigned long *)(RTC_BASE + 0x84)))
#define rBCDYEAR (*((volatile unsigned long *)(RTC_BASE + 0x88)))
#define rCURTICCNT (*((volatile unsigned long *)(RTC_BASE + 0x90)))
#define rRTCLVD (*((volatile unsigned long *)(RTC_BASE + 0x94)))
/**********************************************************************************/
#define BIT_LOCATION_RTCCON_RTCEN_CONTROL (0b1 << 0)
#define RTCCON_FUNC_RTC_CONTROL_ENABLE (0b1 << 0)
#define RTCCON_FUNC_RTC_CONTROL_DISABLE (0b0 << 0)
/**********************************************************************************/
#define BIT_LOCATION_RTCALM_ALMEN (0b1 << 6)
#define RTCALM_FUNC_ALARM_GLOBAL_DISABLE (0b0 << 6)
#define RTCALM_FUNC_ALARM_GLOBAL_ENABLE (0b1 << 6)
#define RTCALM_FUNC_YEAR_ALARM_ENABLE (0b1 << 5)
#define RTCALM_FUNC_YEAR_ALARM_DISABLE (0b0 << 5)
#define RTCALM_FUNC_MONTH_ALARM_ENABLE (0b1 << 4)
#define RTCALM_FUNC_MONTH_ALARM_DISABLE (0b0 << 4)
#define RTCALM_FUNC_DAY_ALARM_ENABLE (0b1 << 3)
#define RTCALM_FUNC_DAY_ALARM_DISABLE (0b0 << 3)
#define RTCALM_FUNC_HOUR_ALARM_ENABLE (0b1 << 2)
#define RTCALM_FUNC_HOUR_ALARM_DISABLE (0b0 << 2)
#define RTCALM_FUNC_MINUTE_ALARM_ENABLE (0b1 << 1)
#define RTCALM_FUNC_MINUTE_ALARM_DISABLE (0b0 << 1)
#define RTCALM_FUNC_SEC_ALARM_ENABLE (0b1 << 0)
#define RTCALM_FUNC_SEC_ALARM_DISABLE (0b0 << 0)
//You can clear specific bits of INTP register by writing 1’s to the bits that you want to clear regardless of RTCEN value.
#define BIT_LOCATION_INTP_ALARM_INTERRUPT (0b1 << 1)
#define INTP_FUNC_ALARM_INTERRUPT_OCCURRED (0b1 << 1)
//函数功能:把十进制 num 转成 bcd 码,譬如把 56 转成 0x56
static unsigned int num_2_bcd(unsigned int num)
{
//第一步,把56拆分成 5 和 6
//第二步,把 5 和 6组合成 0x56
return ( ((num / 10) << 4) | (num % 10));
}
//函数功能:把 bcd 码bcd,转成十进制,譬如把 0x56 转成 56
static unsigned int bcd_2_num(unsigned int bcd)
{
//第一步,把 0x56 拆分成 5 和 6
//第二步,把 5 和 6 组合成56
return ( ((bcd & 0xf0) >> 4) * 10 + (bcd & 0xf));
}
void rtc_set_time(const struct rtc_time *p)
{
//第一步,打开 RTC 读写开关
rRTCCON &= ~(BIT_LOCATION_RTCCON_RTCEN_CONTROL);
rRTCCON |= (RTCCON_FUNC_RTC_CONTROL_ENABLE);
//第二步,写 RTC 时间寄存器
rBCDYEAR = num_2_bcd(p->year - 2000);
rBCDMON = num_2_bcd(p->month);
rBCDDATE= num_2_bcd(p->date);
rBCDHOUR = num_2_bcd(p->hour);
rBCDMIN = num_2_bcd(p->minute);
rBCDSEC = num_2_bcd(p->second);
rBCDDAY = num_2_bcd(p->day);
//最后一个,关上 RTC 的读写开关
rRTCCON &= ~(BIT_LOCATION_RTCCON_RTCEN_CONTROL);
rRTCCON |= (RTCCON_FUNC_RTC_CONTROL_DISABLE);
}
void rtc_get_time(struct rtc_time *p)
{
//第一步,打开 RTC 读写开关
rRTCCON &= ~(BIT_LOCATION_RTCCON_RTCEN_CONTROL);
rRTCCON |= (RTCCON_FUNC_RTC_CONTROL_ENABLE);
//第二步,读 RTC 时间寄存器
p->year = bcd_2_num(rBCDYEAR) + 2000;
p->month = bcd_2_num(rBCDMON);
p->date = bcd_2_num(rBCDDATE);
p->hour = bcd_2_num(rBCDHOUR);
p->minute = bcd_2_num(rBCDMIN);
p->second = bcd_2_num(rBCDSEC);
p->day = bcd_2_num(rBCDDAY);
//最后一个,关上 RTC 的读写开关
rRTCCON &= ~(BIT_LOCATION_RTCCON_RTCEN_CONTROL);
rRTCCON |= (RTCCON_FUNC_RTC_CONTROL_DISABLE);
}
//函数功能:实现 秒级别的闹钟
void rtc_set_alarm(void)
{
rALMSEC = num_2_bcd(31);
rRTCALM &= ~(BIT_LOCATION_RTCALM_ALMEN);
rRTCALM |= (RTCALM_FUNC_ALARM_GLOBAL_ENABLE);
rRTCALM |= (RTCALM_FUNC_SEC_ALARM_ENABLE);
}
void isr_rtc_alarm(void)
{
static int i = 0;
printf("rtc alarm, i = %d...\r\n", i++);
rINTP |= (INTP_FUNC_ALARM_INTERRUPT_OCCURRED);
intc_clearVectaddr();
}
现象截图:可以看到,精确在 31 秒的时候,进入中断.
源自朱友鹏老师.