前言:

這篇主要是來記錄用 Arduino 跟用它的 IDE 用C寫程式,來控制 DS1307 的即時時鐘。一般來說,我喜歡用 FORTH 在 Arduino 來寫這些控制程式但是因爲 Arduino IDE 的普及,大家已經幫它開發了很多方便的函式庫囉。所以主流還是不能忽視的,所以兩邊還是得兼顧。某些狀況下,使用 Arduino IDE 確實快很多!

DS1307 已經有很多現成的函式庫,但是翻閱了它的規格書,其實它的控制算是非常簡單的,所以還是自己來寫一個吧。除了當練習外,自己的程式碼日後的任何應用也比較好維護跟修改。所以這裡也來個很簡單的紀錄。

 

I2C通訊協定:

如何用 I2C 協定來跟 Slave 溝通,這裡就不贅述囉。有興趣請看我的另外一篇 BLOG 文章

 

RTC DS1307 I2C 協定

翻開 DS1307 的規格書,來看看它的 I2C 通訊細節為何?

1. 資料從 Master 寫入 Slave

Data Write.png

翻譯一下就是

Master 先送個 Start 訊號,然後 位址訊號-寫入 來點名 Slave 存在否並要求接收資料,然後 Master 依序地送出要寫入的資料:第一個是要寫入DS1307暫存器的起始位址,第二個之後是依序要寫入暫存器的資料,最後送出 Stop 訊號結束所有 I2C 交談。

對應到的 Arduino IDE程式碼如下

// write to DS1307
void regsWrite(TimeDate *timeDatePtr) {
  Wire.beginTransmission(i2c_addr);
  Wire.write(0x0);                  // 暫存器位址:0
  Wire.write(byte2bcd(timeDatePtr->secs));
  Wire.write(byte2bcd(timeDatePtr->mins));
  Wire.write(byte2bcd(timeDatePtr->hours));
  Wire.write(byte2bcd(timeDatePtr->day));
  Wire.write(byte2bcd(timeDatePtr->date));
  Wire.write(byte2bcd(timeDatePtr->month));
  Wire.write(byte2bcd(timeDatePtr->year));
  Wire.endTransmission();
}

Arduino IDE 的 I2C Wire 函式庫,Wire.beginTransmission() 會自動幫我們把 Start 訊號跟 Slave 位址訊號-寫入來點名並要求 Slave 接收資料給做好。我們只要填入 Slave 的位址就可以囉。

接下來利用 Wire.write(),就可以逐筆傳送我們要給 Slave 的資料。

最後 Wire.endTransmission() 會自動幫我們發送結束訊號,結束整個 I2C 的對談。

 

2. Master 先指定Slave 暫存器的起始位址,然後 Slave 讀取暫存器裡的資料回傳給 Master

Write Then Read.png

翻譯一下就是

Master 先送個 Start 訊號,然後 位址訊號-寫入 來點名 Slave 存在否並要求接收資料,然後 Master 對 Slave 傳送暫存器位址。接下來 Master 傳送 Restart 訊號,要求 Slave 接受新的傳送指令, 位址訊號-讀取 來點名 Slave 並要求要求發送資料。 隨後 Master 依序地讀取從 Slave 所送出來的暫存器內部資料,之後回應 ACK,要求 Slave 繼續送下一筆資料。 最後一筆的時候, Master 回應 NACK 告訴 Slave 可以不用送了,最後送出 Stop 訊號結束所有 I2C 交談。

對應到的程式碼如下

 

// read from DS1307
void regsRead(TimeDate *timeDatePtr) {
  Wire.beginTransmission(i2c_addr);
  Wire.write(0x0);             // 暫存器0的位址
  Wire.endTransmission(false); // false = 還要繼續傳送

  Wire.requestFrom(i2c_addr, 7); // 要求 slave 傳送7 bytes資料回來
  
  while (Wire.available()< 7) {  // 等待7 bytes 完整接收完畢
  }
  
  timeDatePtr->secs = bcd2byte(Wire.read());
  timeDatePtr->mins = bcd2byte(Wire.read());
  timeDatePtr->hours = bcd2byte(Wire.read());
  timeDatePtr->day = bcd2byte(Wire.read());
  timeDatePtr->date = bcd2byte(Wire.read());
  timeDatePtr->month = bcd2byte(Wire.read());
  timeDatePtr->year = bcd2byte(Wire.read());
}

這一部分,看規格說故事。

首先Wire.beginTransmission() 會自動幫我們把 Start 訊號跟 Slave 位址訊號-寫入來點名並要求 Slave 接收資料給做好。我們只要填入 Slave 的位址就可以囉。

接下來利用 Wire.write(),送一筆暫存器位址的起始資料(這裡0x0,從0號暫存器開始)

再來 Wire.endTransmission(false),false 的參數是表示尚勿發送Stop訊號,還要繼續的意思。

Wire.requestFrom(i2c-addr, n) 這個函式會發送 Restart 的訊號,並重新點名要求 Slave 發送 n Bytes 的資料。

使用者可以透過 Wire.available() 來確認一下,目前已經接收到多少資料了。當接收到的資料數目已經抵達所預期的數目後,可以利用 Wire.read() 逐筆的將所有已經接收的資料取出來。 

 

 

RTC DS1307 暫存器及控制

根據 DS1307 這個即時時鐘的規格書,所有暫存器如下表所示

 
Registers.png

位址0: 以 BCD-8421 的格式存放秒數資訊。  BIT 7 - CH 用來控制即時時鐘是否開始運作。 1 代表停止計數,0代表開始計數,出廠預設值為1。

位址1: 以 BCD-8421 的格式存放分鐘資訊。

位址2: 以 BCD-8421 的格式存放小時資訊。BIT 6 用來選擇 12小時制還是 24小時制。當BIT 6 = 1 的時候是 12小時制,這時候 BIT 5 拿來當作 AM/PM, BIT 5 = 1 的時候是 PM。當 BIT 6 = 0 的時候是24小時制,此時 BIT5 剛好當作 BCD 的十進位數。

位址3: 以 BCD-8421 的格式存放星期資訊。1=星期一,2=星期二,... 6=星期六,7=星期日。

位址4: 以 BCD-8421 的格式存放日期資訊。

位址5: 以 BCD-8421 的格式存放月份資訊。

位址6: 以 BCD-8421 的格式存放年份資訊。因為只能有兩位數: 0 -99,所以只能從 2000 - 2099 年。

位址7: 用來控制脈波輸出及其頻率,假如你的電路需要用到的話。

所謂 BCD-8421 格式,就是「十六進制的十進制」。我們一般知道所謂十六進制就是從0數到15,就是 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F。

「十六進制的十進制」就是犧牲 A, B, C, D, E, F 把四個 bit 當做一個10進制來用。所以一個 Byte 只能顯示 00 到 99 兩位數字。這種格式有趣的地方是,你把它轉換成16進制,但實際看到的卻是人類比較熟悉的十進制數字。

這幾個暫存器,當 位址0 的BIT 7 = 1 的時候,計時器就會開始運作。暫存器裡的資料就會以即時時鐘的方式不斷變化著。使用者只要透過 I2C 詢問這些暫存器內部的內容,即可得知當前精確的日期跟時間。

 

BCD 轉 Byte 的函式

簡單的右移四位取得左邊四位高位bits後乘10 再加上透過遮罩取得右邊四位低位bits相加就是囉

byte bcd2byte (byte data) {
  return 10*(data>>4) + (data & 0x0F);
}

 

Byte 轉 BCD 的函式

除以10後左移四位形成高位bits後跟除以10的餘數合併就是囉

byte byte2bcd (byte data) {
  return (data/10)<<4 | (data%10);
}

硬體接線

我是直接購買 Arduino SD/RTC 紀錄模組,所以配線都不需要囉。直接把這個 Shield 掛上 Arduino Uno 就可以囉。模組裡面的 RTC DS1307 當然已經直接接上 Arduino Uno 的 I2C 匯流排囉。所以立即可以使用。

IMG_6285.png

日期時間設定

這裡用比較笨的方法, Setup() 函式裡加上下面這兩行來設定時間,看準時間編譯上傳就設定下去了。

設定OK後,在將這兩行拿掉,再重新編譯上傳一次,就OK囉!

  // setup RTC 1307 if needed
  
  TimeDate initTimeDate = { 21, 3, 2, 2, 0, 10, 3};
  regsWrite(&initTimeDate);

 

 

操作影片

測試了一天,還蠻準確的。有即時時鐘可以用囉,開心!

IMG_6284.png

 

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Frank Lin(@ohiyooo)分享的貼文

 

 

 

 

最後,原始程式碼列表

 

//
// DS1307 RTC
//     2021.3.1  Frank Lin
//

#include <Wire.h>

#define i2c_addr 0x68

typedef struct time_date {
  byte year;
  byte month;
  byte date;
  byte day;
  byte hours;
  byte mins;
  byte secs; 
} TimeDate;


// function phototype

void regsRead(TimeDate *) ;
void regsWrite(TimeDate *);
byte bcd2byte(byte);
byte byte2bcd(byte);

TimeDate currTimeDate;

void setup() {
  // initialize Serial, I2C
  Serial.begin(9600);
  Wire.begin();

  
  // setup RTC 1307 if needed
  
  TimeDate initTimeDate = { 21, 3, 1, 1, 20, 36, 2};
  regsWrite(&initTimeDate);
  
}

void loop() {
  regsRead(&currTimeDate);
  Serial.print(currTimeDate.year);
  Serial.print("/");
  Serial.print(currTimeDate.month);
  Serial.print("/");
  Serial.print(currTimeDate.date);
  Serial.print(" - Day:");
  Serial.print(currTimeDate.day);
  Serial.print("  ");
  Serial.print(currTimeDate.hours);
  Serial.print(":");
  Serial.print(currTimeDate.mins);
  Serial.print(":");
  Serial.println(currTimeDate.secs);
  delay(1000);
}

// read from DS1307
void regsRead(TimeDate *timeDatePtr) {
  Wire.beginTransmission(i2c_addr);
  Wire.write(0x0);             // register:0
  Wire.endTransmission(false); // false = keep going

  Wire.requestFrom(i2c_addr, 7); // request and read 7 bytes
  
  while (Wire.available()< 7) {  // wait for 7 bytes
  }
  
  timeDatePtr->secs = bcd2byte(Wire.read());
  timeDatePtr->mins = bcd2byte(Wire.read());
  timeDatePtr->hours = bcd2byte(Wire.read());
  timeDatePtr->day = bcd2byte(Wire.read());
  timeDatePtr->date = bcd2byte(Wire.read());
  timeDatePtr->month = bcd2byte(Wire.read());
  timeDatePtr->year = bcd2byte(Wire.read());
}

// write to DS1307
void regsWrite(TimeDate *timeDatePtr) {
  Wire.beginTransmission(i2c_addr);
  Wire.write(0x0);                  // register:0
  Wire.write(byte2bcd(timeDatePtr->secs));
  Wire.write(byte2bcd(timeDatePtr->mins));
  Wire.write(byte2bcd(timeDatePtr->hours));
  Wire.write(byte2bcd(timeDatePtr->day));
  Wire.write(byte2bcd(timeDatePtr->date));
  Wire.write(byte2bcd(timeDatePtr->month));
  Wire.write(byte2bcd(timeDatePtr->year));
  Wire.endTransmission();
}

byte bcd2byte (byte data) {
  return 10*(data>>4) + (data & 0x0F);
}

byte byte2bcd (byte data) {
  return (data/10)<<4 | (data%10);
}



 

arrow
arrow
    創作者介紹
    創作者 ohiyooo2 的頭像
    ohiyooo2

    早安,苦命工程師的胡言亂語

    ohiyooo2 發表在 痞客邦 留言(0) 人氣()