實時時鐘入門

2019-06-04
Facebook
Twitter

實時時鐘(RTC)基本上就像手錶:以電池為電源,即使發生停電也能幫我們計算時間。如果在專題裡使用RTC,就能長時間計時。就算微控制器重新編程、拔掉USB線或電源插頭都還能運作。

包含Arduino在內的多數微控制器具有內建的計時器函式「millis()」,而晶片上也常內建計時器,能記錄像是分鐘、天數等較長的時間。那為什麼我們還需要獨立的RTC晶片呢?最主要的原因是millis()只能記錄Arduino最近一次通電開始的時間,也就是說當電源啟動,millisecond timer就會歸零。Arduino不會知道現在是「星期二」或「三月8日」,只知道「距離上一次啟動已經經過14,000毫秒」。

想在Arduino上設定時間該怎麼辦呢?這時我們就要用程式編寫日期和時間,從啟動的時間點開始計算。但如果沒電,就要像便宜電子鬧鐘一樣重設時間,因為電子鬧鐘沒電時都會閃爍顯示12:00。

這種基本的計時方法對某些專題而言還算可行。但像資料記錄器、時鐘等專題就需要有連續計時的功能,不會在Arduino電池沒電或重新編程時重設。因此,我們要加裝獨立的RTC。RTC是專門記錄時間的晶片,不僅可以計算閏年,還知道每個月份的天數。有一點需要注意的是它不會計算「日光節約時間」,因為每個地區的制度不同,要依照你所住的地區或其他需求來寫程式碼。

這是一塊舊的電腦主機板上的Dallas Semiconductor實時時鐘DS1387。其尺寸龐大,因為裡面有鋰電池。來源:Wikimedia Commons

 

選擇RTC

Maker們常用的實時時鐘有三種:PCF8523、DS1307和DS32231,都是很棒的電池供電RTC,適合資料記錄、製作時鐘、時戳、計時器和鬧鐘。它們都以I2C通訊協定溝通。只要把鈕扣型電池裝好,這些RTC都能計時好幾年。

» PCF8523為精準度普通的時鐘:每天誤差可達2秒,但它是上述三種之中最便宜的。而且可以搭配3.3V或5V電源及邏輯。

» DS1307是最常見的。精準度也算普通,但價格便宜又容易焊接。最適合搭配Arduino等5V微控制器;它的電源需求是5V,但可以搭配3.3V邏輯

» DS3231的資料表表示這款溫度補償晶片是「極準確I²C整合式RTC/TCXO/晶體」。而晶片本身跟它號稱的完全一樣。這是我們能買到精準度最好的小尺寸、低功率封裝RTC,3.3V和5V微控制板都可搭配使用。

使用DS3231

多數的RTC採用外部32kHz晶體振盪器以低電流計時。這樣是不錯,但這些晶體會有輕微的頻率漂移現象,尤其是遇到溫度變化。溫度對振盪頻率的影響非常微小,但累積起來仍然可觀。DS3231看起來很厚實,因為它的晶體在晶片內部!在整合式晶體旁邊有一個溫度感測器,它會對振盪次數進行加減來補償頻率的變化,維持計時精準。

Adafruit供應的DS3231是個外型小巧、適用麵包板的擴充板。只要在背面裝上CR1220鈕扣型電池後就能精確計時好幾年,就算主電源斷電也不怕。你可以焊接隨附的排針來插入麵包板,或直接焊接電線到焊墊上。

 

DS3231腳位一覽

電源腳位
• Vin — 這是電源腳位。因為這款RTC可以用2.3V到5.5V的電源供電,我們不需要穩壓器或電平轉換器就能使用3.3V或5V邏輯/電源。只要給板子和微控制器邏輯電平一樣電源,例如Arduino這種5V微控制器就使用5V。
• GND — 電源和邏輯的共用接地。
I2C邏輯腳位
• SCL — I2C時脈腳位,連接到微控制器的I2C時脈線。這個腳位和Vin 之間有10K上拉電阻。
• SDA — I2C資料腳位,連接到微控制器的I2C資料線。和Vin之間也有10K上拉電阻。
其他腳位
• BAT — 和電池正極焊墊是同一個連接點。如果要用鈕扣型電池為其他元件供電或用獨立電池提供備用電源,就使用這個腳位。VBat可以接受介於2.3V和5.5V,而DS3231在Vin斷電時會自動切換。
• 32K — 32kHz振盪器輸出。它是漏極開路,所以要加裝上拉電阻來直接從微控制器腳位讀取此訊號。
• SQW —方波或中斷輸出。它同樣是漏極開路,所以要加裝上拉電阻來從微控制器腳位讀取此訊號。
• RST — 這和多數的RST腳位有些不同。它不單是一個輸出,更具有重置外接裝置或指示主電源已斷電的設計。它是漏極開路,但具有內部的50K上拉電阻只要存在Vin就維持此腳位在高準位。當Vin下降、晶片切換至電池電源,此腳位就轉為低準位。

如果你想在溫度極低的地方使用RTC,可以參考Chris Fastie對四種DS3221晶片(及其電池)在極低溫環境下效能的比較

 

 

Arduino使用

你可以輕鬆將這個時鐘模組連接到任何微控制器;在此我們用Arduino(圖TK)。對於其他微控制器,只要確認它有I2C,並修改程式碼;很簡單!

我在拍這張照片前把連接Arduino 5V腳位和麵包板Vin軌的電源線拔除了,別忘記!

 

把Vin連接到電源,3V到5V都可以。用和微控制器邏輯一樣的電壓;多數的Arduino是5V。
把GND連接到電源/資料共用接地。
把SCL腳位連接到Arduino上的I2C時鐘SCL腳位。在Arduino Uno和其他Atmega328上,該腳位為A5;在Mega上是數位21腳;在Leonardo或Micro上是數位3腳。
把SDA腳位連接到Arduino的I2C資料SDA腳位。在Arduino Uno和其他Atmega328上,該腳位為A4;在Mega上是數位20腳;在Leonardo或Micro上是數位2腳。
注意:DS3231的預設I2C位址是0x68,無法更改。

 

下載RTClib
使用DS3231時需要能在RTC取得和設定時間的資料庫。我們用的是JeeLab提供超棒的RTClib;我們的版本有些不同,所以只能用我們提供的來搭配DS3231擴充板,而且要確認相容!

從我們的Github儲存庫下載Adafruit的RTClib,網址:github.com/adafruit/RTClib。

將未壓縮的資料夾重新命名為RTClib,並檢查RTClib資料夾內含RTClib.cpp和RTClib.h。

將RTClib資料庫資料夾放在[arduinosketchfolder]/libraries/資料夾。(如果這是你的第一個資料庫,可能需要建立資料庫子資料夾。)

重新啟動IDE。

在Adafruit.com也有很棒的Arduino資料庫安裝教學。

RTC初測試
我們首先要示範的是每秒從RTC讀取一次時間的測試腳本程式碼。我們也會示範更換電池時的情形,因為這會讓RTC停止。首先,在Arduino未啟動也沒有插入USB時把電池從電池座取出(圖TK)。等待3秒後裝回電池。如此就會重設RTC晶片。

 

載入範例腳本程式碼
在Arduino IDE裡開啟File→Examples→RTClib→ds3231,並上傳到和RTC連接好的Arduino。

 

接著以鮑率9600查看Serial Monitor(序列埠監控視窗)。幾秒鐘後,就會從報告裡看到Arduino發現這是DS3231第一次啟動,並且根據Arduino腳本程式碼設定時間。

把Arduino + RTC拔除幾秒鐘(或幾分鐘、幾小時、幾星期),再接回去。

下一次執行時,就不會看到「RTC斷電」的訊息,而是會立即顯示正確的時間!

接下來就不用再設定時間了。電池可以供電5年以上。

讀取時間
RTC開始滴答計時後,我們就可以來問它時間。現在來看腳本程式碼是如何達成這個目的。

void loop () {

DateTime now = rtc.now();

 

Serial.print(now.year(), DEC);

Serial.print(‘/’);

Serial.print(now.month(), DEC);

Serial.print(‘/’);

Serial.print(now.day(), DEC);

Serial.print(” (“);

Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);

Serial.print(“) “);

Serial.print(now.hour(), DEC);

Serial.print(‘:’);

Serial.print(now.minute(), DEC);

Serial.print(‘:’);

Serial.print(now.second(), DEC);

Serial.println();

 

用RTClib的話只有一種方法可以讀取時間,也就是呼叫now(),這個函數會回傳DateTime物件,描述呼叫now()時的年、月、日、時、分和秒。

有些RTC資料庫是改為呼叫RTC.year()和RTC.hour()等函數來讀取年份和鐘點。但這裡會出現一個問題,如果你剛好在3:15:00的前一秒(也就是3:14:59)呼叫,你會發現顯示的時間會少一分鐘,也就是3:14:00。如果把順序反過來,會看到3:15:59,也就是多一分鐘。

因為這個情形的可能性不低,尤其是你常常查詢時間的話,我們就會用「快照」方式一次讀取RTC的完整時間,接著像上面的程式碼一樣分割成day()和second()。雖然比較麻煩,但能避免錯誤是值得的。

我們也可以呼叫unixtime()來從DateTime物件取得「時戳」;這個函數會計算從1970年1月1日午夜開始的秒數(不計閏秒):

 

Serial.print(” since midnight 1/1/1970 = “);

Serial.print(now.unixtime());

Serial.print(“s = “);

Serial.print(now.unixtime() / 86400L);

Serial.println(“d”);

 

因為一天有60 * 60 * 24 = 86,400秒,我們也可以輕鬆算出自1970年開始的天數。該方法非常適合用於追蹤上一次查詢後過了多久時間,能讓你免於計算一大堆東西。舉例來說,如果想確認是否過了5分鐘,只要檢查unixtime()是否增加300,而且不用擔心小時轉換。

動手做更多

CircuitPython — CircuitPython和DS3231 RTC也很好搭配!Github的這個實用模組可以載入開發板來用Python程式碼設定和讀取時間。只要匯入DS3231模組、建立類別的實例(instance),並與datetime屬性互動來設定及取得時間(圖TK)。詳情請前往此處

Raspberry Pi —平價但缺少RTC,所以是從網絡時間協定(NTP)伺服器取得時間。對獨立的專題而言,要連接RTC很容易。記得要執行更新過的核心、搭配RTC驅動程式、啟用I2C,並把RTC加入/boot/config.txt,例如dtoverlay=i2c-rtc,ds3231。接著移除fake-hwclock套件並編輯硬體時鐘指令碼/lib/udev/hwclock-set來改為使用RTC。詳情請點此處

 

(譯:屠建明)

【原文】