網頁最後修改時間:2017/08/11
而在這篇網頁中,DS3231 (+AT24C32, 32KBytes EEPROM) RTC 模組會在程式編譯上傳的同時被更新年份、日期和時間,配合整合型 LCD (I2C模式) 和大型數字顯示方式,分別在 LCD 上以四個不同頁面分別以年、日/月、(12/24格式)小時:分鐘和完整格式的方式顯示,藉由這種方式讓使用者了解 RTC 模組的基本使用方法,請看影片
當然,DS3231 RTC 模組不只是一個時鐘而已,還有可程式的方波輸出功能、兩組日曆鬧鐘可以設定和內建精度 ±3°C 的數位溫度感測器可以使用,另外此模組也外掛了一顆 32KBytes 的 EEPROM 晶片可用來儲存資料,有用到時千萬別忘了 !
相關更詳細的資料請參閱晶片手冊 (或賣場的附件資料),或上網搜尋!
時間戳記對於資料的紀錄很重要,之後在其他地方也會用到,所以關於 DS3231 RTC 模組其他的功能就不多講了,但不管如何,RTC 模組時間的設定是一個重點,一定要會!
接線:
下面是使用 Arduino Nano 做為微控制器的接線圖,不過同樣也適用於其他的 Arduino 開發板,因為是使用 I2C 通訊,最多只是接腳編號不一樣而已,換一下就能用!
另外有一點需要注意,DS3231 RTC 模組適用於 DC 3V3 與 DC 5V 的系統,雖然這裡選用的是 DC 5V 系統,但是只要將微控制器 (例如 ESP8266 開發板) 與整合型 LCD 更換為 3V3 版本的,就是適用於 DC 3V3 的系統。
DS3231 RTC Module + 整合型 LCD 參考接線圖 |
實際接線與程式執行情形 - 顯示年份於整合型 LCD 螢幕上 |
Arduino 函式庫:
接線完成之後,先來認識等一下會用到的 DS3231 暫存器。基本上,只有位址 0x00 ~ 0x06 會用到,分別代表秒、分鐘、小時、日、月和年,可讀取或是寫入 (變更) 時間,是 BCD 碼表示法
DS3231 暫存器位址與說明, 來源:maxim integrated, ds3231 datasheets |
如何更新 RTC 模組的時間?
RTC 模組要更新時間可以在任何時間不準確的時候來做,但是對於微控制器來說不是那麼的方便 (我是這樣認為) ? 方便且又實用的方法,就是在編譯的時候順便變更時間,而使用的方 法就是檢查 DS3231 暫存器地址 0x0F 裡的第 7 個字元 (bit) OSF ( Oscillator Stop Flag, 振盪器停止旗標 ),OSF 只有在下面幾種情況時被設置為 1:
- 第一次通電 (或是只裝上鈕扣電池時)
- VCC 和 VBAT 上的電壓不足以支持振盪器動作
- 在電池備份模式,地址 0x0E 的 /EOSC 位元被關閉
- 晶體的外部影響 ( 雜訊、漏電流 ... 等 )
在 Arduino IDE 選單 "File/Examples/RTClib" 開啟 "ds3231" 草稿檔 ( Sketch ),接著設定好 "Tools" 選單下面的 "Board"、"Processor" 和 "Port" 各選項
對照下面程式碼,修改 ds3231.ino 裡 setup() 函式跟下面一樣;修改之後另存新檔為 ds3231_timesync
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | void setup () { Serial.begin(9600); delay(3000); // wait for console opening if (! rtc.begin()) { Serial.println("Couldn't find RTC"); while (1); } if (rtc.lostPower()) { Serial.println("RTC lost power, lets set the time!"); // following line sets the RTC to the date & time this sketch was compiled DateTime timeupdate = DateTime( F( __DATE__ ), F( __TIME__ ) ) + TimeSpan( /*days*/0, /*hours*/0, /*minutes*/0, /*seconds*/20 ); // This line sets the RTC with an explicit date & time, for example to set // January 21, 2014 at 3am you would call: rtc.adjust( timeupdate ); } } |
line 15 是最重要的一行,timeupdate 是最終要寫入到 RTC 模組裡面 (暫存器位址 0x00 ~ 0x06 ) 設定時間的,包含下面兩個部分的加總:
- DateTime( F( __DATE__ ), F( __TIME__ ) )
這一部分所產生出來的時間是程式編譯的時間 (2017/08/10 23:04:10) - TimeSpan( /*days*/0, /*hours*/0, /*minutes*/0, /*seconds*/20 )
這一部分我添加上去,用來補償編譯至上傳完成之後執行 RTC 模組時間更新的時間差。如果沒有加上這一段的話,則最後在 Serial Monitor 上面的時間會與作業系統的時間出現多十幾秒以上的時間差。
程式編譯至上傳結束的時間計時 |
DS3231 RTC 模組時間校對之後與系統時間的比對 |
所以歸納之後可得到步驟流程如下:
首先,先確認 Arduino 開發板通電 (這裡指的是將 USB 插上電腦,通電與通訊),RTC 模組鈕扣電池拔掉也沒通電
- 打開 Arduino IDE (建議使用 v1.8.3) 並 ( "File/New" ) 開啟一個新的草稿檔
- Arduino 開發板通電後,設定好 "Tools" 選單下面的 "Board"、"Processor" 和 "Port" 各選項,編譯並上傳程式 *
- RTC 模組通電,這時 OSF 會被設定為 1
- 打開上面剛剛修改過的 ds3231_timesync 程式,編譯之後上傳
- 打開 Serial Monitor ( 設定為 9600 bps ) 對照輸出的時間和系統時間之間的差距 **
- RTC 模組時間差異可接受,直接裝上鈕扣電池,完成 RTC 模組時間設定;否則,下一步驟
- 將步驟 5 的時間差,回填修改到 setup() 函式裡 TimeSpan 的引數
- 將 RTC 模組斷電,跳至步驟 1
* 由於修改 RTC 模組時間的程式會重複上傳幾次,若上傳之前就存在之前的程式,那麼是無法在正常情況下進行修改的;因為在上傳程式的時候,即便 OSF 先被設置為 1,由於 Arduino 開發板是在通電的情況,程式還未上傳就已被就程式修改過了,新程式是來不及更新時間的。
** 編譯的時間跟電腦效能有很大關係,所以為了減少每一次編譯相同程式的時間,不要開一堆耗能的程式。
/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*
大型數字時鐘製作?
這個數字時鐘使用到自訂字元的方式顯示大型數字,這些大型數字字元的介紹與說明,在部落格的這個網頁中 { 單晶片 + Arduino + 樹莓派 } 整合型 LCD ( @ I2C 模式 ) 的漂亮數字顯示 ( 自訂字型或圖案 ) 有詳細說明,在這裡我們將這些常用的程式碼組合成一個 Arduino 標頭檔案來用,有該零件雲端資料夾的,可以在 codes/Arduino/RTC 模組大型數字標頭檔 資料夾中下載 IICLCDLargeNumber.h 這個檔案來用。
/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 程式碼:
這邊的程式碼使用 ds3231_timesync 這個程式進行修改,因此同樣具備修改 RTC 模組時間的功能,同時加上了對於整合型 LCD 的支援,可顯示影片中展示的時間各項資訊。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | #include <Wire.h> #include <RTClib.h> /** 整合型 LCD @ IIC Mode */ #include "IICLCDLargeNumber.h" /** DS3231 */ RTC_DS3231 rtc; /** time update */ unsigned long previousMills = 0; const long interval = 1000; // milliseconds int idx = 0; char buffer[16]; void setup() { Serial.begin( 9600 ); /** DS3231 RTC Module */ rtc.begin(); // Wire.begin() 在此初始化了 if (rtc.lostPower()) { Serial.println("RTC lost power, lets set the time!"); // following line sets the RTC to the date & time this sketch was compiled DateTime timeupdate = DateTime( F( __DATE__ ), F( __TIME__ ) ) + TimeSpan( /*days*/0, /*hours*/0, /*minutes*/0, /*seconds*/13 ); // 預先編譯得知編譯時間之後,補上所花費的時間 (second) rtc.adjust( timeupdate ); // This line sets the RTC with an explicit date & time, for example to set // January 21, 2014 at 3am you would call: // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0)); } /** 整合型 LCD */ initLCD(); delay(100); clearLCD(); delay(100); // 自訂字元寫入 writeCGRAM( &CGRAM_block[0], 3, 1 ); writeCGRAM( &CGRAM_block[24], 3, 4 ); displayCharOnLCD( 1, 1, "**DS3231 ready**", 16 ); delay( 1000 ); displayCharOnLCD( 2, 1, "**IIC LCD ready*", 16 ); delay( 900 ); clearLCD(); delay( 100 ); } void loop() { // time update very second unsigned long currentMillis = millis(); if( currentMillis - previousMills >= interval ){ previousMills = currentMillis; DateTime now = rtc.now(); ++idx; if( idx == 17 ) { clearLCD(); // 清除螢幕 idx = 1; } switch( idx ) { case 1: // 2017 sprintf( buffer, "%4d", now.year() ); displayNumeric( buffer, 4 ); break; case 3: // 08/09 sprintf( buffer, "%02d%02d", now.month(), now.day() ); displayCharOnLCD( 1, 9, "/", 1 ); displayCharOnLCD( 2, 9, "/", 1 ); displayNumeric( buffer, 4 ); break; case 5: // 15:51 sprintf( buffer, "%02d:%02d", now.hour(), now.minute() ); displayNumeric( buffer, 5 ); break; default: { if( ( idx > 7 ) && ( idx < 16 ) ) { // ***2017/08/09*** sprintf( buffer, "***%4d/%02d/%02d***", now.year(), now.month(), now.day() ); displayCharOnLCD( 1, 1, buffer, 16 ); // ****15:51:38**** sprintf( buffer, "****%02d:%02d:%02d****", now.hour(), now.minute(), now.second() ); displayCharOnLCD( 2, 1, buffer, 16 ); } } } } } |
/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 優化:加入判斷 I2C 裝置存在的程式碼:
眼尖的使用者不知道看到沒有 ? 在 setup() 函式中 rtc.begin() 原本的 if 判斷式,整個被移除掉了,為什麼呢 ? 因為這個判斷式決不會產生作用,因為回傳都是 true,有跟沒有一樣 !
所以接下來,就要加入這個檢測 I2C 裝置的功能到上面的程式中,它可以用來輔助確認接線是否正確以及檢測裝置是否存在。
在上面程式的最下方,加入下面的這一個新增的函式
1 2 3 4 5 6 | bool checki2cdevice( uint8_t i2caddr ) { // 呼叫此函示之前, Wire 必須先初始化 Wire.beginTransmission( i2caddr ); if( Wire.endTransmission() == 0 ) return true; return false; } |
接著到 setup() 裡面,對照下面修改裡面的程式碼 (就是插入兩行 //----- 之間的程式碼 ),重新編譯上傳即可 ( RTC 模組不需要斷電 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /** DS3231 RTC Module */ rtc.begin(); // Wire.begin() 在此初始化了 //--------------------- delay(10); // wait wire to stable if( !checki2cdevice( DS3231_ADDRESS ) ) { Serial.println( "DS3231 RTC Module not found!" ); // 沒有 delay(10), 這裡輸出不了! while(1) delay(1); } if( !checki2cdevice( IIC_ADDR_LCD1 ) ) { Serial.println( "LCD not found!" ); while(1) delay(1); } //--------------------- if (rtc.lostPower()) { // ...省略... } |
之後,只要整合型 LCD 或是 DS3231 RTC 模組沒有裝好,就會在 Serial Monitor 輸出找不到該裝置的訊息。所以之後若是出現問題,別忘了打開 Serial Monitor 看看!
*********************************************************************************
相關模組可至露天賣場訂購:
- Arduino Nano 擴充底板
- {5V}整合型{4/8BIT,IIC,4SPI}1602英文字型藍底白字LCD螢幕(附排針與可變電阻)
- 高精度即時時鐘 DS3231 + 32KByte EEPROM二合一模組
結論:
在本篇網頁中,提供了設定 RTC 模組時間的步驟,以及時間設定與取出做為數位時鐘顯示的程式碼,展示了 DS3231 RTC 模組基本的操作,並且在最後提供了檢測 I2C 裝置的方法,在電源取得不易且沒有網路或是任何資源可取得時間的環境下,就可選擇 RTC 模組來做為保持時間運行的裝置。
<< 部落格相關網頁 >>
沒有留言:
張貼留言
留言屬名為"Unknown"或"不明"的用戶,大多這樣的留言都會直接被刪除掉,不會得到任何回覆!
發問問題,請描述清楚你(妳)的問題,別人回答前不會想去 "猜" 問題是什麼?
不知道怎麼發問,請看 [公告] 部落格提問須知 - 如何問問題 !