DS3231でArduinoをタイマー制御する

Arduinoでウォッチドッグタイマーの代わりにDS3231 RTCモジュールを使用してタイマー制御する。

10日間の連休は、あっという間に終わってしまった。OTTOの制作だけではなく、長らく放置していた気象データ取得も何とかしたかったのだが、時間切れとなった。ウォッチドッグタイマーの代わりとなるDS3231 RTCモジュールの使用に目途が立つまで漕ぎ着けたので、メモとして残しておく。

DS3231の時刻合わせ

DS3231は、Amazonで300円も出せば手に入る。少し細かい作業を施す必要があるので、自信のない方は幾つか買っておくのもよいだろう。

DS3231は、通常の時計同様、合わせた時刻を保つために電池を使用する。写真裏側にボタン電池CR2032が丁度入る大きさのケースが付いている。ところがこのケース、本来は充電池を入れるためのもので、電源電圧が供給されているとのことである。そこで、写真右側の赤丸で囲った部分にある表面実装抵抗を外して、電源から断線させる必要があるらしい。また、写真左側の赤丸で囲った部分にある表面実装LEDが大分明るいので、こちらも取り外してしまうか、そのすぐ下にある1 kΩの表面実装抵抗を10 kΩに付け替えるとよい。

首尾よく細工を終えたら、ケースに電池を取り付け、GND、VCC、SDA、SCLをそれぞれRaspberry Piの対応するピンと接続する。写真のRaspberry Pi 3 Model B+では、1、3、5、9番ピンがVCC、SDA、SCL、GNDに対応する。接続後、“DS1307 Tiny RTC”の記載に従い、時計合わせをする。Raspberry Piのロケール設定によって、--localtime--utcかを使い分ける。

firewheel@raspi3bp:~ $ chronyc sources
210 Number of sources = 4
MS Name/IP address Stratum Poll Reach LastRx Last sample
^* ntp-a2.nict.go.jp 1 6 177 13 +64us[ +696us] +/- 5658us
^+ ntp-a3.nict.go.jp 1 6 177 15 +459us[+1092us] +/- 5613us
^+ ntp-b3.nict.go.jp 1 6 177 14 +4012us[+4012us] +/- 8853us
^+ ntp-b2.nict.go.jp 1 6 177 13 +4096us[+4096us] +/- 8923us
firewheel@raspi3bp:~ $ lsmod | grep i2c_dev
i2c_dev 16384 0
firewheel@raspi3b:~ $ i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- 57 -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
firewheel@raspi3bp:~ $ sudo modprobe rtc-ds1307
firewheel@raspi3bp:~ $ sudo bash
root@raspi3bp:/home/firewheel# echo ds1307 0x68 > /sys/class/i2c-adapter/i2c-1/new_device
root@raspi3bp:/home/firewheel# exit
exit
firewheel@raspi3bp:~ $ sudo hwclock -D --systohc --localtime
hwclock from util-linux 2.29.2
Using the /dev interface to the clock.
Assuming hardware clock is kept in local time.
Waiting for clock tick…
/dev/rtc does not have interrupt functions. Waiting in loop for time from /dev/rtc to change
...got clock tick
Time read from Hardware Clock: 2000/01/01 00:05:41
Hw clock time : 2000/01/01 00:05:41 = 946652741 seconds since 1969
Time since last adjustment is -610392530 seconds
Calculated Hardware Clock drift is 0.000000 seconds
1557127070.500000 is close enough to 1557127070.500000 (0.000000 < 0.002000)
Set RTC to 1557127070 (1557127069 + 1; refsystime = 1557127069.000000)
Setting Hardware Clock to 16:17:50 = 1557127070 seconds since 1969
ioctl(RTC_SET_TIME) was successful.
Not adjusting drift factor because the --update-drift option was not used.
firewheel@raspi3bp:~ $ sudo timedatectl
Local time: 月 2019-05-06 16:20:10 JST
Universal time: 月 2019-05-06 07:20:10 UTC
RTC time: 月 2019-05-06 16:20:10
Time zone: Asia/Tokyo (JST, +0900)
Network time on: yes
NTP synchronized: yes
RTC in local TZ: yes
Warning: The system is configured to read the RTC time in the local time zone.
This mode can not be fully supported. It will create various problems
with time zone changes and daylight saving time adjustments. The RTC
time is never updated, it relies on external facilities to maintain it.
If at all possible, use RTC in UTC by calling
'timedatectl set-local-rtc 0'.
firewheel@raspi3bp:~ $

Arduinoのタイマー制御

DS3231には2つのタイマーがある。幸い、非常に使いやすいArduinoのライブラリー “DS3232RTC” があるので、活用させてもらう。Arduinoは、 UNOではなく、3.3 V、8 MHzのPro Miniを使用した。アナログ4番(A4)ピンをSCA、A5ピンをSCL、デジタル2番(D2)ピンをSQWと接続する。下記のスケッチはライブラリー作者のGitHubサイトに掲載されていたものを参考にした。

#include <DS3232RTC.h>
#include <avr/sleep.h>
#include <avr/power.h>

const uint8_t SQW_PIN         = 2; // INT0
const uint8_t LED_PIN         = 13;
const uint8_t MAX_ALARM_COUNT = 5; // every 5 secs

volatile boolean caughtAlarm = false;
uint8_t alarmCount = 0;

void setup(void) {
  power_all_enable();
  pinMode(LED_PIN, OUTPUT);

  RTC.alarmInterrupt(ALARM_1, false);
  RTC.alarmInterrupt(ALARM_2, false);
  RTC.squareWave(SQWAVE_NONE);
  RTC.oscStopped(true);
  pinMode(SQW_PIN, INPUT_PULLUP);
  attachInterrupt(INT0, onAlarm, FALLING);

  RTC.setAlarm(ALM1_EVERY_SECOND, 0, 0, 0, 1);
  // RTC.setAlarm(ALM2_EVERY_MINUTE, 0, 0, 0, 1);
  RTC.alarm(ALARM_1);
  RTC.alarmInterrupt(ALARM_1, true);
}
 
void loop(void) {
  if (caughtAlarm) {
    caughtAlarm = false;
    alarmCount++;

    if (alarmCount >= MAX_ALARM_COUNT) {
      alarmCount = 0;
      doWork();
    }

    RTC.alarm(ALARM_1);
  }

  enterSleep();
}
 
void onAlarm() {
  caughtAlarm = true;
}

void enterSleep(void) {
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sleep_mode();
  /* wait for alarm */
  sleep_disable();
}

void doWork(void) {
  power_all_enable();

  digitalWrite(LED_PIN, HIGH);
  delay(200);
  digitalWrite(LED_PIN, LOW);
}

このスケッチでは、毎秒にアラームによる割り込みがかかる。毎分にするには、アラーム2のALM2_EVERY_MINUTEを使用すればよい。その場合、以下のALARM_1ALARM_2に変更するのもお忘れなく。割り込みがMAX_ALARM_COUNTに達したら、スリープモードから復帰してdoWork()に制御が移り、またスリープモードに入る。単純にLチカにしてみた。

上手くいっているようである。