RTC实时时钟实验(低功耗、纽扣电池供电)
目錄
- I.MX6U RTC 簡介
- 硬件原理分析
- 實驗程序編寫
- 修改文件MCIMX6Y2.h
- 編寫實驗程序
- 編譯下載驗證
- 編寫Makefile 和鏈接腳本
- 編譯下載
實時時鐘是很常用的一個外設,通過實時時鐘我們就可以知道年、月、日和時間等信息。因此在需要記錄時間的場合就需要實時時鐘,可以使用專用的實時時鐘芯片來完成此功能,但是現在大多數的MCU 或者MPU 內部就已經自帶了實時時鐘外設模塊。比如I.MX6U 內部的SNVS 就提供了RTC 功能,本章我們就學習如何使用I.MX6U 內部的RTC 來完成實時時鐘功能。
I.MX6U RTC 簡介
如果學習過STM32 的話應該知道,STM32 內部有一個RTC 外設模塊,這個模塊需要一個32.768KHz 的晶振,對這個RTC 模塊進行初始化就可以得到一個實時時鐘。I.MX6U 內部也有個RTC 模塊,但是不叫作“RTC”,而是叫做“SNVS”,這一點要注意!本章我們參考《I.MX6UL參考手冊》,而不是《I.MX6ULL 參考手冊》,因為《I.MX6ULL 參考手冊》很多SNVS 相關的
寄存器并沒有給出來,不知道是為何?但是《I.MX6UL 參考手冊》里面是完整的。所以本章我們使用《I.MX6UL 參考手冊》,如果直接在《I.MX6UL 參考手冊》的書簽里面找“RTC”相關的字眼是找不到的。I.MX6U 系列的RTC 是在SNVS 里面,也就是《I.MX6UL 參考手冊》的第46 章“Chapter 46 Secure Non-Volatile Storage(SNVS)”。
SNVS 直譯過來就是安全的非易性存儲,SNVS 里面主要是一些低功耗的外設,包括一個安全的實時計數器(RTC)、一個單調計數器(monotonic counter)和一些通用的寄存器,本章我們肯定只使用實時計數器(RTC)。SNVS 里面的外設在芯片掉電以后由電池供電繼續運行,I.MX6U-ALPHA 開發板上有一個紐扣電池,這個紐扣電池就是在主電源關閉以后為SNVS 供電的,如圖25.1.1 所示:
因為紐扣電池在掉電以后會繼續給SNVS 供電,因此實時計數器就會一直運行,這樣的話時間信息就不會丟失,除非紐扣電池沒電了。在有紐扣電池作為后備電源的情況下,不管系統主電源是否斷電,SNVS 都正常運行。SNVS 有兩部分:SNVS_HP 和SNVS_LP,系統主電源斷電以后SNVS_HP 也會斷電,但是在后備電源支持下,SNVS_LP 是不會斷電的,而且SNVS_LP是和芯片復位隔離開的,因此SNVS_LP 相關的寄存器的值會一直保存著。
SNVS 分為兩個子模塊:SNVS_HP 和SNVS_LP,也就是高功耗域(SNVS_HP)和低功耗域(SNVS_LP),這兩個域的電源來源如下:
SNVS_LP:專用的always-powered-on 電源域,系統主電源和備用電源都可以為其供電。
SNVS_HP:系統(芯片)電源。
SNVS 的這兩個子模塊的電源如圖25.1.2 所示:
圖25.1.2 中各個部分功能如下:
①、VDD_HIGH_IN 是系統(芯片)主電源,這個電源會同時供給給SNVS_HP 和SNVS_LP。
②、VDD_SNVS_IN 是紐扣電池供電的電源,這個電源只會供給給SNVS_LP,保證在系
統主電源VDD_HIGH_IN 掉電以后SNVS_LP 會繼續運行。
③、SNVS_HP 部分。
④、SNVS_LP 部分,此部分有個SRTC,這個就是我們本章要使用的RTC。
其實不管是SNVS_HP 還是SNVS_LP,其內部都有一個SRTC,但是因為SNVS_HP 在系統電源掉電以后就會關閉,所以我們本章使用的是SNVS_LP 內部的SRTC。畢竟我們肯定都不想開發板或者設備每次關閉以后時鐘都被清零,然后開機以后先設置時鐘。
其實不管是SNVS_HP 里面的RTC,還是SNVS_LP 里面的SRTC,其本質就是一個定時器,和我們在第八章講的EPIT 定時器一樣,只要給它提供時鐘,它就會一直運行。SRTC 需要外界提供一個32.768KHz 的時鐘,I.MX6U-ALPHA 核心板上的32.768KHz 的晶振就是提供這個時鐘的。寄存器SNVS_LPSRTCMR 和SNVS_LPSRTCLR 保存著秒數,直接讀取這兩個寄存器的值就知道過了多長時間了。一般以1970 年1 月1 日為起點,加上經過的秒數即可得到現在的時間和日期,原理還是很簡單的。SRTC 也是帶有鬧鐘功能的,可以在寄存器SNVS_LPAR 中寫入鬧鐘時間值,當時鐘值和鬧鐘值匹配的時候就會產生鬧鐘中斷,要使用時鐘功能的話還需要進行一些設置,本章我們就不使用鬧鐘了。
接下來我們看一下本章要用到的與SRTC 相關的部分寄存器,首先是SNVS_HPCOMR 寄存器,這個寄存器我們只用到了位:NPSWA_EN(bit31),這個位是非特權軟件訪問控制位,如果非特權軟件要訪問SNVS 的話此位必須為1。
接下來看一下寄存器SNVS_LPCR寄存器,此寄存器也只用到了一個位:SRTC_ENV(bit0),此位為1 的話就使能STC 計數器。
最后來看一下寄存器SNVS_SRTCMR 和SNVS_SRTCLR,這兩個寄存器保存著RTC 的秒數,按照NXP 官方的《6UL 參考手冊》中的說法,SNVS_SRTCMR 保存著高15 位,SNVS_SRTCLR保存著低32 位,因此SRTC 的計數器一共是47 位。
但是!我在編寫驅動的時候發現按照手冊上說的去讀取計數器值是錯誤的!具體表現就是時間是混亂的,因此我在查找了NXP 提供的SDK 包中的fsl_snvs_hp.c 以及Linux 內核中的rtc-snvs.c 這兩個驅動文件以后發現《6UL 參考手冊》上對SNVS_SRTCMR 和SNVS_SRTCLR 的
解釋是錯誤的,經過查閱這兩個文件,得到如下結論:
①、SRTC計數器是32位的,不是47位!
②、SNVS_SRTCMR的bit14:0這15位是SRTC計數器的高15位。
③、SNVS_SRTCLR的bit31:bit15這17位是SRTC計數器的低17位。
按照上面的解釋去讀取這兩個寄存器就可以得到正確的時間,如果要調整時間的話也是向這兩個寄存器寫入要設置的時間值對應的秒數就可以了,但是要修改這兩個寄存器的話要先關閉SRTC。
關于SNVS 中和RTC 有關的寄存器就介紹到這里,關于這些寄存器詳細的描述,請參考《I.MX6UL 參考手冊》第2931 頁的46.7 小節。本章我們使用I.MX6U 的SNVS_LP 的SRTC,配置步驟如下:
1、初始化SNVS_SRTC
初始化SNVS_LP 中的SRTC。
2、設置RTC 時間
第一次使用RTC 肯定要先設置時間。
3、使能RTC
配置好RTC 并設置好初始時間以后就可以開啟RTC 了。
硬件原理分析
本試驗用到的資源如下:
①、指示燈LED0。
②、RGB LCD 接口。
③、SRTC。
SRTC 需要外接一個32.768KHz 的晶振,在I.MX6U-ALPHA 核心板上就有這個32.768KHz的晶振,原理圖如圖25.2.1 所示:
實驗程序編寫
本實驗對應的例程路徑為:開發板光盤-> 1、裸機例程-> 16_rtc。
修改文件MCIMX6Y2.h
在第十三章移植的NXP 官方SDK 包是針對I.MX6ULL 編寫的,因此文件MCIMX6Y2.h中的結構體SNVS_Type 里面的寄存器是不全的,我們需要在其中加入本章實驗所需要的寄存器,修改SNVS_Type 為如下所示:
1 typedef struct { 2 __IO uint32_t HPLR; 3 __IO uint32_t HPCOMR; 4 __IO uint32_t HPCR; 5 __IO uint32_t HPSICR; 6 __IO uint32_t HPSVCR; 7 __IO uint32_t HPSR; 8 __IO uint32_t HPSVSR; 9 __IO uint32_t HPHACIVR; 10 __IO uint32_t HPHACR; 11 __IO uint32_t HPRTCMR; 12 __IO uint32_t HPRTCLR; 13 __IO uint32_t HPTAMR; 14 __IO uint32_t HPTALR; 15 __IO uint32_t LPLR; 16 __IO uint32_t LPCR; 17 __IO uint32_t LPMKCR; 18 __IO uint32_t LPSVCR; 19 __IO uint32_t LPTGFCR; 20 __IO uint32_t LPTDCR; 21 __IO uint32_t LPSR; 22 __IO uint32_t LPSRTCMR; 23 __IO uint32_t LPSRTCLR; 24 __IO uint32_t LPTAR; 25 __IO uint32_t LPSMCMR; 26 __IO uint32_t LPSMCLR; 27 }SNVS_Type;編寫實驗程序
本章實驗在上一章例程的基礎上完成,更改工程名字為“rtc”,然后在bsp 文件夾下創建名為“rtc”的文件夾,然后在bsp/rtc 中新建bsp_rtc.c 和bsp_rtc.h 這兩個文件。在bsp_rtc.h 中輸入如下內容:
1 #ifndef _BSP_RTC_H 2 #define _BSP_RTC_H 3 /*************************************************************** 4 Copyright ? zuozhongkai Co., Ltd. 1998-2019. All rights reserved. 5 文件名: bsp_rtc.h 6 作者: 左忠凱 7 版本: V1.0 8 描述: RTC驅動頭文件。 9 其他: 無 10 論壇: www.openedv.com 11 日志: 初版V1.0 2019/1/3 左忠凱創建 12 ***************************************************************/ 13 #include "imx6ul.h" 14 15 /* 相關宏定義*/ 16 #define SECONDS_IN_A_DAY (86400) /* 一天86400秒*/ 17 #define SECONDS_IN_A_HOUR (3600) /* 一個小時3600秒*/ 18 #define SECONDS_IN_A_MINUTE (60) /* 一分鐘60秒*/ 19 #define DAYS_IN_A_YEAR (365) /* 一年365天*/ 20 #define YEAR_RANGE_START (1970) /* 開始年份1970年*/ 21 #define YEAR_RANGE_END (2099) /* 結束年份2099年*/ 22 23 /* 時間日期結構體*/ 24 struct rtc_datetime 25 { 26 unsigned short year; /* 范圍為:1970 ~ 2099 */ 27 unsigned char month; /* 范圍為:1 ~ 12 */ 28 unsigned char day; /* 范圍為:1 ~ 31 (不同的月,天數不同).*/ 29 unsigned char hour; /* 范圍為:0 ~ 23 */ 30 unsigned char minute; /* 范圍為:0 ~ 59 */ 31 unsigned char second; /* 范圍為:0 ~ 59 */ 32 }; 33 34 /* 函數聲明*/ 35 void rtc_init(void); 36 void rtc_enable(void); 37 void rtc_disable(void); 38 unsigned int rtc_coverdate_to_seconds(struct rtc_datetime *datetime); 39 unsigned int rtc_getseconds(void); 40 void rtc_setdatetime(struct rtc_datetime *datetime); 41 void rtc_getdatetime(struct rtc_datetime *datetime); 42 43 #endif第16 到21 行定義了一些宏,比如一天多少秒、一小時多少秒等等,這些宏將用于將秒轉換為時間,或者將時間轉換為秒。第24 行定義了一個結構體rtc_datetime,此結構體用于描述日期和時間參數。剩下的就是一些函數聲明了,很簡單。
在文件bsp_rtc.c 中輸入如下內容:
/*************************************************************** Copyright ? zuozhongkai Co., Ltd. 1998-2019. All rights reserved. 文件名: bsp_rtc.c 作者: 左忠凱 版本: V1.0 描述: RTC驅動文件。 其他: 無 論壇: www.openedv.com 日志: 初版V1.0 2019/1/3 左忠凱創建 ***************************************************************/ 1 #include "bsp_rtc.h" 2 #include "stdio.h" 3 4 /* 5 * @description :初始化RTC 6 */ 7 void rtc_init(void) 8 { 9 /* 10 * 設置HPCOMR寄存器 11 * bit[31] 1 : 允許訪問SNVS寄存器,一定要置1 12 */ 13 SNVS->HPCOMR |= (1 << 31); 14 15 #if 0 16 struct rtc_datetime rtcdate; 17 18 rtcdate.year = 2018U; 19 rtcdate.month = 12U; 20 rtcdate.day = 13U; 21 rtcdate.hour = 14U; 22 rtcdate.minute = 52; 23 rtcdate.second = 0; 24 rtc_setDatetime(&rtcdate); /* 初始化時間和日期*/ 25 #endif 26 rtc_enable(); /* 使能RTC */ 27 } 28 29 /* 30 * @description : 開啟RTC 31 */文件bsp_rtc.c 里面一共有9 個函數,依次來看一下這些函數的意義。函數rtc_init 明顯是初始化rtc 的,主要是使能RTC,也可以在rtc_init 函數里面設置時間。函數rtc_enable 和rtc_disable分別是RTC 的使能和禁止函數。函數rtc_isleapyear 用于判斷某一年是否為閏年。函數rtc_coverdate_to_seconds 負責將給定的日期和時間信息轉換為對應的秒數。函數rtc_setdatetime
用于設置時間,也就是設置寄存器SNVS_LPSRTCMR 和SNVS_LPSRTCLR 。函數rtc_convertseconds_to_datetime 用于將給定的秒數轉換為對應的時間值。函數rtc_getseconds 獲取SRTC 當前秒數,其實就是讀取寄存器SNVS_LPSRTCMR 和SNVS_LPSRTCLR,然后將其結合成32 位的值。最后一個函數rtc_getdatetime 是獲取時間值。
我們在main 函數里面先初始化RTC,然后進入3S 倒計時,如果這3S 內按下了KEY0 按鍵,那么就設置SRTC 的日期。如果3S 倒計時結束以后沒有按下KEY0,也就是沒有設置SRTC時間的話就進入while 循環,然后讀取RTC 的時間值并且顯示在LCD 上,在文件main.c 中輸入如下所示內容:
1 #include "bsp_clk.h" 2 #include "bsp_delay.h" 3 #include "bsp_led.h" 4 #include "bsp_beep.h" 5 #include "bsp_key.h" 6 #include "bsp_int.h" 7 #include "bsp_uart.h" 8 #include "bsp_lcd.h" 9 #include "bsp_lcdapi.h" 10 #include "bsp_rtc.h" 11 #include "stdio.h" 12 13 /* 14 * @description : main函數 15 * @param : 無 16 * @return : 無 17 */ 18 int main(void) 19 { 20 unsigned char key = 0; 21 int t = 0; 22 int i = 3; /* 倒計時3S */ 23 char buf[160]; 24 struct rtc_datetime rtcdate; 25 unsigned char state = OFF; 26 27 int_init(); /* 初始化中斷(一定要最先調用!) */ 28 imx6u_clkinit(); /* 初始化系統時鐘*/ 29 delay_init(); /* 初始化延時*/ 30 clk_enable(); /* 使能所有的時鐘*/ 31 led_init(); /* 初始化led */ 32 beep_init(); /* 初始化beep */ 33 uart_init(); /* 初始化串口,波特率115200 */ 34 lcd_init(); /* 初始化LCD */ 35 rtc_init(); /* 初始化RTC */ 36 37 tftlcd_dev.forecolor = LCD_RED; 38 lcd_show_string(50, 10, 400, 24, 24, /* 顯示字符串*/ (char*)"ALPHA-IMX6UL RTC TEST"); 39 tftlcd_dev.forecolor = LCD_BLUE; 40 memset(buf, 0, sizeof(buf)); 41 42 while(1) 43 { 44 if(t==100) /* 1s時間到了*/ 45 { 46 t=0; 47 printf("will be running %d s......\r", i); 48 49 lcd_fill(50, 40,370, 70, tftlcd_dev.backcolor); /* 清屏*/ 50 sprintf(buf, "will be running %ds......", i); 51 lcd_show_string(50, 40, 300, 24, 24, buf); 52 i--;第35 行調用函數rtc_init 初始化RTC。
第42 到73 行是倒計時3S,如果在這3S 內按下了KEY0 按鍵就會調用函數rtc_setdatetime設置當前的時間。如果3S 倒計時結束以后沒有按下KEY0 那就表示不需要設置時間,跳出循環,執行下面的代碼。
第79 到89 行就是主循環,此循環每隔1S 調用函數rtc_getdatetime 獲取一次時間值,并且通過串口打印給SecureCRT 或者在LCD 上顯示。
編譯下載驗證
編寫Makefile 和鏈接腳本
修改Makefile 中的TARGET 為rtc,然后在在INCDIRS 和SRCDIRS 中加入“bsp/rtc”,修改后的Makefile 如下:
1 CROSS_COMPILE ?= arm-linux-gnueabihf- 2 TARGET ?= rtc 3 4 /* 省略掉其它代碼...... */ 5 6 INCDIRS := imx6ul \ 7 stdio/include \ 8 bsp/clk \ 9 bsp/led \ 10 bsp/delay \ 11 bsp/beep \ 12 bsp/gpio \ 13 bsp/key \ 14 bsp/exit \ 15 bsp/int \ 16 bsp/epittimer \ 17 bsp/keyfilter \ 18 bsp/uart \ 19 bsp/lcd \ 20 bsp/rtc 21 22 SRCDIRS := project \ 23 stdio/lib \ 24 bsp/clk \ 25 bsp/led \ 26 bsp/delay \ 27 bsp/beep \ 28 bsp/gpio \ 29 bsp/key \ 30 bsp/exit \ 31 bsp/int \ 32 bsp/epittimer \ 33 bsp/keyfilter \ 34 bsp/uart \ 35 bsp/lcd \ 36 bsp/rtc 37 38 /* 省略掉其它代碼...... */ 39 40 clean: 41 rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)第2 行修改變量TARGET 為“rtc”,也就是目標名稱為“rtc”。
第20 行在變量INCDIRS 中添加RTC 驅動頭文件(.h)路徑。
第36 行在變量SRCDIRS 中添加RTC 驅動驅動文件(.c)路徑。
鏈接腳本保持不變。
編譯下載
使用Make 命令編譯代碼,編譯成功以后使用軟件imxdownload 將編譯完成的rtc.bin 文件下載到SD 卡中,命令如下:
chmod 777 imxdownload //給予imxdownload 可執行權限,一次即可 ./imxdownload rtc.bin /dev/sdd //燒寫到SD 卡中,不能燒寫到/dev/sda 或sda1 設備里面!燒寫成功以后將SD 卡插到開發板的SD 卡槽中,然后復位開發板。程序一開始進入3S倒計時,如圖25.4.2.1 所示:
如果在倒計數結束之前按下KEY0,那么RTC 就會被設置為我們代碼中設置的時間和日期值,RTC 運行如圖24.4.2.2 所示:
我們在main 函數中設置的時間是2018 年1 月15 日,16 點23 分0 秒,在倒計數結束之前按下KEY0 按鍵設置RTC,圖24.4.2.2 中的時間就是我們設置以后的時間。
總結
以上是生活随笔為你收集整理的RTC实时时钟实验(低功耗、纽扣电池供电)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Win10系统中英文切换
- 下一篇: 多点电容触摸屏实验