13. 從0學ARM-Cortex-A9 RTC裸機程式編寫
阿新 • • 發佈:2021-02-07
![rtc](https://img-blog.csdnimg.cn/20201206093016864.png)
## 一、RTC
RTC(Real-Time Clock) 實時時鐘。
![RTC](https://img-blog.csdnimg.cn/20201206092030675.png)
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](https://img-blog.csdnimg.cn/20201123103421774.png#pic_center)
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
![晶振](https://img-blog.csdnimg.cn/2020112311163052.png#pic_center)晶振時鐘頻率 32.768 kHz。
![ ](https://img-blog.csdnimg.cn/20201123114047515.png#pic_center)
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來啟用它。
引腳連線圖:
![引腳連線圖](https://img-blog.csdnimg.cn/20201123143512733.png#pic_center)
由電路圖可知,只連線了RTC振盪器時鐘輸入引腳XT_RTC_I 。
## 三、暫存器
### 1. RTC暫存器組:
![暫存器組](https://img-blog.csdnimg.cn/20201123114739983.png#pic_center)
### 2. INTP
![INTP](https://img-blog.csdnimg.cn/2020112311484763.png#pic_center)
設定對應的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時鐘輸出控制。
3. CTLEN位控制CPU和RTC之間的所有介面。因此,您應該在RTC控制元件中將其設定為“1”,在系統重置後啟用資料寫入的例程。為了防止無意中寫入BCD計數器暫存器,應該關閉電源前將CTLEN位清除為0。
4. CLKRST是2^15^時鐘分頻器的計數器復位。在設定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。
```c
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](https://img-blog.csdnimg.cn/20201123135216279.png#pic_center)
RTC計時器是一個遞增計數器,並引發計時中斷。TICNT暫存器包含32位目標計數值,並且CURTICCNT暫存器包含32位當前計時計數。如果當前滴答數達到TICNT中指定的目標值時,計時中斷髮生。
一秒鐘計數的次數,由RTCCON[7:4]即TICCKSEL位決定:
![TICCKSEL](https://img-blog.csdnimg.cn/2020112315084456.png#pic_center)
因為我們的晶振頻率也是32768,為方便計數,所以我們設定RTCCON[7:4]為0,開啟滴答計時器需要設定RTCCON[8]位1:
![TICEN](https://img-blog.csdnimg.cn/2020112315130496.png#pic_center)
程式碼如下:
```c
RTCCON = RTCCON & (~(0xf << 4)) | (1 << 8);
TICCNT = 32768;
```
### 3. 操作ALARM鬧鐘
#### RTCALM
![RTCALM](https://img-blog.csdnimg.cn/2020112313531034.png#pic_center)
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。
程式碼如下:
```c
RTCALM.ALM = (1 << 6)|(1 << 0)|(1 << 1);//使能bite:MINEN、SECEN
RTCALM.SEC = 0x58;
RTCALM.MIN = 0x25; //每小時25:58產生一次中斷
```
alarm功能設定鬧鐘時間暫存器如下:
![ALMSEC](https://img-blog.csdnimg.cn/2020112313553918.png#pic_center)
![ALMMIN](https://img-blog.csdnimg.cn/20201123135556345.png#pic_center)
![ALMHOUR](https://img-blog.csdnimg.cn/20201123135609652.png#pic_center)
![ALMDAY](https://img-blog.csdnimg.cn/20201123135625576.png#pic_center)
![ALMMON](https://img-blog.csdnimg.cn/20201123135638557.png#pic_center)
![ALMYEAR](https://img-blog.csdnimg.cn/20201123135649968.png#pic_center)
暫存器操作,採用BCD格式。
## 五、完整程式碼實現
滴答計時器和alarm鬧鐘會產生內部中斷訊號,所以我們必須給這兩個中斷訊號進行中斷相關的初始化,並在中斷處理函式中增加相應的處理程式碼。
### 中斷號
參考datasheet *9.2.2 GIC Interrupt Table*
![rtc中斷號](https://img-blog.csdnimg.cn/20201123153732178.png#pic_center)
關於中斷的初始化的暫存器配置,我們可以參考《11. 從0開始學ARM-基於Exynos4412中斷詳解、key程式編寫》
區別是,key連線在了第一級中斷控制器,而rtc的這兩個中斷則沒有。
清中斷需要設定的暫存器如下:
**滴答計時器清中斷:**
```c
RTCINTP = RTCINTP | (1 << 0);
//清GIC中斷標誌位
ICDICPR.ICDICPR2 = ICDICPR.ICDICPR2 | (0x1 << 13);
//清cpu中斷標誌位
CPU0.ICCEOIR = CPU0.ICCEOIR&(~(0x3ff))|irq_num;
```
**alarm計時器清中斷:**
```c
RTCINTP = RTCINTP | (1 << 1);
//清GIC中斷標誌位
ICDICPR.ICDICPR2 = ICDICPR.ICDICPR2 | (0x1 << 12);
//清cpu中斷標誌位
CPU0.ICCEOIR = CPU0.ICCEOIR&(~(0x3ff))|irq_num;
```
**滴答計時器中斷初始化:**
```c
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初始化**
```c
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
}
```
**中斷處理函式**
```c
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;
}
```
**其他程式碼:**
```c
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