13. 從0學ARM-Cortex-A9 RTC裸機程序編寫


rtc

一、RTC

RTC(Real-Time Clock) 實時時鍾。

RTC

RTC是集成電路,通常稱為時鍾芯片。在一個嵌入式系統中,通常采用RTC來提供可靠的系統時間,包括時分秒和年月日等,而且要求在系統處於關機狀態下它也能正常工作(通常采用后備電池供電)。它的外圍也不需要太多的輔助電路,典型的就是只需要一個高精度的32.768kHz 晶體和電阻電容等,並且具有鬧鍾的功能。。

二、Exynos 4412 RTC

本篇主要以Cortex-A9 soc為例講解RTC的使用方法。

1. 特性

實時時鍾(RTC)單元可以通過備用電池供電,因此,即使系統電源關閉,它也可以繼續工作。RTC可以通過STRB/LDRB 指令將8位BCD碼數據送至CPU。這些BCD數據包括秒、分、時、日期、星期、月和年。RTC單元通過一個外部的32.768kHz 晶振提供時鍾。RTC具有定時報警的功能。
其功能說明如下:
1 -- 時鍾數據采用BCD編碼。
2 -- 能夠對閏年的年月日進行自動處理。
3 -- 具有告警功能,當系統處於關機狀態時,能產生警告中斷。
4 -- 具有獨立的電源輸入。
5 -- 提供毫秒級時鍾中斷,該中斷可以用於作為嵌入式操作系統的內核時鍾。

2. RTC Block

RTC Block Diagram

RTC在Linux中主要實現兩種功能,分別是系統掉電后的時間日期維持和時間日期報警(類似定時器)。

1)時間日期維持功能:
主要是由RTC實時時鍾控制寄存器RTCCON進行功能的使能控制,由節拍時間計數寄存器TICNT來產生節拍時間中斷來實現實時操作系統功能相關的時間和實時同步。其中對時間日期的操作實際上是對BCD碼操作,而BCD碼則是由一系列的寄存器組成(BCD秒寄存器BCDSEC、BCD分寄存器BCDMIN、BCD小時寄存器BCDHOUR、BCD日期寄存器BCDDATE、BCD日寄存器BCDDAY、BCD月寄存器BCDMON、BCD年寄存器BCDYEAR)。

2)報警功能:
主要由RTC報警控制寄存器RTC ALM進行功能使能控制,並產生報警中斷。報警時間日期的設置也是對一系列的寄存器進行操作(報警秒數據寄存器ALMSEC、報警分鍾數據寄存器ALMMIN、報警小時數據寄存器ALMHOUR、報警日期數據寄存器ALMDATE、報警月數據寄存器ALMMON、報警年數據寄存器ALMYEAR)。

3)閏年發生器
可以根據BCDDAY、BCDMON和BCDEEAR的值自動計算閏年。

3. 備用電池

備用電池可以驅動RTC邏輯。備用電池通過RTCVDD引腳向RTC塊,即使系統電源關閉。如果系統關閉,您應該阻止CPU和RTC邏輯。為了減少功耗,備用電池單獨驅動振盪電路和BCD計數器。

4. Alarm【報警】 功能

RTC在斷電模式或正常運行模式都可以在執行的時間產生一個ALARM_INT 和ALARM_WK信號。在正常工作模式下,它會產生ALARM_INT。在斷電模式下,它會ALARM_WK以及ALARM_INT信號。RTC報警寄存器(RTCALM)確定報警啟用/禁用狀態和報警時間設置的條件。

5. 晶振

32.768 kHz X-Tal Connection Example

晶振晶振時鍾頻率 32.768 kHz。

  1. XT_RTC_I 32.768 kHz RTC振盪器時鍾輸入
  2. XT_RTC_O 32.768 kHz RTC振盪器時鍾輸出
  3. XRTCCLKO 32.768 kHz RTC振盪器時鍾輸出,此信號默認關閉。可以通過設置寄存器RTCCON的CLKOUTEN字段為1來啟用它。

引腳連接圖:

引腳連接圖

由電路圖可知,只連接了RTC振盪器時鍾輸入引腳XT_RTC_I 。

三、寄存器

1. RTC寄存器組:

寄存器組

2. INTP

INTP

設置對應的bit為1就可以清除中斷。

3. RTCCON

RTCCON 描述 復位值
保留 [31:10] 保留 0
CLKOUTEN [9] 使能RTC通過XRTCCLKO輸出 0 disable 1 enbale 0
TICEN [8] 嘀嗒計時器 0 = 禁止 1 = 使能 0
TICCKSEL [7:4] 嘀嗒計時器子時鍾源選擇 4'b0000 = 32768 Hz 4'b0001 = 16384 Hz 4'b0010 = 8192 Hz 4'b0011 = 4096 Hz 4'b0100 = 2048 Hz 4'b0101 =1024 Hz 4'b0110 =512 Hz 4'b0111 =256 Hz 4'b1000 =128 Hz 4'b1001 =64 Hz 4'b1010 =32 Hz 4'b1011 =16 Hz 4'b1100 =8 Hz 4'b1101 =4 Hz 4'b1110 =2 Hz 4'b1111 =1 Hz 4'b0000
CLKRST [3] RTC時鍾計數復位 0 = 不復位 1 = 復位 0
CNTSEL [2] BCD計數選擇 0 = 分配 BCD 計數 1 = 保留 0
CLKSEL [1] BCD 時鍾選擇 0 = XTAL 1/2 divided clock 1 = 保留(XTAL 供頻) 0
RTCEN [0] RTC控制使能 0 = 禁止 1 = 使能 0
  1. RTCCON寄存器由10位組成,如控制BCD SEL讀/寫啟用的CTLEN,
    CNTSEL、CLKRST、TICKSEL、TICEN用於測試,CLKOUTEN用於RTC時鍾輸出控制。
  2. CTLEN位控制CPU和RTC之間的所有接口。因此,您應該在RTC控件中將其設置為“1”,在系統重置后啟用數據寫入的例程。為了防止無意中寫入BCD計數器寄存器,應該關閉電源前將CTLEN位清除為0。
  3. CLKRST是215時鍾分頻器的計數器復位。在設置RTC時鍾之前,應重置215時鍾分頻器以獲得精確的RTC操作。

四、RTC的操作

1. 設置時間

舉例:
我們要將當前時間設置為 2020年11月11日, 15:24:50

1) 先將RTC控制使能開啟,即RTCCON[0]置為1;
2)然后將時間對應的BCD格式數值,設置到應對的寄存器,BCDYEAR 、BCDMON 、BCDDAY 、BCDHOUR 、BCDMIN 、BCDSEC;
3) 將RTCCON[0]置為0,防止誤操作修改了時間;
4)如果我們要訪問當前時間,可以直接讀取寄存器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;//關閉RTC控制寫功能
}

2. 操作滴答定時器

TICNT

TICNT

RTC計時器是一個遞增計數器,並引發計時中斷。TICNT寄存器包含32位目標計數值,並且CURTICCNT寄存器包含32位當前計時計數。如果當前滴答數達到TICNT中指定的目標值時,計時中斷發生。

一秒鍾計數的次數,由RTCCON[7:4]即TICCKSEL位決定:

TICCKSEL

因為我們的晶振頻率也是32768,為方便計數,所以我們設置RTCCON[7:4]為0,開啟滴答計時器需要設置RTCCON[8]位1:

TICEN

代碼如下:

	RTCCON = RTCCON & (~(0xf << 4)) | (1 << 8);
	TICCNT = 32768;

3. 操作ALARM鬧鍾

RTCALM

RTCALM

RTCALM寄存器控制報警功能的啟用和報警時間。請注意,RTCALM寄存器在斷電模式下將同時生成ALARM_INT和ALARM_WK信號,但在正常模式下僅生成ALARM_INT信號。設置ALMEN[6]為1以產生ALARM_INT和ALARM_WK信號。

舉例:

比如我們想每個小時的25分58秒產生一個中斷信號,那我們需要設置RTCALM[1]、RTCALM[0]為1,同時設置RTCALM[6]位1以開啟alarm功能,然后將BCD格式的時間設置到寄存器ALMSEC、ALMMIN。

代碼如下:

	RTCALM.ALM = (1 << 6)|(1 << 0)|(1 << 1);//使能bite:MINEN、SECEN
	RTCALM.SEC = 0x58;
	RTCALM.MIN = 0x25;  //每小時25:58產生一次中斷

alarm功能設置鬧鍾時間寄存器如下:

ALMSEC

ALMMIN

ALMHOUR

ALMDAY

ALMMON

ALMYEAR

寄存器操作,采用BCD格式。

五、完整代碼實現

滴答計時器和alarm鬧鍾會產生內部中斷信號,所以我們必須給這兩個中斷信號進行中斷相關的初始化,並在中斷處理函數中增加相應的處理代碼。

中斷號

參考datasheet 9.2.2 GIC Interrupt Table

rtc中斷號

關於中斷的初始化的寄存器配置,我們可以參考《11. 從0開始學ARM-基於Exynos4412中斷詳解、key程序編寫》

區別是,key連接在了第一級中斷控制器,而rtc的這兩個中斷則沒有。
清中斷需要設置的寄存器如下:

滴答計時器清中斷:

RTCINTP	 = RTCINTP | (1 << 0);
//清GIC中斷標志位
ICDICPR.ICDICPR2 = ICDICPR.ICDICPR2 | (0x1 << 13);
//清cpu中斷標志位
CPU0.ICCEOIR = CPU0.ICCEOIR&(~(0x3ff))|irq_num; 

alarm計時器清中斷:

RTCINTP	 = RTCINTP | (1 << 1);
//清GIC中斷標志位
ICDICPR.ICDICPR2 = ICDICPR.ICDICPR2 | (0x1 << 12);
//清cpu中斷標志位
CPU0.ICCEOIR = CPU0.ICCEOIR&(~(0x3ff))|irq_num; 

滴答計時器中斷初始化:

void rtc_tic(void)
{
	RTCCON = RTCCON & (~(0xf << 4)) | (1 << 8);
	TICCNT = 32768;
	ICDDCR = 1;  //使能分配器
	ICDISER.ICDISER2 = ICDISER.ICDISER2 | (0x1 << 13); //使能相應中斷到分配器
	ICDIPTR.ICDIPTR19 = ICDIPTR.ICDIPTR19 & (~(0xff << 8))|(0x1 << 8); //選擇CPU接口
	CPU0.ICCPMR = 255; //中斷屏蔽優先級
	CPU0.ICCICR = 1;   //使能中斷到CPU
}

alarm初始化

void rtc_alarm(void)
{
	RTCALM.ALM = (1 << 6)|(1 << 0)|(1 << 1);
	RTCALM.SEC = 0x58;
	RTCALM.MIN = 0x25;  //每小時25:58產生一次中斷
	ICDDCR = 1;    //使能分配器
	 //使能相應中斷到分配器
	ICDISER.ICDISER2 = ICDISER.ICDISER2 | (0x1 << 12);
	//選擇CPU接口
	ICDIPTR.ICDIPTR19 = ICDIPTR.ICDIPTR19 & (~(0xff << 0))|(0x1 << 0); 
	CPU0.ICCPMR = 255; //中斷屏蔽優先級
	CPU0.ICCICR = 1;   //使能中斷到CPU
}

中斷處理函數

void do_irq(void)
{
	static int a = 1;
	int irq_num;
	irq_num = CPU0.ICCIAR&0x3ff;  //獲取中斷號
	switch(irq_num)
	{
		case 57: //按鍵key
			printf("in the irq_handler\n");
			//清GPIO中斷標志位
			EXT_INT41_PEND = EXT_INT41_PEND |((0x1 << 1)); 
			//清GIC中斷標志位
			ICDICPR.ICDICPR1 = ICDICPR.ICDICPR1 | (0x1 << 25); 
		break;
		case 76:
			printf("in the alarm interrupt!\n");
			RTCINTP	 = RTCINTP | (1 << 1);
			//清GIC中斷標志位
			ICDICPR.ICDICPR2 = ICDICPR.ICDICPR2 | (0x1 << 12); 
		break;
		case 77:
			printf("in the tic interrupt!\n");
			RTCINTP	 = RTCINTP | (1 << 0);
			 //清GIC中斷標志位
			ICDICPR.ICDICPR2 = ICDICPR.ICDICPR2 | (0x1 << 13);
			break;
	}
	//清cpu中斷標志位
	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;//關閉RTC控制寫功能
}
int main (void)
{	    rtc_init();
		rtc_alarm();
		rtc_tic();
		//每隔一秒打印以下當前時間
		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);
		}
}

更多 ARM Linux干貨,請關注 一口Linux


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM