Mini2440裸机开发之RTC
一、S3C2440上的RTC
1.1 概述
RTC,英文全称Real Time Clock,中文就是实时时钟,是一个可以为系统提供精确的时间基准的元器件,RTC一般采用精度较高的晶振作为时钟源,有些RTC为了在主电源掉电时还可以工作,需要外加电池供电。
RTC可以通过使用STRB/LDRB ARM操作发送8位二-十进制交换码(BCD)值数据给CPU。这些数据包括年、月、日、星期、时、分和秒的时间信息。RTC单元工作在外部32.768kHz晶振并且可以执行闹钟功能。
1.2 特性
- 时钟数据采用BCD编码,时钟数据包括秒、分、时、日、月、年、星期;
- 32.768K 的晶振提供时钟输入;
- 可以提供毫秒级的时钟节拍中断,该中断可用于作为嵌入式操作系统的内核时钟;
- 闹钟功能:闹钟中断或从掉电模式唤醒;
- 独立电源引脚(RTCVDD);
1.3 RTC方框图
1.3.1 中断
从上图可以看到RTC模块提供了2个中断:
- INT_RTC:RTC alarm interrupt,即RTC闹钟中断;
- INT_TICK: RTC Time tick interrupt,即RTC时钟节拍中断;
1.3.2 瑞年发生器
闰年发生器能够基于 BCDDATE、BCDMON 和 BCDYEAR 的数据,从 28、29、30 或 31 中决定哪个是每月的最后日,此模块决定每月最后日时会考虑闰年因素。
1.3.3 读写寄存器
为了写RTC模块中的BCD寄存器,RTCCON寄存器的位[0]必须设置为高。
为了显示,年、月、日、时、分和秒,CPU应该分别读取RTC模块中的 BCDSEC、BCDMIN、BCDHOUR、BCDDAY、BCDDATE、BCDMON和 BCDYEAR 寄存器中的数据。然而可能存在1秒的偏差,因为读取了多个寄存器。
例如,当用户从 BCDYEAR到BCDMIN读取寄存器,其结果假定为2059(年)、12(月)、31(日)、23(时)和 59(分)。当用户读取 BCDSEC寄存器并且值的范围是从 1 到 59(秒),这没有问题,但是如果该值为 0 秒。则年、月、日、时和分可能要变为 2060(年)、1(月)、1(日)、0(时)和 0(分),因为存在着 1 秒的偏差。在这种情况中,如果 BCDSEC为0则用户应该重新读取 BCDYEAR 到 BCDSEC。
1.3.4 备用电池
RTC逻辑可以由备用电池驱动,即使如果系统电源关闭了,则由 RTCVDD引脚供电给RTC模块。当关闭了电源则应该阻塞掉CPU和RTC逻辑的接口,并且备用电池只驱动振荡电路和BCD计数器来最小化功耗。
1.3.5 闹钟功能
RTC在掉电模式中或正常工作模式中的指定时间产生一个闹钟信号;
- 在正常工作模式中,只激活闹钟中断(INT_RTC)信号;
- 在掉电模式中,除了INT_RTC 之外还激活电源管理唤醒(PMWKUP)信号;
RTC闹钟寄存器(RTCALM)决定了闹钟使能/禁止状态和闹钟时间设置的条件。
1.3.6 时钟节拍中断
RTC时钟节拍是用于中断请求。TICNT寄存器有一个中断使能位和中断的计数值。当时钟节拍中断发生时计数值达到'0'。中断周期如下:$周期=(n+1)/128秒$
其中:n为时钟节拍计数值,1到127。
1.4 BCD码
BCD(Binary Coded Decimal)码,即二进制编码的十进制数,这种方法是用4位二进制码的组合代表十进制数的0,1,2,3,4,5,6 ,7,8,9 十个数符。
4位二进制数码有16种组合,原则上可任选其中的10种作为代码,分别代表十进制中的0,1,2,3,4,5,6,7,8,9这十个数符。最常用的BCD码称为8421BCD码,8.4.2.1 分别是4位二进数的位取值。
下图为十进制数和8421BCD编码的对应关系表:
十进制 | BCD(8421)码 |
0 | 0000 |
1 | 0001 |
2 | 0010 |
3 | 0011 |
4 | 0100 |
5 | 0101 |
6 | 0110 |
7 | 0111 |
8 | 1000 |
9 | 1001 |
二、RTC相关寄存器
2.1 RTC控制寄存器RTCCON
RTCCON寄存器由4 位组成,如控制BCD寄存器读/写使能的RTCEN、CLKSEL、CNTSEL和测试用的CLKRST。
RTCEN位可以控制所有CPU与RTC之间的接口,因此在系统复位后在RTC控制程序中必须设置为1来使能数据的读/写。同样的在掉电前,RTCEN位应该清除为0来预防误写入RTC寄存器中。
寄存器 | 地址 | R/W | 描述 | 复位值 |
RTCCON |
0x57000040(L 小端) 0x57000043(B 大端) |
R/W字节 | RTC控制寄存器 | 0x0 |
寄存器位信息:
RTCCON | 位 | 描述 | 初始状态 |
CLKRST | [3] |
RTC时钟计数复位 0:不复位 1:复位 |
0 |
CNTSEL | [2] |
BCD计数选择 0:融入BCD计数器 1:保留(分离BCD计数器) |
0 |
CLKSEL | [1] |
BCD时钟选择 0:XTAL 1/215分频时钟 1:保留(XTAL时钟只用于测试) |
0 |
RTCEN | [0] |
RTC控制使能 0:禁止 1:使能 注意:只能执行BCD时间计数和读操作 |
0 |
注意:所有RTC寄存器必须按自己为单位使用STRB或LDRB指令或char型指针访问;
2.2 时钟节拍计数寄存器TICNT
寄存器 | 地址 | R/W | 描述 | 复位值 |
TICNT |
0x57000044(L 小端) 0x57000047(B 大端) |
R/W字节 | 时钟节拍计数寄存器 | 0x0 |
寄存器位信息:
TICNT | 位 | 描述 | 初始状态 |
TICK INT使能 | [7] |
INT_TICK中断使能 0:禁止 1:使能 |
0 |
TICK时钟计数 | [6:0] |
时钟节拍计数值,1到127 此计数器值内部递减并且用户不能在工作中读取此计数器的值 |
000000 |
2.3 RTC闹钟控制寄存器RTCALM
RTCALM寄存器决定了闹钟使能和闹钟时间。请注意RTCALM寄存器在掉电模式中同时通过INT_RTC和PMWKUP产生闹钟信号,但是在正常工作模式中只产生INT_RTC。
寄存器 | 地址 | R/W | 描述 | 复位值 |
RTCALM |
0x57000050(L 小端) 0x57000053(B 大端) |
R/W字节 | RTC闹钟控制寄存器 | 0x0 |
寄存器位信息:
RTCALM | 位 | 描述 | 初始状态 |
保留 | [7] |
- |
0 |
ALMEN | [6] |
全局闹钟使能 0:禁止 1:使能 |
0 |
YAEREN | [5] |
年闹钟使能 0:禁止 1:使能 |
0 |
MONREN | [4] |
月闹钟使能 0:禁止 1:使能 |
0 |
DATAEN | [3] | 日闹钟使能
0:禁止 1:使能 |
0 |
HOUREN | [2] | 时闹钟使能
0:禁止 1:使能 |
0 |
MINEN | [1] | 分闹钟使能
0:禁止 1:使能 |
0 |
SECEN | [0] | 秒闹钟使能
0:禁止 1:使能 |
0 |
2.3.1 闹钟秒数据寄存器
寄存器 | 地址 | R/W | 描述 | 复位值 |
ALMSEC |
0x57000054(L 小端) 0x57000057(B 大端) |
R/W字节 | 闹钟秒数据寄存器 | 0x0 |
寄存器位信息:
ALMSEC | 位 | 描述 | 初始状态 |
保留 | [7] |
- |
0 |
SECDATA | [6:4] |
闹钟秒BCD值 0至5 |
000 |
[3:0] | 0至9 | 0000 |
2.3.2 闹钟分数据寄存器
寄存器 | 地址 | R/W | 描述 | 复位值 |
ALMMIN |
0x57000058(L 小端) 0x5700005B(B 大端) |
R/W字节 | 闹钟分数据寄存器 | 0x0 |
寄存器位信息:
ALMMIN | 位 | 描述 | 初始状态 |
保留 | [7] |
- |
0 |
MINDATA | [6:4] |
闹钟分BCD值 0至5 |
000 |
[3:0] | 0至9 | 0000 |
2.3.3 闹钟时数据据寄存器
寄存器 | 地址 | R/W | 描述 | 复位值 |
ALMHOUR |
0x5700005C(L 小端) 0x5700005F(B 大端) |
R/W字节 | 闹钟时数据寄存器 | 0x0 |
寄存器位信息:
ALMHOUR | 位 | 描述 | 初始状态 |
保留 | [7:6] |
- |
0 |
HOURDATA | [5:4] |
闹钟时BCD值 0至2 |
00 |
[3:0] | 0至9 | 0000 |
2.3.4 闹钟日数据寄存器
寄存器 | 地址 | R/W | 描述 | 复位值 |
ALMDATE |
0x57000060(L 小端) 0x57000063(B 大端) |
R/W字节 | 闹钟日数据寄存器 | 0x01 |
寄存器位信息:
ALMDATE | 位 | 描述 | 初始状态 |
保留 | [7:6] |
- |
0 |
DATEDATA | [5:4] |
闹钟日BCD值 0至3 |
00 |
[3:0] | 0至9 | 0001 |
2.3.5 闹钟月数据寄存器
寄存器 | 地址 | R/W | 描述 | 复位值 |
ALMMON |
0x57000064(L 小端) 0x57000067(B 大端) |
R/W字节 | 闹钟月数据寄存器 | 0x01 |
寄存器位信息:
ALMMON | 位 | 描述 | 初始状态 |
保留 | [7:5] |
- |
0 |
MONDATA | [4] |
闹钟月BCD值 0至1 |
00 |
[3:0] | 0至9 | 0001 |
2.3.6 闹钟年数据寄存器
寄存器 | 地址 | R/W | 描述 | 复位值 |
ALMYEAR |
0x57000068(L 小端) 0x5700006B(B 大端) |
R/W字节 | 闹钟年数据寄存器 | 0x0 |
寄存器位信息:
ALMYEAR | 位 | 描述 | 初始状态 |
YEARDATA | [7:0] |
闹钟年BCD值 00至99 |
0x0 |
2.4 BCD寄存器
2.4.1 BCD秒寄存器
寄存器 | 地址 | R/W | 描述 | 复位值 |
BCDSEC |
0x57000070(L 小端) 0x57000073(B 大端) |
R/W字节 | BCD秒寄存器 | 未定义 |
寄存器位信息:
BCDSEC | 位 | 描述 | 初始状态 |
保留 | [7] |
- |
- |
SECDATA | [6:4] |
秒BCD值 0至5 |
- |
[3:0] | 0至9 | - |
2.4.2 BCD分寄存器
寄存器 | 地址 | R/W | 描述 | 复位值 |
BCDMIN |
0x57000074(L 小端) 0x57000077(B 大端) |
R/W字节 | BCD分寄存器 | 未定义 |
寄存器位信息:
BCDMIN | 位 | 描述 | 初始状态 |
保留 | [7] |
- |
- |
MINDATA | [6:4] |
分BCD值 0至5 |
- |
[3:0] | 0至9 | - |
2.4.3 BCD时寄存器
寄存器 | 地址 | R/W | 描述 | 复位值 |
BCDHOUR |
0x57000078(L 小端) 0x5700007B(B 大端) |
R/W字节 | BCD时寄存器 | 未定义 |
寄存器位信息:
BCDHOUR | 位 | 描述 | 初始状态 |
保留 | [7:6] |
- |
- |
HOURDATA | [5:4] |
时BCD值 0至2 |
- |
[3:0] | 0至9 | - |
2.4.4 BCD日寄存器
寄存器 | 地址 | R/W | 描述 | 复位值 |
BCDDATE |
0x5700007C(L 小端) 0x5700007F(B 大端) |
R/W字节 | BCD日寄存器 | 未定义 |
寄存器位信息:
BCDDATE | 位 | 描述 | 初始状态 |
保留 | [7:6] |
- |
- |
DATEDATA | [5:4] |
日BCD值 0至3 |
- |
[3:0] | 0至9 | - |
2.4.5 BCD星期寄存器
寄存器 | 地址 | R/W | 描述 | 复位值 |
BCDDAY |
0x57000080(L 小端) 0x57000083(B 大端) |
R/W字节 | BCD星期寄存器 | 未定义 |
寄存器位信息:
BCDDAY | 位 | 描述 | 初始状态 |
保留 | [7:3] |
- |
- |
DAYDATA | [2:0] |
星期BCD值 1至7 |
- |
2.4.6 BCD月寄存器
寄存器 | 地址 | R/W | 描述 | 复位值 |
BCDMON |
0x57000084(L 小端) 0x57000087(B 大端) |
R/W字节 | BCD月寄存器 | 未定义 |
寄存器位信息:
BCDMON | 位 | 描述 | 初始状态 |
保留 | [7:5] |
- |
- |
MONDATA | [4] |
月BCD值 0至1 |
- |
[3:0] | 0至9 | - |
2.4.7 BCD年寄存器
寄存器 | 地址 | R/W | 描述 | 复位值 |
BCDYEAR |
0x57000088(L 小端) 0x5700008B(B 大端) |
R/W字节 | BCD年寄存器 | 未定义 |
寄存器位信息:
BCDYEAR | 位 | 描述 | 初始状态 |
YEARDATA | [7:0] |
年BCD值 00至99 |
- |
三、RTC代码
3.1 RTC初始化步骤
首先我们需要进行校准RTC,通俗的说,就是时间校准:
- 使能进入RTC模块的PCLK,配置CLKCON时钟控制寄存器位[14]为1,该位默认为1,因此该步骤可以忽略;
- RTC控制使能,配置RTCCON位[0]为1;
- 根据当前时间配置BCD寄存器,包括BCDYEAR、BCDMON、BCDDATE、BCDDAY、BCDHOUR、BCDMIN、BCDSEC;
- RTC控制禁止,配置RTCCON位[0]为0;
接下来,我们就可以通过读取BCD寄存器的值,获取当前时间:
- RTC控制使能,配置RTCCON位[0]为1;
- 读取BCDYEAR、BCDMON、BCDDATE、BCDDAY、BCDHOUR、BCDMIN、BCDSEC;
- RTC控制禁止,配置RTCCON位[0]为0;
3.2 rtc.h
/***************************************************************************************************************** * * FileName : rtc.h * Function : RTC * Author : zy * ****************************************************************************************************************/ #ifndef __RTC_H__ #define __RTC_H__ #include "type.h" #include "gpio.h" /******************************************************************************************************************/ #ifdef __BIG_ENDIAN #define RTCCON (*(volatile u8 *) 0x57000043) #define TICNT (*(volatile u8 *) 0x57000047) #define RTCALM (*(volatile u8 *) 0x57000053) #define ALMSEC (*(volatile u8 *) 0x57000057) #define ALMMIN (*(volatile u8 *) 0x5700005B) #define ALMHOUR (*(volatile u8 *) 0x5700005F) #define ALMDATE (*(volatile u8 *) 0x57000063) #define ALMMON (*(volatile u8 *) 0x57000067) #define ALMYEAR (*(volatile u8 *) 0x5700006B) #define RTCRST (*(volatile u8 *) 0x5700006F) #define BCDSEC (*(volatile u8 *) 0x57000073) #define BCDMIN (*(volatile u8 *) 0x57000077) #define BCDHOUR (*(volatile u8 *) 0x5700007B) #define BCDDATE (*(volatile u8 *) 0x5700007F) #define BCDDAY (*(volatile u8 *) 0x57000083) #define BCDMON (*(volatile u8 *) 0x57000087) #define BCDYEAR (*(volatile u8 *) 0x5700008B) #else /* Little Endian */ /* RTC控制寄存器 */ #define RTCCON (*(volatile u8 *)0x57000040) /* 时钟节拍计数寄存器 */ #define TICNT (*(volatile u8 *)0x57000044) /* RTC闹钟控制寄存器 */ #define RTCALM (*(volatile u8 *)0x57000050) /* 闹钟数据寄存器 */ #define ALMSEC (*(volatile u8 *)0x57000054) #define ALMMIN (*(volatile u8 *)0x57000058) #define ALMHOUR (*(volatile u8 *)0x5700005C) #define ALMDATE (*(volatile u8 *)0x57000060) #define ALMMON (*(volatile u8 *)0x57000064) #define ALMYEAR (*(volatile u8 *)0x57000068) /* BCD寄存器 */ #define BCDSEC (*(volatile u8 *)0x57000070) #define BCDMIN (*(volatile u8 *)0x57000074) #define BCDHOUR (*(volatile u8 *)0x57000078) #define BCDDATE (*(volatile u8 *)0x5700007C) #define BCDDAY (*(volatile u8 *)0x57000080) #define BCDMON (*(volatile u8 *)0x57000084) #define BCDYEAR (*(volatile u8 *)0x57000088) #endif /* 星期 */ typedef enum { Sunday, /* 星期日 */ Monday, /* 星期一 */ Tuesday, /* 星期一二 */ Wednesday, /* 星期三 */ Thursday, /* 星期四 */ Friday, /* 星期五 */ Saturday /* 星期六 */ }WEEK; /* 日历 */ typedef struct { u16 year; u8 month; u8 day; u8 hour; u8 min; u8 sec; WEEK week; }calendar; /* 函数声明 */ extern void rtc_set(calendar date); /* 时间校准 */ extern void rtc_get(calendar *date); /* 获取RTC时间 */ u8* get_week(WEEK week); /* 将星期转换为字符串 */ extern void rtc_tick_init(); /* RTC滴答设置 */ extern void rtc_alarm_init(); /* RTC闹钟设置 */ #endif
3.3 rtc.c
/************************************************************************** * * FileName : rtc.c * Function : RTC * Author : zy * *************************************************************************/ #include "rtc.h" #include "isr.h" #include "stdio.h" /************************************************************* * * Function : RTC16进制转为10进制 * Input : val 需要转换的BCD数 * Return :转换完成的10进制值 * **************************************************************/ u8 rtc_bcd_to_ten(u8 val) { u8 temp = 0; temp = (val>>4)*10; return (temp + (val&0x0f)); } /************************************************************* * * Function : RTC10进制转为16进制 * Input : val 需要转换的10进制数 * Return :转换完成的BCD值 * **************************************************************/ u8 rtc_ten_to_bcd(u8 val) { u8 high = val / 10; u8 low = val %10 ; return (high<<4|low); } /************************************************************* * * Function : 中断源8 RTC 时钟滴答中断 * **************************************************************/ void TICK_IRQHandler() { calendar current_date; rtc_get(¤t_date); printf("NOWTIME: %04d-%02d-%02d %s %02d:%02d:%02d\r\n",current_date.year, current_date.month, current_date.day, get_week(current_date.week), current_date.hour, current_date.min, current_date.sec); /* 清除滴答中断位 */ SRCPND |= BIT_TICK; INTPND |= BIT_TICK; } /************************************************************* * * Function : 中断源30 RTC RTC闹钟中断处理函数 * **************************************************************/ void RTC_IRQHandler() { printf("alarm int\r\n"); /* 清除中断位 */ SRCPND |= BIT_RTC; INTPND |= BIT_RTC; } /************************************************************* * * Function : 校准RTC实时时钟 以2000年1月1日为基址 星期六 * Input : date 当前时间 * **************************************************************/ void rtc_set(calendar date) { /* RTC读写使能,BCD时钟、计数器、无复位 */ RTCCON = 0x01; BCDYEAR = rtc_ten_to_bcd(date.year - 2000); BCDMON = rtc_ten_to_bcd(date.month); BCDDATE = rtc_ten_to_bcd(date.day); BCDDAY = rtc_ten_to_bcd(date.week + 1); BCDHOUR = rtc_ten_to_bcd(date.hour); BCDMIN = rtc_ten_to_bcd(date.min); BCDSEC = rtc_ten_to_bcd(date.sec); RTCCON = 0x00; } /************************************************************* * * Function : RTC时间获取 以2000年1月1日为基址 星期六 * Input : date * **************************************************************/ void rtc_get(calendar *date) { /* RTC读写使能,BCD时钟、计数器、无复位 */ RTCCON = 0x01; date->year = rtc_bcd_to_ten(BCDYEAR) + 2000; date->month = rtc_bcd_to_ten(BCDMON & 0x1F); date->day = rtc_bcd_to_ten(BCDDATE & 0x3F); date->week = rtc_bcd_to_ten(BCDDAY & 0x07) - 1; date->hour = rtc_bcd_to_ten(BCDHOUR & 0x3F); date->min = rtc_bcd_to_ten(BCDMIN & 0x7F); date->sec = rtc_bcd_to_ten(BCDSEC & 0x7F); RTCCON = 0x00; } /************************************************************* * * Function : 将星期转换为字符串 * Input : week 星期 * **************************************************************/ u8* get_week(WEEK week) { const u8 *week_str[7] = {"SUN","MON","TUES","WED","THURS","FRI","SAT"}; return week_str[week]; } /************************************************************* * * Function : RTC滴答设置 * **************************************************************/ void rtc_tick_init() { /* 时钟节拍周期 = (时间计数+1)/128 = (127+1)/128 = 1s */ TICNT = ((1<<7) | (0x7f<<0)); /* 使能中断,时间计数设置为127 */ /* 清除时钟节拍中断位 */ SRCPND |= BIT_TICK; INTPND |= BIT_TICK; /* 使能时钟节拍中断 */ INTMSK &= ~BIT_TICK; } /************************************************************* * * Function : RTC闹钟设置 * **************************************************************/ void rtc_alarm_init() { /* RTC读写使能,BCD时钟、计数器、无复位 */ RTCCON = 1; /* 秒闹钟设置为0,当秒数为0时,触发中断 */ ALMSEC = 0; /* 使能全局闹钟,使能秒闹钟 */ RTCALM |= ((1<<6) | (1<<0)); /* RTC读写禁止,BCD时钟、计数器、无复位 */ RTCCON = 0; /* 清除闹钟中断位 */ SRCPND |= BIT_RTC; INTPND |= BIT_RTC; /* 使能闹钟中断 */ INTMSK &= ~BIT_RTC; }
3.4 测试
主函数代码如下:
#include "led.h" #include "common.h" #include "uart.h" #include "rtc.h" calendar current_date = { .year = 2023, .month = 1, .day = 1, .week = Sunday, .hour = 0, .min = 0, .sec = 0, }; int main() { u8 i = 0; vector_enable(); led_init(); uart_init(); rtc_tick_init(); rtc_alarm_init(); delay_ms(1000); printf("rtc set 2023-01-01 00:00:00 \r\n"); rtc_set(current_date); while(1) { led_turn(LED1); delay_ms(1000); } return 0; }
将代码下载到Nand Flash,启动开发板运行输出如下:
rtc set 2023-01-01 00:00:00 alarm int NOWTIME: 2023-01-01 SUN 00:00:00 NOWTIME: 2023-01-01 SUN 00:00:01 NOWTIME: 2023-01-01 SUN 00:00:02 NOWTIME: 2023-01-01 SUN 00:00:03 NOWTIME: 2023-01-01 SUN 00:00:04 NOWTIME: 2023-01-01 SUN 00:00:05 NOWTIME: 2023-01-01 SUN 00:00:06 NOWTIME: 2023-01-01 SUN 00:00:07...... NOWTIME: 2023-01-01 SUN 00:00:52 NOWTIME: 2023-01-01 SUN 00:00:53 NOWTIME: 2023-01-01 SUN 00:00:54 NOWTIME: 2023-01-01 SUN 00:00:55 NOWTIME: 2023-01-01 SUN 00:00:56 NOWTIME: 2023-01-01 SUN 00:00:57 NOWTIME: 2023-01-01 SUN 00:00:58 NOWTIME: 2023-01-01 SUN 00:00:59 alarm int NOWTIME: 2023-01-01 SUN 00:01:00
四、代码下载
Young / s3c2440_project【14.rtc】
参考文章
[2]STM32---RTC(Real Time Clock)
[3]RTC实验