Arduino的millis(),每次重開機就會從0開始計數,單位是千分之一秒,型別為unsigned long,大概50天便會溢位(2^32-1 / 1000 / 60 / 60 / 24 = 49.71),對某些情況而言已足夠,但若我們想要在Arduino沒電不運作時、仍然繼續計數,那麼就需要另一個晶片與額外的電池了。
所謂RTC(Real-Time Clock、即時時鐘),其功能就好像手錶一樣,由電池供應電力,持續地跳動計數,當電子電路其他部分斷電不運作時,RTC還是能繼續跑,記錄著正確的時間日期。個人電腦主機板也會有個RTC,當電腦關機時,還是會由一顆獨立的電池供應RTC電力,維持時間日期。
本篇以RTC晶片型號DS1307為範例,便宜不貴,精確度算普通。(若想要更好更精準的,可改用DS3231。)
我的板子是Arduino Uno R3,軟體開發環境為1.0.5 Windows版,電路圖(Fritzing格式)與原始碼可到這裡下載。
關於RTC,有很多已經組合好的「模組」 產品,譬如底下這三個:
Adafruit Industries的DS1307 Real Time Clock breakout board kit。此產品必須自己焊接組裝,底下是完成後的樣子,看起來很迷你精緻。
藝科資訊 Aroboto Studio的RTC時間模組。
在某拍賣網站找到的【秋葉原電腦周邊-旗艦店】㊣ mini_RTC DS1307時鐘模組 24C32記憶體 arduino 帶電池。
不過我是一個一個零件單獨購買,如下(依序是下圖中的從左到右,從上到下):
- RTC晶片,型號DS1307
- 鋰電池CR1220,3V
- 鋰電池座
- 電阻2.2K ohm(色環:紅紅紅金),1/4W,2個
- 陶瓷電容0.1uF(104)
- 石英晶體32.768 KHz,12.5 pF
首先是硬體線路,翻閱DS1307的規格資料表,有8支腳。
線路如下:
首先將Arduino的5V與GND接到麵包板。
DS1307的Vcc接5V。
DS1307的Vcc另接陶瓷電容0.1uF再接地。
DS1307的SQW/OUT,可輸出四種頻率的方波,此篇不用,不接任何東西。
DS1307的SCL,接到Arduino的SCL(Uno的類比腳位5)。
DS1307的SCL另接上拉電阻2.2K ohm接5V。
DS1307的SDA,接到Arduino的SDA(Uno的類比腳位4)。
DS1307的SDA另接上拉電阻2.2K ohm接5V。
DS1307的X1與X2,接石英晶體32.768 KHz的兩支腳,不分極性。
DS1307的GND,接地。
DS1307的Vbat與GND,接到鋰電池CR1220的正極與負極。
注意,在下圖中,因為我在Fritzing裡找不到鋰電池的元件與圖案,所以用2xAA電池代替。
實際情況如下:
特寫DS1307的部份。
DS1307以I2C作資料傳輸的介面,而我已經將SDA、SCL分別接上Arduino的類比腳位4、5了。
以上是硬體線路的部份,接下來軟體程式的部份。
我使用Arduino的Time程式庫,可到這裡下載。下載解壓縮後,裡頭有Time目錄與DS1307RTC目錄,搬移到sketchbook/libraries底下。還有個TimeAlarms目錄,不使用。
程式碼如下,程式碼能經由序列埠設定時間日期(年、月、日、時、分、秒),每隔一秒就輸出時間日期到序列埠。
// 匯入需要的程式庫標頭檔
// Wire是I2C傳輸,最低階
// DS1307RTC,讀取即時時鐘DS1307
// Time,較高階的程式庫,可令它從DS1307取得時間
#include <Wire.h>
#include <Time.h>
#include <DS1307RTC.h>
// 簡便的工具函式,格式化輸出字串
void pf(const char *fmt, ... ){
char tmp[128]; // resulting string limited to 128 chars
va_list args;
va_start (args, fmt );
vsnprintf(tmp, 128, fmt, args);
va_end (args);
Serial.print(tmp);
}
void setup() {
Serial.begin(115200);
// 設定從哪裡取得時間,由DS1307RTC程式庫的RTC.get負責
// 這也是此程式碼裡唯一使用DS1307RTC程式庫的地方
// 其餘皆使用較高階的Time程式庫的介面
setSyncProvider(RTC.get);
// 判斷能否從DS1307取得時間
if(timeStatus() != timeSet)
Serial.println("Unable to sync with the RTC");
else
Serial.println("RTC has set the system time");
}
void loop()
{
// 每隔一秒就輸出時間日期
pf("%d/%d/%d %02d:%02d:%02d\n", year(), month(), day(), hour(), minute(), second());
delay(1000);
}
// 聆聽序列埠,從這裡設定時間日期,格式如下:
// 「y2013」設定為2013年
// 「M1」設定為1月,注意是大寫的「M」
// 「d27」設定為26日
// 「h2」設定為2時
// 「m34」設定為34分
// 「s56」設定為56秒
void serialEvent()
{
int t = Serial.read();
int v = 0;
int temp;
while((temp = Serial.read()) != -1){
v = v * 10 + (temp - '0');
}
int y = year();
int M = month();
int d = day();
int h = hour();
int m = minute();
int s = second();
switch(t){
case 'y':
y = v;
break;
case 'M':
M = v;
break;
case 'd':
d = v;
break;
case 'h':
h = v;
break;
case 'm':
m = v;
break;
case 's':
s = v;
break;
default:
pf("Wrong\n");
break;
}
tmElements_t tm;
tm.Year = y - 1970; // 從1970年開始算
tm.Month = M;
tm.Day = d;
tm.Hour = h;
tm.Minute = m;
tm.Second = s;
// Time程式庫會有一段延遲時間,過後才會與RTC晶片同步
// 所以在此強制把日期時間寫入RTC晶片
setTime(makeTime(tm));
RTC.set(makeTime(tm));
}
開啟序列埠監控視窗後,可看到如下訊息。請記得把右下角的換行設定成「沒有行結尾(no line ending)」。
輸入y2014,便可更改年份,依此類推。
因為有鋰電池供應DS1307,即使拔掉USB線(板子的電源),DS1307仍會繼續運作,下次再讀取時間日期,仍會是正確的。
另外,若無外接鋰電池,DS1307也可運作,將Vbat接地即可,其餘不變,如下圖。只不過這麼一來,拔掉板子的電力來源後,時間就會錯掉了。
參考資料:
- Maxim datasheet:DS1307,64 x 8, Serial, I2C Real-Time Clock。
- ladyada:DS1307 RTC tutorial。
- Arduino Playground:Time程式庫(內含DS1307RTC)。
- Hobby Robotics:An I2C Bus Example Using the DS1307 Real-Time Clock。
- mezl:DS1307 時鐘IC用法。
- GROBO:Arduino - DS1307時鐘IC。