RTC DS1307からArduino(ESP32)で時刻の取得&更新をする①

2024年2月11日日曜日

Arduino ESP32 電子工作

 Cansatや人力飛行機に乗せるロガーにRTC(Real-Time-Clock)をよく使うので、秋月で売ってるRTC,DS1307の時刻取得と時刻の更新をしてみる。

RTCじゃなくてGPS時刻のほうが精度がいいと思うのだけど、電波が切れた時のバックアップとしても使えるということで。

ちなみにESP32やRP2040などは内部にRTCを持っているがそれらは精度が著しく悪い(らしい)のとバックアップ電源の用意が面倒なので取り扱わない。


使うもの

・DS1307使用リアルタイムクロック(RTC)モジュールキット

https://akizukidenshi.com/catalog/g/g115488/

RTCのほかにEEPROMもついてるらしい。EEPROM使ったことないなぁ。

・Seeed Studio XIAO ESP32C3

https://akizukidenshi.com/catalog/g/g117454/

ArduinoIDEで動かすので他のArduino(ESP32)でも大丈夫そう

ロジックレベルは3V3だけど5V電源は取り出せるのでお勧め。もちろん3V3電源もとれる。

・ジャンパーワイヤー

適当数

・ブレッドボード

適当な大きさ


プログラム

よさそうなライブラリが見つからなかったので通信部分は自分で実装してみる。
最終的なスケッチだけほしい場合は、
からダウンロードしてください。(Bloggerだとzip添付ができない様子)

・データシートを読んでみる

fig 1

身構えてたよりは簡単そう?

一番左の00hとか01hとかがデータのあるメモリの番地(=アドレス)を示しています。例えば00hには秒のデータが格納されています。hex(16進数)なのでプログラムで指定するときは0x??みたいなのになりそうです。

bit7とかbit6はデータそのものです。bit0~bit7まであるので計8bit=1byteのデータということになります。00001001とかです。

02h(0x02)にはhour(時)データが格納されています。AM/PM表記ならばbit6が1、24時間表記ならbit6が0です。


RTCは時間の設定も自分でやらねばなりませんので時間を更新するときにも前述のメモリの番地にデータを上書きしてやる必要があります。

このRTCはBCD(Binary-coded decimal)形式で値を書き込んだり読み込んだりする仕様なので、例えば秒(0x00)に37秒を書き込みたいときは0011 0111(0x37)を使用します。つまり、0x??の??が10進数に対応することになります。わかりにくい。

同じように値を呼び出すときも0x??の??を16進数としてではなく??を10進数で読む必要があります。例えば0x00の場所のデータが0x57なら57秒です。


まとめると

・DS1307の値を更新、呼び出しするときにはアドレスを指定する。

・使う値はBCD形式で、16進数の0x??の2ケタ(曜日などは1ケタ)を10進数にそのまま治せない。


・BCDが面倒だ

前述のとおりこのRTCではデータはBCD形式を用いるので0x??を生真面目に10進数に直しているとバカ時間しか得られない。
例えば・・・以下のデータが得られたとする。
アドレス:データ(2進数)
0x00:0x47(0B01000111)
0x01:0x09(0B00001001)
0x02:0x23(0B00100011)
頭の柔らかい人間たちはデータシートの情報から、今は23時9分47秒だとわかる。
しかしこれを機械が10進数に直してしまうと、
アドレス:データ(2進数)(10進数)
0x00:0x47(0B01000111)(71)
0x01:0x09(0B00001001)(9)
0x02:0x23(0B00100011)(35)
つまり今が35時9分71秒になってしまう。あらら。

ということで
BCDを10進数にする方法を考える必要がある。
例として0x00:0x47(0B01000111)を使って考えてみよう。
ここでわざわざ0B01000111を併記したのはBCDを10進数にするのに必要だからだ。ちなみに16進数は0B,8進数は0を頭につけて表現する。
(0x47)0B01000111の10の位を取り出すには右に4ビットシフトすればよい。
書き方がプログラムとはちょっと違うが考え方的には、
0B01000111 >> 4 = 0B00000100 = 4
(0x47)0B01000111の1の位を取り出すには0x0F(0B00001111)とAND計算すればよい。
ANDは計算はbitが互いに1の時に1になるので0x47で言う10の位は無視され、一の位だけ残る。
0B01000111 & 0B00001111 = 0B00000111 =7

つまり0x47を47に直すためには
(0x47 >> 4)  × 10 + (0x47 & 0x0F) = 47
これをArduinoのプログラムに直すと
---------------------------------------------------
int BCD_to_int(byte BCD) {
  // e.g. BCD =  0x89(59, B01011001)
  uint8_t ones = BCD & B1111;      // 9
  uint8_t tens = (BCD >> 4) * 10;  // 50
  uint8_t data = tens + ones;

  return data;
}
---------------------------------------------------
となる。

また、時刻更新時には10進数から16進数に変える必要がある。
例として36分を書き込みたいとする。分なのでアドレスは0x01で書き込む値は0x36である。
36分の10の位を取り出すには、
36 / 10 = 0B00000011 = 3
 いや3.6だろうと言いたいだろうがArduino言語のベースとなっているCは整数と整数の除算の答えは整数が小数点にか切り捨てで帰ってくる。もしpythonとかで書きたいなら以下のような感じになると思う。
floor(36 / 10) = 0B00000011 = 3
今度は先ほどとは逆に10の位を左に4ビットシフトする。
0B00000011 << 4 = 0B00110000 = 30
一の位を出すためには
36 % 10 = 0B00000110 = 6
これで十の位と一の位のどちらもが出たのでOR計算をする。OR計算はどちらかが1なら1で、記号は|を使う(1|1=1, 1|0=1, 0|1=1)
0B00110000 | 0B00000110 = 0B00110110 = 0x36
できた。
これをArduinoのプログラムに直すと
---------------------------------------------------
int int_to_BCD(byte data) {
  // e.g. data = 59
  uint8_t ones = data % 10;           // 9
  uint8_t tens = (data - ones) / 10;  // 5
  uint8_t BCD = (tens << 4) | ones;   // 89 = 0x59

  return BCD;
}
---------------------------------------------------
となる。

ちょっと疲れたので続きはまた今度。