2013/01/27

Arduino練習:RTC即時時鐘DS1307

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接地即可,其餘不變,如下圖。只不過這麼一來,拔掉板子的電力來源後,時間就會錯掉了。



參考資料:

134 comments:

  1. 請問您,有些DS1307會搭配24c32,有什麼用途?

    ReplyDelete
    Replies
    1. 我沒用過24C32耶。
      上網搜尋後,24C32是個EEPROM晶片,容量是32Kbit記憶體,就是用來儲存資料。

      我猜想,或許可以用來,讓使用者透過某種方式設定某時間日期,然後,當RTC跑到那個時刻,便可以響起鈴聲之類的。也就是說變成鬧鐘。

      Delete
  2. Replies
    1. 忘記說了,如果是Arduino Uno的ATmega328,已經內建1024 bytes的EEPROM。

      Delete
  3. Raspberry Pi也可以裝DS1307 只是邏輯準位是3.3V

    ReplyDelete
    Replies
    1. 嗯,可參考這篇http://learn.adafruit.com/adding-a-real-time-clock-to-raspberry-pi/overview

      Delete
    2. 我自己也寫了一篇「Raspberry Pi與即時時鐘RTC」
      http://yehnan.blogspot.tw/2014/01/raspberry-pirtc.html

      Delete
  4. 好像setTime()後面要加一句
    RTC.set(now());
    才能正確設置,我也是從別處找到答案的:
    http://forum.arduino.cc/index.php?topic=128928.0

    ReplyDelete
    Replies
    1. 我有RTC.set(makeTime(tm));這一行啊。

      Delete
  5. Anonymous8/7/13 12:51

    RTC.set(makeTime(tm));這行我設定時間給DS1307 也是沒作用
    我有加入鋰電池 當我開啟序列埠監控視窗設定日期後有改日期 然後關掉序列埠監控視窗 拔掉usb 之後再插入usb然後開啟序列埠監控視窗他的時間還是沒有我設定的時間

    ReplyDelete
    Replies
    1. 更改日期時間後,在還沒拔掉usb之前,是否能正確讀取日期時間呢?

      若可以,那就是電池部分有問題囉,導致拔掉usb後將沒有電力了。

      Delete
    2. Anonymous8/7/13 13:34

      更改時間後可以讀到正確時間 我有用三用電表量電池有輸出電壓 我將usb拔掉後arduino uno 板子是完全沒有電的 是否是arduino板子沒電導致鋰電池沒有供電給ds1307呢?

      Delete
    3. 拔掉usb後arduino沒電是正常,但鋰電池必須供電給ds1307,日期時間才會持續跳動。

      Delete
    4. Anonymous8/7/13 16:40

      剛剛我將序列埠監控視窗開啟後設定日期設定完畢後關閉序列埠監控視窗 再次開啟序列埠監控視窗 然後顯示的數據不是我設定過的 這是否跟電池供電問題無關? 在操作過程中無拔除usb

      Delete
    5. 不是你設定過的?那是什麼呢?
      所以根本沒有設定成功吧?

      重開序列埠監控視窗會使得arduino重置,這應該是原因吧。

      Delete
    6. Anonymous9/7/13 07:32

      如何知道時間是設定成功的?

      Delete
    7. 根據我的sketch,會每秒輸出目前日期時間到序列埠,如果設定成功,監控視窗馬上就會看到設定後的日期時間啊。

      Delete
    8. Anonymous9/7/13 14:25

      我檢查了一下麵包版有鬆脫現象 修理好後就正常了 目前順利使用中

      Delete
    9. Anonymous9/7/13 22:47

      感謝

      Delete
  6. 您好
    能否請教一個問題
    在下使用return方式 來把時間指給一個變數
    char* 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);
    // mySerial.print(tmp);
    return tmp;
    }

    char* timeRtc = pf("%d%d%d%02d%02d%02d", year(), month(), day(), hour(), minute(), second());
    但回傳的資料
    年月日時的部分正常
    但分和秒格式錯誤(如下)
    2013102810 « â ª@

    能否提供除錯方向

    謝謝您

    ReplyDelete
    Replies
    1. 原本我寫的pf不回傳東西,無回傳值(void)。

      您回傳的東西tmp,屬於區域變數(陣列tmp),只存活在該函式裡,pf回傳後(結束後),tmp就不見了,裡頭的資料就會被別的東西覆蓋,年月日時的部分正常,那是剛好而已。

      你可以把
      char tmp[128];
      改成
      static char tmp[128];
      試試看吧,讓它的存活時間與整支程式相同。

      不過這樣一來,因只有一份tmp,下次呼叫pf的話,裡頭的東西就會被改掉。

      Delete
  7. 不好意思想請教一下, 在您的 sketch 中, 只有 setup 時 sync 一次 RTC, 那一開始提到的 millis() 溢位問題不是依然存在嗎?

    ReplyDelete
    Replies
    1. 呃,不太清楚你的問題。
      時間到了,millis()就會溢位。

      現在加入RTC了,就可以取用RTC的日期時間,我記得DS1307可以到2099/12/31吧。

      Delete
  8. Anonymous1/11/13 13:18

    不好意思, 我問得太模糊, 您的意思是說, 如果原本在code中有用到 millis() 來作運算, 可以透過Time library 改讀 RTC 的時間來運算, 譬如說用 now() 來取代 millis(), 對嗎?
    而 RTC chip 的好處在於維持 counter 不被斷電或 reset 影響, 否則在不斷電的情況下只需要手動 setTime 一次, 就算沒有 RTC chip, 也可以用 now() 來運算, 防止 50 天溢位的問題, 但是精度由 ms 變成 s.
    不知道這樣的理解是不是正確?

    ReplyDelete
    Replies
    1. 如果改用Time library的now(),精確度的確會從ms變成s。

      是的,如果有RTC chip,由外部電池供電,就可以維持住日期時間,這也是RTC chip的功用。Time library其實還是去呼叫millis(),一秒一秒增加,然後每隔一段時間才跟RTC chip同步時間。

      若沒有RTC,也可以使用Time library,手動setTime一次。但是它就沒辦法跟RTC chip同步,在Time.c裡,
      time_t now(){
      while( millis() - prevMillis >= 1000){ // 這裡可能會有問題,因為millis()會溢位歸零
      sysTime++;
      prevMillis += 1000;
      #ifdef TIME_DRIFT_INFO
      sysUnsyncedTime++; // this can be compared to the synced time to measure long term drift
      #endif
      }
      if(nextSyncTime <= sysTime){ // 該與RTC chip同步時間了
      if(getTimePtr != 0){ // 若有RTC chip
      time_t t = getTimePtr(); 取得RTC的時間
      if( t != 0)
      setTime(t); // 同步
      else
      Status = (Status == timeNotSet) ? timeNotSet : timeNeedsSync;
      }
      }
      return sysTime;
      }

      void setTime(time_t t){
      #ifdef TIME_DRIFT_INFO
      if(sysUnsyncedTime == 0)
      sysUnsyncedTime = t; // store the time of the first call to set a valid Time
      #endif

      sysTime = t;
      nextSyncTime = t + syncInterval;
      Status = timeSet;
      // 這裡會再去呼叫millis(),便可避掉millis歸零的問題
      prevMillis = millis(); // restart counting from now (thanks to Korman for this fix)
      }

      光看以上的實作,你說的「就算沒有 RTC chip, 也可以用 now() 來運算, 防止 50 天溢位的問題」可能不成立,在while( millis() - prevMillis >= 1000){ 那一行可能會有問題,當millis()溢位後會變成小值,譬如0x00000005,而prevMillis卻是個大值,譬如0xFFFFFFF1,我本來以為此兩者相減後會產生預期之外的值,造成問題,不過使用Uno與Arduino IDE 1.0.5試驗後,發現相減後會得到0x00000014,,而且prevMillis加上1000後也會產生正確的值,所以應不會有問題,時間仍會正常地遞增。

      以上所說只是我看原始碼與進行一些簡單的試驗所得到的猜測,我沒有真的去讓Arduino跑50天讓它溢位。

      所以,若以上的Time library原始碼不會因millis超過50天溢位而產生問題,那麼我們就可以使用Time library、不加RTC chip,來取得時間,精確度是秒。

      Delete
    2. 使用Time程式庫也可以達到精確度ms,需要自己修改Time.h Time.cpp,
      Time.cpp裡也是會呼叫millis(),記錄在static time_t prevMillis裡,
      所以可以自己加介面,拿到prevMillis後,再自己呼叫millis(),
      ...經過一番邏輯、運算...等等...
      就能得到ms的精確度。

      只是一些想法,我沒有做過。

      Delete
  9. Anonymous1/11/13 15:50

    超感謝您的解釋, 我大概了解 Time library 跟 millis() 的關係了, 花費您那麼多時間精神教導, 真是抱歉~
    可是如果像您說的例子裡, millis() 溢位的時候, millis() - prevMillis < 1000, 那 while( millis() - prevMillis >= 1000) 這個迴圈裡的 sysTime++ 和 prevMillis +=1000 就不會發生, sysTime++ 沒發生的話 if(nextSyncTime <= sysTime) 這個決定重新 sync 的條件也不成立, 這樣不就卡住了?

    ReplyDelete
    Replies
    1. 我剛剛查了一下,根據C的spec,
      Every operation on unsigned integers always produces a result value that is congruent modulo 2^n to the true mathematical result of the operation.

      所以若x與y都是unsigned long,
      x=4; y = 7; x - y會等於0xFFFFFFFD,很大的值。
      x=4; y = 0xFFFFFFFD,那麼x-y會等於7。在此例裡剛好是我們希望的。
      所以,如果Arduino的編譯器符合上述行為,Time程式庫的寫法就是正確的,沒問題。

      另外,若prevMillis等於0xFFFFFFFD,
      那麼prevMillis += 1000;後,prevMillis會等於997,也是此例想要的行為。

      millis()溢位後,millis() - prevMillis會< 1000,這樣沒錯,但經過1000毫秒後,就會> 1000了。

      我本來擔心unsigned long小值 減 大值,會產生大值或奇怪的數值,看來我想錯了。

      Delete
  10. Anonymous1/11/13 18:38

    有道理, 我腦袋卡住好久, 現在終於看懂了..
    所以就 Time 程式庫的寫法, 當發生 millis() 溢位的時候, 頂多晚個 1000 ms, 就會把 prevMillis 修正成新的值, 這時 sysTime 時間可能會稍稍落後, 等下一次的 sync 就會補回來
    再次感謝~

    ReplyDelete
    Replies
    1. 並不會落後啊,跟平常一樣在跑啊。

      Delete
  11. Anonymous2/11/13 01:51

    對喔, 你說得沒錯, 完全不會落後, 小數目減大數目的結果就像在小數目前多加 1bit, 所以在這個狀況下相減的餘數都不會錯.

    ReplyDelete
  12. 葉先生您好(好像電影一代宗師裡的對白^^)
    能否請問
    有一個錯誤是這樣的
    我把unsigned long starttime = millis();
    之後中間有一些其他UART的動作程式嗎
    大約30秒後
    Serial.println(millis())約30000;
    Serial.println(starttime)約1;
    結果Serial.println(millis()-starttime);
    跑出48900000的值
    可以給各除錯的方向嗎?
    感謝

    ReplyDelete
    Replies
    1. 應該不會有問題,我測試的結果也是正常的。

      Delete
    2. 感謝您回覆還讓您測試了
      中間的程式碼因故無法公開(還請見諒)
      但事實上
      在下在serial監視窗中的確得到這樣的錯誤
      不知道該往哪個方向下去dubug
      能否請葉先生給各方向
      感恩

      Delete
    3. 根據你給的程式碼,我測試並沒有錯誤。
      是不是混雜了unsigned int與int?
      每一秒就計算並輸出一次吧,看看從何時開始得到錯誤結果。

      Delete
    4. 葉先生
      謝謝您立即的回覆
      感謝

      在下說明這個程式碼(請參考聯結http://sensor.free.sahost.cc/image/apc_particleCounter_1121.ino)

      是uno利用UART和一個3g模組(simcom 5218A發送資料到HTTP上的內容
      錯誤會產生在第322行Serial.println(millis()-starttime);會跑出這樣的結果(請見圖片http://sensor.free.sahost.cc/image/millis_error.png)
      由於您那沒有此3g模組所以也無法實際測試
      可以的話還請您幫忙看看
      這樣的millis錯誤會是什麼方向,目前方向有四
      1.您剛有提到unsigned,但您看我程式中starttime也已經設定為unsigned long)
      2.millis使用Timer0,記憶體相關問題
      3.或者println本身就不能
      4.IDE compiler隨機性問題
      以上

      感謝

      Delete
    5. cz=millis()-starttime;
      cz是long不是unsigned long

      Delete
    6. 葉先生(有你的,馬上看見一個疑點)

      我立馬修改程式碼(請看下面程式碼)
      http://sensor.free.sahost.cc/image/apc_particleCounter_1122.ino
      把17行的cz改成unsigned long

      然後跑程式

      但跑到.ino中第324行時,又發生了一樣mills()變很大的情況(請看下面t監視窗結果第1376行)
      http://sensor.free.sahost.cc/image/result_monitor_port_1122.txt
      m12_s12_4294933758

      葉先生 這真是難為阿,您可以再提供點方向來除錯嗎?

      感恩

      Delete
    7. Serial.print("m12_");
      Serial.println(millis());
      Serial.print("s12_");
      Serial.println(starttime);
      Serial.print("m12_s12_");
      Serial.println(millis()-starttime);
      的輸出是
      m12_143392
      s12_176931
      m12_s12_4294933758

      millis()比starttime小,所以「減」後得到很大的值。

      不過我看不出為什麼millis()會比starttime小。 嗯,真奇怪。

      Delete
    8. 葉先生
      不好意思晚回應您
      其實兩天前看見您早期blog有提到暫存器與編譯器相關資訊
      我在想millis()這個問題是不是該往那個方向去進行?
      您覺得這個方向是否正確?

      感恩

      Delete
    9. 暫存器與編譯器?什麼資訊?

      不知道是starttime出問題,還是millis()出問題。

      我也沒什麼頭緒。

      Serial.print("m12_s12_");那附近的程式碼位於do-while迴圈內,迴圈外有starttime=millis();,
      我建議在這個迴圈前後印出starttime的值,看看有無改變。

      setup()會不會太長了?可以先整理一下吧。

      用了goto,是不是可以改掉。

      duta[x-5]=='T',當x等於0時,這會讀取陣列合法範圍以外的地方吧,雖然是"讀取",但還是不要吧。

      Delete
    10. This comment has been removed by the author.

      Delete
    11. 葉先生
      謝謝您的回覆
      我看了您早期的blog文章
      讓我想往底層的地方去看看
      例如 51,組語 編譯 stack 記憶體測試 除錯器等等

      然後 您提的幾個點我先試試

      另外 這幾天 往51等底層去找
      看到了幾本不錯的書
      我先來研究看看
      有進度在po上來和您交流
      1.建立嵌入式系統,White, O'REILLY
      2.DEBUG HACKS, 安部東洋等,O'REILLY

      Delete
  13. 請問您 如果想要在特定時間跑之前的程式 要如何撰寫 比如說 原本在一開始的時間跑了A程式 之後設定在隔日凌晨再跑一段A 一直重複下去這樣 感激

    ReplyDelete
    Replies
    1. 將你說的程式放到函式裡,在loop裡讀取RTC日期時間,進行判斷後呼叫你想執行的函式。

      Delete
    2. 喔喔 I got it !! 我試試看 感激不盡 sorry 我是新手 感覺好像問了蠢問題

      Delete
  14. 請問SDA,SCL怎麼改用其他腳位輸入

    ReplyDelete
    Replies
    1. 不能改吧,就是要使用SDA與SCL腳位。

      I2C屬於匯流排架構,可以多個裝置共用SDA與SCL腳位。

      Delete
  15. #include

    int clockAddress = 0x68; // This is the I2C address


    // Convert normal decimal numbers to binary coded decimal
    byte decToBcd(byte val)
    {
    return ( (val/10*16) + (val%10) );
    }

    // Convert binary coded decimal to normal decimal numbers
    byte bcdToDec(byte val)
    {
    return ( (val/16*10) + (val%16) );
    }

    // 1) Sets the date and time on the ds1307
    // 2) Starts the clock
    // 3) Sets hour mode to 24 hour clock
    // Assumes you're passing in valid numbers,
    // Probably need to put in checks for valid numbers.
    void setDateDs1307(byte second,byte minute,byte hour)
    {
    // Use of (byte) type casting and ascii math to achieve result.

    Wire.beginTransmission(clockAddress);
    Wire.write(byte(0));
    Wire.write(decToBcd(second)); // 0 to bit 7 starts the clock
    Wire.write(decToBcd(minute));
    Wire.write(decToBcd(hour)); // If you want 12 hour am/pm you need to set
    // bit 6 (also need to change readDateDs1307)
    Wire.endTransmission();
    }

    // Gets the date and time from the ds1307 and prints result
    void getDateDs1307(byte *second,byte *minute,byte *hour)
    {
    // Reset the register pointer
    Wire.beginTransmission(clockAddress);
    Wire.write(0);
    Wire.endTransmission();

    Wire.requestFrom(clockAddress, 7);

    // A few of these need masks because certain bits are control bits
    *second = bcdToDec(Wire.read() & 0x7f);
    *minute = bcdToDec(Wire.read());

    // Need to change this if 12 hour am/pm
    *hour = bcdToDec(Wire.read() & 0x3f);

    }

    void displaytime()
    {
    byte second,minute,hour;

    getDateDs1307(&second,&minute,&hour);

    Serial.print(hour,DEC);
    Serial.print(":");
    if(minute<10){
    Serial.print("0");
    }
    Serial.print(minute,DEC);
    Serial.print(":");
    if(second<10){
    Serial.print("0");
    }
    Serial.print(second,DEC);
    Serial.print(" ");
    }
    void setup()
    {
    Wire.begin();
    Serial.begin(9600);
    setDateDs1307(50,59,12);//second,minute,hour
    }

    void loop() {
    displaytime();
    delay(1000);
    }



    葉大這是我打的程式碼
    想問一下要怎麼讓監控視窗的時間自動換行
    這個的時間只會在第一行一直接下去

    ReplyDelete
    Replies
    1. 在loop()裡加一行println("");

      Delete
  16. 葉大再請問一下
    DS1307輸出是二進位?
    如果要做成共陽極LED顯示要怎麼把位數分開輸出?

    ReplyDelete
    Replies
    1. 我使用兩套程式庫
      // DS1307RTC,讀取即時時鐘DS1307
      // Time,較高階的程式庫,可令它從DS1307取得時間

      設定後,呼叫year()、month()、day()、hour()、minute()、second()就可以取得年月日時分秒。

      假設25秒,存在int s = 25;裡,s / 10 可得到2,s % 10可得到5。

      至於輸出七段顯示器,請參考另兩篇吧:
      Arduino練習:seven-segment display七段顯示器與時鐘
      http://yehnan.blogspot.tw/2012/02/arduinoseven-segment-display.html
      Arduino練習:四合一的七段顯示器
      http://yehnan.blogspot.tw/2013/08/arduino_26.html

      Delete
  17. 葉大我是想要做真空管時鐘
    但是真空管控制IC是二進位ABCD轉十進位0-9
    所以目前我已經把RTC輸出的數字時分秒都拆成個別一個數字了
    我的假想是用if()判斷式
    如果a=1則ABCD=0001
    來完成輸出到真空管控制IC的ABCD
    雖然很不精簡但應該是可行的吧@@

    ReplyDelete
    Replies
    1. 看不懂你說的「真空管控制IC是二進位ABCD轉十進位0-9」。

      加油,祝好運。

      Delete
  18. SyncArduinoClock:88: error: expected `;' before 'tm'
    SyncArduinoClock:89: error: 'tm' was not declared in this scope
    SyncArduinoClock:95: error: 'makeTime' was not declared in this scope
    我有出現這些問題,請問我是哪邊沒寫好嗎?

    ReplyDelete
    Replies
    1. SyncArduinoClock:88: error: expected `;' before 'tm'
      看起來是少了「;」

      SyncArduinoClock:89: error: 'tm' was not declared in this scope
      大概因為前一個錯誤,所以tm並未正常宣告

      SyncArduinoClock:95: error: 'makeTime' was not declared in this scope
      makeTime是Time程式庫裡的東西,請檢查程式庫是否安裝了,是否匯入標頭檔

      Delete
  19. while((temp = Serial.read()) != -1){
    v = v * 10 + (temp - '0');
    }
    請問一下 這一句是做怎麼的 謝謝

    ReplyDelete
    Replies
    1. 從Serial一次讀取一個byte,直到值為-1(代表沒東西了),
      每個byte代表ASCII數字,減去'0'的話,得到整數0~9,
      每個整數代表一個位數,組合成一個大整數。

      假設Serial.read()會依序讀到1 3 2 4,
      那麼v的值就會是1,然後變成1 * 10 + 3 = 13,
      然後變成13 * 10 + 2 = 132,
      然後變成132 * 10 + 4 = 1324。

      Delete
    2. 噢 明白了 謝謝

      Delete
    3. 我還有一個問題想確認一下
      假設我輸入y2014
      讀取的次序是y=>2=>0=>1=>4
      因為 int t = Serial.read();在最開始,
      所以t讀取的字是y ,去分辨是h, m, s, d, M, y
      然後就是你所說的
      對不對?

      Delete
    4. 謝謝 葉師父

      Delete
  20. Anonymous20/7/14 01:09

    葉先生您好:
    我想請問一個問題,我照您的敘述建立了DS1307的時鐘,但鋰電池是使用CR2032供電。
    在arduino上可以正常的設定日期時間,也可以在serial monitor上看到時間在跑。
    但是當我每次重新開啟serial monitor的時候,時間都是從最後設定的時間開始跑。
    比如說當我設定 "M7" 的時間為2000/7/1 3:52:20,之後每次重開serial monitor都會從2000/7/1 3:52:20開始跑。
    似乎DS1307本身只記住設定時間,但沒有在計時。我如果停止所有電源供應,時間則會歸零。
    搜了一下網路上的文章,我想可能是晶體沒有在震盪。不知道您的看法是如何?

    ReplyDelete
    Replies
    1. > 也可以在serial monitor上看到時間在跑
      Time程式庫會運用Arduino函式millis()來跑,不需要存取底下的DS1307,因為不斷存取的話較耗電,等待滿足某些條件後,才會寫入DS1307,詳細情形需查看time_t now()。

      > 之後每次重開serial monitor都會從2000/7/1 3:52:20開始跑
      > 似乎DS1307本身只記住設定時間,但沒有在計時
      嗯,我也猜想如此。

      > 我想可能是晶體沒有在震盪
      那就換個石英晶體32.768 KHz,12.5 pF吧,這種零件很小很像似,是不是拿到錯的零件了?

      可以試試保留DS1307的電池,拔掉Arduino板的電源、然後再插上,看看會讀到什麼樣的時間。

      鋰電池與Arduino有共同接地嗎?

      Delete
    2. Anonymous20/7/14 10:47

      謝謝您的回覆。

      >那就換個石英晶體32.768 KHz,12.5 pF吧,這種零件很小很像似,是不是拿到錯的零件了?
      可以確認的是頻率是正確的,至於12.5pF就沒辦法確認,是那種大約1公分長的小圓柱體(長得跟您照片上的一樣)。
      買的時候有問過老闆,老闆說只有這一種,但是也沒有確認是12.5pF的。

      >可以試試保留DS1307的電池,拔掉Arduino板的電源、然後再插上,看看會讀到什麼樣的時間。
      這我有試過,還是一樣從設定時的時間開始跑。

      >鋰電池與Arduino有共同接地嗎?
      有,我把 "電池的負極" ,"DS1307的Vcc另接陶瓷電容0.1uF再接地" 的接地 跟 "DS1307的GND" 都接在一起了,然後會連接到arduino的GND。

      我會去換個新的石英晶體試試看。想再請問一下,石英晶體對於焊接時的熱會非常敏感嗎?

      Delete
    3. 石英晶體確認是32.768 KHz的話,應該就沒錯了,我買的時候也沒有在意12.5pF的部份。

      > 石英晶體對於焊接時的熱會非常敏感嗎?
      零件受熱太久都會壞,orz。換許先在麵包板上試試看。

      這樣看來,我只能猜測:
      1. 線路有錯,請檢查看看吧。
      2. 程式庫的版本,您或許下載版本較新,可能有所改變,導致我這篇裡的程式變得不對了。

      Delete
    4. Anonymous20/7/14 19:57

      今天換過新的石英震盪器了,換完之後DS1307的時間就會前進了,現在無論何時重開serial monitor都會從正確的時間開始顯示。
      感謝您的幫助。我是初學者,也沒有任何程式語言的經驗,您的文章對我有很大的幫助。非常感謝。

      Delete
  21. 實際用過pf()之後覺得很好用,請問一下pf()函式怎麼排string?如果有相關說明的話我可以自己學著用。

    ReplyDelete
    Replies
    1. 照理說,用法跟C語言標準程式庫的printf一樣,但Arduino程式庫裡的函式實作,為了縮小程式大小,功能往往少於標準的定義,譬如xxprintf預設不能印浮點數。



      Delete
  22. 原來是printf,那應該沒問題,謝囉。

    ReplyDelete
  23. 我以原來的程式碼為基礎(http://yehnan.blogspot.tw/2013/01/arduinortcds1307.html),改用Timer程式庫(使用1.3版,https://github.com/JChristensen/Timer/tree/v1.3),
    寫好後放在https://github.com/yehnan/arduino_practices/tree/master/RTC_DS1307_Timer
    改寫後,輸入設定值時,必須在末尾加一個非字母或數字的字元,譬如:
    「M9 」,含有M、9、空白字元。

    ReplyDelete
  24. 您好,想請教一個問題,如果不裝那顆鋰電池,可以做動嗎?

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. DS1307只要Vcc有電源,就可以動。

      Delete
    3. 了解! 謝謝您^^

      Delete
  25. 前輩請問一下,這IC可否設定為,每十五分鐘鬧鐘叫一次?等關閉後下一個十五分鐘又叫一次,周而復始的。

    ReplyDelete
    Replies
    1. 嗯,應該不行。
      你想要的功能,應該要自己寫程式實作。

      Delete
  26. 請問一下,要怎麼設定時間?

    ReplyDelete
    Replies
    1. 根據這篇內文,
      透過序列埠輸入y2014,便可更改年份,依此類推,其他請見程式碼。

      Delete
    2. 不好意思,再請教您一個問題
      為什麼我重開序列埠後,上次設定的時間又跑掉了

      Delete
    3. 如果有持續供電給RTC,時間應該保持正常。

      Delete
    4. 但我的usb都沒有拔除,所以也是供電問題囉?

      Delete
    5. 呃,不應該會這樣。

      Delete
  27. 請問如果要把數值讀取到七段顯示器上如何製作呢

    ReplyDelete
    Replies
    1. 請參考
      Arduino練習:seven-segment display七段顯示器與時鐘
      http://yehnan.blogspot.tw/2012/02/arduino_21.html

      Arduino練習:四合一的七段顯示器
      http://yehnan.blogspot.tw/2012/02/arduino_21.html

      另外,拙作《Arduino輕鬆入門:範例分析與實作設計》的第5.4節「即時時鐘」,使用四合一的七段顯示器與RTC。

      Delete
  28. 請問為什麼我把電池拆掉再裝回去之後就不能讀取時間了?

    ReplyDelete
    Replies
    1. 電池壞了,電池沒電了。
      線路錯了。電路更動了。
      etc.. etc...

      Delete
  29. 葉大,您好 我在實作上遇到一點問題
    想結合兩個範例RTC DS3231 +LCD 1602製作一個簡單的時鐘
    兩個模組在個別的範例程式都可以正常運作
    但是如果把此篇的程式碼加上一段lcd顯示
    #include
    LiquidCrystal lcd(12, 11, 5, 4, 3, 2); //這個5,4,3,2這個接腳有檢查過了
    void setup() {
    lcd.begin(16, 2);
    lcd.print("hello, world!");
    }
    單單只是額外加入顯示一個hello, world!
    lcd就會顯示亂碼
    不知道問題出在哪裡
    還請您指點一下,謝謝

    ReplyDelete
    Replies
    1. include 後面加的是IDE內建的LiquidCrystal

      Delete
    2. 光這樣看不出問題。

      亂碼是什麼?
      試試在print之前加上lcd.setCursor(0, 0);
      若只單獨使用LCD 1602,沒問題嗎?

      Delete
  30. 老師您好,我是個才剛接觸Arduino的新手,多多指教
    我買了一塊如下面連結圖中一樣的DS1307板子,用在Arduino MEGA2560上
    http://www.atomsindustries.com/assets/images/items/1078/1078.jpg
    我用純粹的杜邦線把SCL,SDA分別接到2560板子上的21,20腳位(就是對應著的I2C口)
    也接上VCC到5V,GND接到GND
    下載上面的程式後,竟然!!沒有反應
    串口監視器中連Unable to sync with the RTC的失敗提示都沒有!
    就像是卡住了一樣,直到我拔掉其中一個連接才出現Unable to sync with the RTC...
    為什麼會這樣子呢?望不吝賜教。

    ReplyDelete
    Replies
    1. 更正︰是只有拔掉GND或SCL(21腳位)的時候才會出現Unable to sync with the RTC,拔掉VCC或SDA都仍然沒有反應

      Delete
    2. 沒反應很奇怪,至少會看到"Unable to sync with the RTC"或是"RTC has set the system time"。

      這塊板子上有DS1307、還有24C32,都是I2C介面,你接對邊了嗎?

      Delete
    3. 意思是要把P1和P2的i2c端口都要接到板子上?
      😂我只有接P1的端口

      Delete
    4. This comment has been removed by the author.

      Delete
    5. 嗯,看起來DS1307應該是P1那邊。

      Delete
    6. 這個模組已經裝好電池了吧?

      Delete
    7. 我先試試把P1和P2都接好吧

      Delete
    8. 想不到有什麼問題。

      Delete
    9. 還是不行...
      把P1和P2都接上SDA,SCL,VCC,GND
      中間沒有電阻,就是導線直接
      P1和P2插在板子上的位置都是共用的...
      太奇怪了

      Delete
    10. 我找了很久也沒找出問題
      稍為有眉目的就是似乎出於setSyncProvider(RTC.get);這句
      程序好像一直在這句等待RTC回應的樣子,所以沒有動作
      會不會是IDE版本和庫不合呢?

      另外這塊板子,原來P1和P2本來就是接在一起的,所以接哪邊的I2C口都是一樣的

      Delete
    11. SDA與SLC應會接在一起,因為I2C是匯流排(bus)。

      > 會不會是IDE版本和庫不合呢?
      或許吧。

      http://playground.arduino.cc/Code/Time 好像有一些關於新版本的訊息,舊版跟Arduino 1.6.1不合。

      Delete
  31. This comment has been removed by the author.

    ReplyDelete
  32. 你好~~
    我照著你的程式上傳之後,輸入y2014,顯示2044年...打M10,顯示5月...不管我輸入幾月幾日都不規則變數!!

    ReplyDelete
    Replies
    1. 嗯,哪裡出錯呢?

      序列埠的傳輸速率?線路有問題?程式庫新舊版不同?還是...?

      Delete
    2. 序列埠的傳輸速率是對的,線路正常,版本是ARDUINO IDE內建升級最新的。
      我用TIME內建範例檔TimeRTCSet,查了GOOGLE發現用網站http://www.epochconverter.com/轉換工具...把時間轉換代碼就正常可以設定時間...
      再回來用你寫的程式卻無法設定...

      Delete
    3. 嗯,謝謝回報。

      但只有這些描述,我也無法看出問題所在。

      Delete
    4. 序列埠監控視窗,應設為 沒有行結尾(no line ending)。

      Delete
    5. 哇~~可以了耶!!謝謝~~

      Delete
  33. 請問一下
    我想把RTC取得的時間上傳至google spread sheet上
    本來是用char buf[50]
    後來照上面回復改成
    static char buf[50]
    string(buf[50])
    但是還是傳不上去
    請問還能怎麼改呢?

    ReplyDelete
    Replies
    1. > 照上面回復改成
      照哪裡?上面在哪?

      > 還是傳不上去
      資訊不足,無法回答。

      Delete
  34. 葉大您好
    小弟為arduino初學者
    想請問照葉大的code該如何將時間變數print在SD卡上
    data.print(??)

    ReplyDelete
    Replies
    1. 抱歉忘了補充data是小弟的SD卡裡要讀的檔案

      Delete
    2. 對,
      開啟SD卡上的檔案,然後寫入,可使用print或write。

      Delete
  35. 可以請問這段是什麼意思嗎?還有為什麼要從1970開始
    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));

    ReplyDelete
    Replies
    1. tmElements_t是Time程式庫定義的struct,
      其Year從1970開始算,所以你給它40的話,代表的是2010年。
      把日期時間填入後,呼叫setTime,
      照理說這樣就好了,Time程式庫底下會去使用DS1307RTC程式庫,
      不過我當初使用時,會有同步問題,所以再自己呼叫RTC.set寫入日期時間。

      Delete
  36. Anonymous13/2/17 14:38

    葉難大您好
    我目前用的rtc是ds3232
    請問有辦法在燒錄RTC時 自動抓取當前PC時間嗎
    我是想說這樣能夠在燒錄不止一個RTC時
    只要電源不中斷就能夠同步多個RTC時間

    ReplyDelete
    Replies
    1. 相當不錯的需求,但我不知道怎麼做。

      燒錄就是燒錄,燒錄完後才會執行你燒錄的程式,
      那麼,要由誰設定RTC時間呢?
      我不知道怎麼在燒錄時想完成你要的功能。

      Delete
    2. 您好!!請教個問題
      我想要在序列窗裡
      在每一筆資料後面加入時間
      好讓自己知道每抓一筆資料所用時間多久
      該如何讓millis()顯示出來的數字變成XX:XX:XX
      該如何實現!!
      感恩

      Delete
    3. 呼叫millis()後,把毫秒轉成分、秒、毫秒,三個整數,

      然後參考這篇 http://yehnan.blogspot.tw/2012/02/arduino-sketch.html ,
      裏頭有個函式pf,用法跟C語言的printf差不多,

      pf("%d:%d:%d\n", minute, second, millisecond);

      Delete
    4. 請問DS1307的宣告腳位是宣告在哪 如果是改DS1302有差別嗎 函式庫也需要改嗎

      Delete
    5. DS1307採用I2C傳輸,需要連接SCL與SDA這兩個腳位。

      DS1302採用 非標準的傳輸方式,但有人已經寫好程式庫了http://playground.arduino.cc/Main/DS1302RTC

      Delete
  37. 大大不好意思
    我只能轉換成秒
    分我不知道如何用
    還有就是要如何讓他到60後再從0開始
    求大大解惑,感激不盡。
    unsigned long millisecond;
    unsigned long second;
    unsigned long minute;

    void setup() {
    Serial.begin(9600); // 開啟 Serial port, 通訊速率為 9600 bps
    }
    void loop() {
    Serial.print("millisecond:");
    millisecond= millis();
    Serial.println(millisecond);

    Serial.print("second:");
    second = millis()/1000;
    Serial.println(second);

    Serial.print("minute:");
    minute=millis()>60000;
    Serial.println(minute);
    pf("%d:%d:%d\n", minute, second, millisecond);
    delay(1000); // 延遲 1 秒鐘
    }
    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);
    }

    ReplyDelete
    Replies
    1. // 分解出 分鐘、秒、毫秒
      x = millis();
      ms = x % 1000; // ms是毫秒
      s = x / 1000;
      m = s / 60; // m 是分鐘
      s = s % 60; // s 是秒

      Delete
    2. > 讓他到60後再從0開始
      誰?

      Delete
    3. 如果60秒了之後不是應該從0開始嗎
      我上面的程式碼會繼續加上去 61.62.63...

      Delete
    4. 按照我的寫法,
      只有分鐘會繼續加上去。

      Delete
    5. 大大您好,對不起小弟愚笨,只能夠在冒昧請教您!
      按照您的寫法已經能夠顯示出XX:XX:XX
      但是當秒數到達60進位之後會變成1:0:0
      按照正常來講應該顯示0:1:0
      然後如果要讓分也進位成時的話
      要在哪裡做修改呢?
      unsigned long x;
      unsigned long minute;
      unsigned long second;
      unsigned long millisecond;

      void setup() {
      Serial.begin(9600); // 開啟 Serial port, 通訊速率為 9600 bps
      }

      void loop() {
      x=millis();
      millisecond=x%1000;
      second=x/1000;
      minute=second/60;
      second=second%60;
      pf("%d:%d:%d\n", minute, second, millisecond);
      delay(1000);
      }
      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);
      }

      Delete
    6. pf("%d:%d:%d\n", minute, second, millisecond);
      你顯示的是 分鐘 秒 毫秒,
      所以秒達到60時,會變成 1:0:0,沒錯。

      要讓分也進位成時的話,
      嗯,自己寫寫看吧, 也是用 / 與 %

      Delete
  38. 葉先生您好:
    我試Run" RTC_DS1307_Timer" 後出現" error: 'tmElements_t' has not been declared

    static void read(tmElements_t &tm);..."等一串錯誤信息,我是新手,可否給我一些建議?謝謝!

    ReplyDelete