一文搞懂Cortex-A9 RTC
一、RTC
RTC(Real-Time Clock) 實(shí)時(shí)時(shí)鐘。

RTC
RTC是集成電路,通常稱為時(shí)鐘芯片。在一個(gè)嵌入式系統(tǒng)中,通常采用RTC來提供可靠的系統(tǒng)時(shí)間,包括時(shí)分秒和年月日等,而且要求在系統(tǒng)處于關(guān)機(jī)狀態(tài)下它也能正常工作(通常采用后備電池供電)。
它的外圍也不需要太多的輔助電路,典型的就是只需要一個(gè)高精度的32.768kHz 晶體和電阻電容等,并且具有鬧鐘的功能。。
二、Exynos 4412 RTC
本篇主要以Cortex-A9 soc為例講解RTC的使用方法。
1. 特性
實(shí)時(shí)時(shí)鐘(RTC)單元可以通過備用電池供電,因此,即使系統(tǒng)電源關(guān)閉,它也可以繼續(xù)工作。RTC可以通過STRB/LDRB 指令將8位BCD碼數(shù)據(jù)送至CPU。這些BCD數(shù)據(jù)包括秒、分、時(shí)、日期、星期、月和年。
RTC單元通過一個(gè)外部的32.768kHz 晶振提供時(shí)鐘。
RTC具有定時(shí)報(bào)警的功能。
其功能說明如下:
1 -- 時(shí)鐘數(shù)據(jù)采用BCD編碼。
2 -- 能夠?qū)﹂c年的年月日進(jìn)行自動(dòng)處理。
3 -- 具有告警功能,當(dāng)系統(tǒng)處于關(guān)機(jī)狀態(tài)時(shí),能產(chǎn)生警告中斷。
4 -- 具有獨(dú)立的電源輸入。
5 -- 提供毫秒級(jí)時(shí)鐘中斷,該中斷可以用于作為嵌入式操作系統(tǒng)的內(nèi)核時(shí)鐘。
2. RTC Block

RTC Block Diagram
RTC在Linux中主要實(shí)現(xiàn)兩種功能,分別是系統(tǒng)掉電后的時(shí)間日期維持和時(shí)間日期報(bào)警(類似定時(shí)器)。
「1)時(shí)間日期維持功能:」
主要是由RTC實(shí)時(shí)時(shí)鐘控制寄存器RTCCON進(jìn)行功能的使能控制,由節(jié)拍時(shí)間計(jì)數(shù)寄存器TICNT來產(chǎn)生節(jié)拍時(shí)間中斷來實(shí)現(xiàn)實(shí)時(shí)操作系統(tǒng)功能相關(guān)的時(shí)間和實(shí)時(shí)同步。其中對(duì)時(shí)間日期的操作實(shí)際上是對(duì)BCD碼操作,而BCD碼則是由一系列的寄存器組成(BCD秒寄存器BCDSEC、BCD分寄存器BCDMIN、BCD小時(shí)寄存器BCDHOUR、BCD日期寄存器BCDDATE、BCD日寄存器BCDDAY、BCD月寄存器BCDMON、BCD年寄存器BCDYEAR)。
「2)報(bào)警功能:」
主要由RTC報(bào)警控制寄存器RTC ALM進(jìn)行功能使能控制,并產(chǎn)生報(bào)警中斷。報(bào)警時(shí)間日期的設(shè)置也是對(duì)一系列的寄存器進(jìn)行操作(報(bào)警秒數(shù)據(jù)寄存器ALMSEC、報(bào)警分鐘數(shù)據(jù)寄存器ALMMIN、報(bào)警小時(shí)數(shù)據(jù)寄存器ALMHOUR、報(bào)警日期數(shù)據(jù)寄存器ALMDATE、報(bào)警月數(shù)據(jù)寄存器ALMMON、報(bào)警年數(shù)據(jù)寄存器ALMYEAR)。
「3)閏年發(fā)生器」
可以根據(jù)BCDDAY、BCDMON和BCDEEAR的值自動(dòng)計(jì)算閏年。
3. 備用電池
備用電池可以驅(qū)動(dòng)RTC邏輯。備用電池通過RTCVDD引腳向RTC塊,即使系統(tǒng)電源關(guān)閉。如果系統(tǒng)關(guān)閉,您應(yīng)該阻止CPU和RTC邏輯。為了減少功耗,備用電池單獨(dú)驅(qū)動(dòng)振蕩電路和BCD計(jì)數(shù)器。
4. Alarm【報(bào)警】
功能RTC在斷電模式或正常運(yùn)行模式都可以在執(zhí)行的時(shí)間產(chǎn)生一個(gè)ALARM_INT 和ALARM_WK信號(hào)。在正常工作模式下,它會(huì)產(chǎn)生ALARM_INT。在斷電模式下,它會(huì)ALARM_WK以及ALARM_INT信號(hào)。RTC報(bào)警寄存器(RTCALM)確定報(bào)警啟用/禁用狀態(tài)和報(bào)警時(shí)間設(shè)置的條件。
5. 晶振
32.768 kHz X-Tal Connection Example

晶振時(shí)鐘頻率 32.768 kHz。

- XT_RTC_I 32.768 kHz RTC振蕩器時(shí)鐘輸入
- XT_RTC_O 32.768 kHz RTC振蕩器時(shí)鐘輸出
- XRTCCLKO 32.768 kHz RTC振蕩器時(shí)鐘輸出,此信號(hào)默認(rèn)關(guān)閉。可以通過設(shè)置寄存器RTCCON的CLKOUTEN字段為1來啟用它。
引腳連接圖:

由電路圖可知,只連接了RTC振蕩器時(shí)鐘輸入引腳XT_RTC_I 。
三、寄存器
1. RTC寄存器組:

寄存器組
2. INTP

設(shè)置對(duì)應(yīng)的bit為1就可以清除中斷。
3. RTCCONRTCCON
1.RTCCON寄存器由10位組成,如控制BCD SEL讀/寫啟用的CTLEN, CNTSEL、CLKRST、TICKSEL、TICEN用于測試,CLKOUTEN用于RTC時(shí)鐘輸出控制。
2.CTLEN位控制CPU和RTC之間的所有接口。因此,您應(yīng)該在RTC控件中將其設(shè)置為“1”,在系統(tǒng)重置后啟用數(shù)據(jù)寫入的例程。為了防止無意中寫入BCD計(jì)數(shù)器寄存器,應(yīng)該關(guān)閉電源前將CTLEN位清除為0。
3.CLKRST是2^15^時(shí)鐘分頻器的計(jì)數(shù)器復(fù)位。在設(shè)置RTC時(shí)鐘之前,應(yīng)重置215時(shí)鐘分頻器以獲得精確的RTC操作。
四、RTC的操作
1. 設(shè)置時(shí)間
「舉例:」我們要將當(dāng)前時(shí)間設(shè)置為 「2020年11月11日, 15:24:50」。
1) 先將RTC控制使能開啟,即RTCCON[0]置為1;2)然后將時(shí)間對(duì)應(yīng)的BCD格式數(shù)值,設(shè)置到應(yīng)對(duì)的寄存器,BCDYEAR 、BCDMON 、BCDDAY 、BCDHOUR 、BCDMIN 、BCDSEC;3) 將RTCCON[0]置為0,防止誤操作修改了時(shí)間;4)如果我們要訪問當(dāng)前時(shí)間,可以直接讀取寄存器BCDYEAR 、BCDMON 、BCDDAY 、BCDHOUR 、BCDMIN 、BCDSEC。
- void rtc_init(void)
- {
- RTCCON = 1;//使能RTC控制寫功能
- RTC.BCDYEAR = 0x20;// 2020年11月11日, 15:24:50.以BCD碼格式寫入
- RTC.BCDMON = 0x11;
- RTC.BCDDAY = 0x11;
- RTC.BCDHOUR = 0x15;
- RTC.BCDMIN = 0x24;
- RTC.BCDSEC = 0x50;
- RTCCON = 0;//關(guān)閉RTC控制寫功能
- }
2. 操作滴答定時(shí)器
TICNT

TICNT
RTC計(jì)時(shí)器是一個(gè)遞增計(jì)數(shù)器,并引發(fā)計(jì)時(shí)中斷。TICNT寄存器包含32位目標(biāo)計(jì)數(shù)值,并且CURTICCNT寄存器包含32位當(dāng)前計(jì)時(shí)計(jì)數(shù)。如果當(dāng)前滴答數(shù)達(dá)到TICNT中指定的目標(biāo)值時(shí),計(jì)時(shí)中斷發(fā)生。
一秒鐘計(jì)數(shù)的次數(shù),由RTCCON[7:4]即TICCKSEL位決定:

TICCKSEL
因?yàn)槲覀兊木д耦l率也是32768,為方便計(jì)數(shù),所以我們設(shè)置RTCCON[7:4]為0,開啟滴答計(jì)時(shí)器需要設(shè)置RTCCON[8]位1:
TICEN
代碼如下:
- RTCCON = RTCCON & (~(0xf << 4)) | (1 << 8);
- TICCNT = 32768;
3. 操作ALARM鬧鐘
RTCALM

RTCALM寄存器控制報(bào)警功能的啟用和報(bào)警時(shí)間。請注意,RTCALM寄存器在斷電模式下將同時(shí)生成ALARM_INT和ALARM_WK信號(hào),但在正常模式下僅生成ALARM_INT信號(hào)。設(shè)置ALMEN[6]為1以產(chǎn)生ALARM_INT和ALARM_WK信號(hào)。
「舉例:」
比如我們想每個(gè)小時(shí)的25分58秒產(chǎn)生一個(gè)中斷信號(hào),那我們需要設(shè)置RTCALM[1]、RTCALM[0]為1,同時(shí)設(shè)置RTCALM[6]為1以開啟alarm功能,然后將BCD格式的時(shí)間設(shè)置到寄存器ALMSEC、ALMMIN。
代碼如下:
- RTCALM.ALM = (1 << 6)|(1 << 0)|(1 << 1);//使能bite:MINEN、SECEN
- RTCALM.SEC = 0x58;
- RTCALM.MIN = 0x25; //每小時(shí)25:58產(chǎn)生一次中斷
alarm功能設(shè)置鬧鐘時(shí)間寄存器如下:





寄存器操作,采用BCD格式。
五、完整代碼實(shí)現(xiàn)
滴答計(jì)時(shí)器和alarm鬧鐘會(huì)產(chǎn)生內(nèi)部中斷信號(hào),所以我們必須給這兩個(gè)中斷信號(hào)進(jìn)行中斷相關(guān)的初始化,并在中斷處理函數(shù)中增加相應(yīng)的處理代碼。
中斷號(hào)
參考datasheet 9.2.2 GIC Interrupt Table

rtc中斷號(hào)
關(guān)于中斷的初始化的寄存器配置,我們可以參考《11. 從0開始學(xué)ARM-基于Exynos4412中斷詳解、key程序編寫》
區(qū)別是,key連接在了第一級(jí)中斷控制器,而rtc的這兩個(gè)中斷則沒有。清中斷需要設(shè)置的寄存器如下:
「滴答計(jì)時(shí)器清中斷:」
- RTCINTP = RTCINTP | (1 << 0);
- //清GIC中斷標(biāo)志位
- ICDICPR.ICDICPR2 = ICDICPR.ICDICPR2 | (0x1 << 13);
- //清cpu中斷標(biāo)志位
- CPU0.ICCEOIR = CPU0.ICCEOIR&(~(0x3ff))|irq_num;
「alarm計(jì)時(shí)器清中斷:」
- RTCINTP = RTCINTP | (1 << 1);
- //清GIC中斷標(biāo)志位
- ICDICPR.ICDICPR2 = ICDICPR.ICDICPR2 | (0x1 << 12);
- //清cpu中斷標(biāo)志位
- CPU0.ICCEOIR = CPU0.ICCEOIR&(~(0x3ff))|irq_num;
「滴答計(jì)時(shí)器中斷初始化:」
- void rtc_tic(void)
- {
- RTCCON = RTCCON & (~(0xf << 4)) | (1 << 8);
- TICCNT = 32768;
- ICDDCR = 1; //使能分配器
- ICDISER.ICDISER2 = ICDISER.ICDISER2 | (0x1 << 13); //使能相應(yīng)中斷到分配器
- ICDIPTR.ICDIPTR19 = ICDIPTR.ICDIPTR19 & (~(0xff << 8))|(0x1 << 8); //選擇CPU接口
- CPU0.ICCPMR = 255; //中斷屏蔽優(yōu)先級(jí)
- CPU0.ICCICR = 1; //使能中斷到CPU
- }
「alarm初始化」
- void rtc_alarm(void)
- {
- RTCALM.ALM = (1 << 6)|(1 << 0)|(1 << 1);
- RTCALM.SEC = 0x58;
- RTCALM.MIN = 0x25; //每小時(shí)25:58產(chǎn)生一次中斷
- ICDDCR = 1; //使能分配器
- //使能相應(yīng)中斷到分配器
- ICDISER.ICDISER2 = ICDISER.ICDISER2 | (0x1 << 12);
- //選擇CPU接口
- ICDIPTR.ICDIPTR19 = ICDIPTR.ICDIPTR19 & (~(0xff << 0))|(0x1 << 0);
- CPU0.ICCPMR = 255; //中斷屏蔽優(yōu)先級(jí)
- CPU0.ICCICR = 1; //使能中斷到CPU
- }
「中斷處理函數(shù)」
- void do_irq(void)
- {
- static int a = 1;
- int irq_num;
- irq_num = CPU0.ICCIAR&0x3ff; //獲取中斷號(hào)
- switch(irq_num)
- {
- case 57: //按鍵key
- printf("in the irq_handler\n");
- //清GPIO中斷標(biāo)志位
- EXT_INT41_PEND = EXT_INT41_PEND |((0x1 << 1));
- //清GIC中斷標(biāo)志位
- ICDICPR.ICDICPR1 = ICDICPR.ICDICPR1 | (0x1 << 25);
- break;
- case 76:
- printf("in the alarm interrupt!\n");
- RTCINTP = RTCINTP | (1 << 1);
- //清GIC中斷標(biāo)志位
- ICDICPR.ICDICPR2 = ICDICPR.ICDICPR2 | (0x1 << 12);
- break;
- case 77:
- printf("in the tic interrupt!\n");
- RTCINTP = RTCINTP | (1 << 0);
- //清GIC中斷標(biāo)志位
- ICDICPR.ICDICPR2 = ICDICPR.ICDICPR2 | (0x1 << 13);
- break;
- }
- //清cpu中斷標(biāo)志位
- CPU0.ICCEOIR = CPU0.ICCEOIR&(~(0x3ff))|irq_num;
- }
「其他代碼:」
- void rtc_init(void)
- {
- RTCCON = 1;//使能RTC控制寫功能
- RTC.BCDYEAR = 0x20;// 2020年11月11日, 15:24:50.以BCD碼格式寫入
- RTC.BCDMON = 0x11;
- RTC.BCDDAY = 0x11;
- RTC.BCDHOUR = 0x15;
- RTC.BCDMIN = 0x24;
- RTC.BCDSEC = 0x50;
- RTCCON = 0;//關(guān)閉RTC控制寫功能
- }
- int main (void)
- { rtc_init();
- rtc_alarm();
- rtc_tic();
- //每隔一秒打印以下當(dāng)前時(shí)間
- while(1)
- {
- printf("%x-%x-%x %x:%x:%x\n",RTC.BCDYEAR,
- RTC.BCDMON,
- RTC.BCDDAY,
- RTC.BCDHOUR,
- RTC.BCDMIN,RTC.BCDSEC);
- delay_ms(1000);
- }
- }