RaspberryPi & ESP32 [38] I2C接続のDS3231 RTCモジュール
I2C接続のRTCモジュールです。アマゾンで4つで1485円で買いました。

I2Cなので、液晶ディスプレイと同じピンから接続できます。アドレスを確認してみると、0x68でした。これが表示されないなら、I2Cが有効になっているか確認してください。
pi@w2-x4-44:~/work/PPW $ 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: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- +5V 3.3Vの両方に対応しています。
DS3231を認識させるために、/boot/config.txt に dtoverlay=i2c-rtc,ds3231 と追記してリブートします。なんか、いろいろやるのかなと思いましたが、それだけです。一度、インターネットに接続してRTCに現時刻を記録させればインターネット接続が途切れても時刻を刻みます。
5番 + ーー>Vcc
4番 D -->SDA
3番 C -->SDC
2番 NC
1番 ー -->GND
DS3231のチップにはSQW(Square Wave)がありますが、このモジュールはそれが配線されておらず、NCになっています。
Raspberry PiでI2C RTC(DS3231)を使い、NTP同期後にRTCへ書き戻す構成
■RTCをconfig.txtで有効化
/boot/config.txt を編集してRTCオーバーレイを追加する。
sudo vi /boot/config.txt
末尾に追加:
dtoverlay=i2c-rtc,ds3231
保存後再起動。
sudo reboot
■RTCが認識されているか確認
i2cdetectでデバイス確認(通常0x68)。
sudo i2cdetect -y 1
デバイスファイル確認。
ls /dev/rtc*
RTCが表示されればカーネル認識はOK。
■起動時RTCが使われるか確認
ネットワークを切断して再起動し、dateが現在時刻に近ければRTCから自動読み込みされている。
sudo hwclock -r でRTC内容も確認可能。
fake-hwclockが有効な場合はRTCより優先されるため無効化する。
sudo systemctl disable fake-hwclock
sudo apt purge fake-hwclock
■chrony同期後にRTCへ書き戻すスクリプト
#!/bin/bash
# 許容できるオフセット(秒単位)
THRESHOLD=0.05
echo "Waiting for chrony to synchronize..."
while true; do
# `chronyc tracking` の出力から `System time offset` を取得
OFFSET=$(chronyc tracking | grep "System time offset" | awk '{print $4}')
# 絶対値に変換
OFFSET_ABS=$(echo $OFFSET | awk '{ print ($1 < 0) ? -$1 : $1 }')
# 許容範囲内なら RTC に書き込む
if (( $(echo "$OFFSET_ABS < $THRESHOLD" | bc -l) )); then
echo "Chrony is synchronized. Writing to RTC..."
sudo hwclock -w
exit 0
fi
# 5秒ごとに再チェック
sleep 5
done
■root cronで実行(起動時+日次更新)
RTC書き込みはroot権限が必要なためroot crontabに登録する。
sudo crontab -e
以下を追加:
@reboot /bin/bash /home/pi/work/XING/timesync.sh >> /var/log/timesync.log 2>&1
0 17 * * * /bin/bash /home/pi/work/XING/timesync.sh >> /var/log/timesync.log 2>&1
これにより
起動直後:RTC時刻で起動
ネット同期後:RTCへ書き戻し
日次17時:RTC再補正
という安定構成になる。
■ポイント
スクリプトはpi所有のままで問題ない
root cronからbashで実行すれば実行権限や所有者変更は不要
RTCは定期的に書き戻すことで長期ズレを防げる
■まとめ
RTC追加後は「起動時はRTC」「同期後はRTCへ書き戻す」という双方向同期が重要。cronでのワンショット同期を入れておくと現場運用で安定する。
同じアマゾンで、VKLSVAN 2個 DS3231 AT24C32 RTCモジュールというのがありますが、これはSQWと32Kの出力があります。

これをI2Cチェッカにかけると、二つのアドレスが見えす。これは、EEPROMが搭載されているモジュールでそのアドレスが見えるようです。
| デバイス | チップ | I2C アドレス |
|---|---|---|
| RTC(リアルタイムクロック) | DS3231 | 0x68 |
| EEPROM(不揮発性メモリ) | AT24C32 | 0x57 |
1️⃣ DS3231(RTC:リアルタイムクロック)
- I2C アドレス:
0x68 - 現在時刻や温度データを管理する IC。
0x68は 固定アドレス で、変更不可。
2️⃣ AT24C32(EEPROM:不揮発性メモリ)
- I2C アドレス:
0x57 - 32Kbit(4KB) のメモリを搭載し、データの保存が可能。
- 例えば、RTC の設定データやユーザー独自のデータを記録できる。
- EEPROM のアドレスは
0x50~0x57の範囲で設定
ESP32でのアドレスチェックのスケッチとSQWが生きているかを調べるスケッチを上げておきます。
#include <Wire.h>
#define SDA_PIN 23 // 使用する SDA ピン(変更可能)
#define SCL_PIN 19 // 使用する SCL ピン(変更可能)
void setup() {
Serial.begin(115200);
Serial.println("\nI2C Scanner");
// I2C 通信を開始(カスタム SDA/SCL の場合は Wire.begin(SDA, SCL);)
Wire.begin(SDA_PIN, SCL_PIN);
Serial.println("Scanning for I2C devices...");
}
void loop() {
byte error, address;
int nDevices = 0;
for (address = 1; address < 127; address++) {
Wire.beginTransmission(address);
error = Wire.endTransmission();
if (error == 0) {
Serial.print("I2C device found at address 0x");
Serial.print(address, HEX); // 16進数で表示
Serial.println(" !");
nDevices++;
} else if (error == 4) {
Serial.print("Unknown error at address 0x");
Serial.println(address, HEX);
}
}
if (nDevices == 0) {
Serial.println("No I2C devices found\n");
} else {
Serial.println("Scan complete\n");
}
delay(5000); // 5秒ごとにスキャンを繰り返す
}
#include <Wire.h>
#include "RTClib.h"
#include <Wire.h>
#define DS3231_ADDRESS 0x68 // DS3231のI2Cアドレス
#define SDA_PIN 23 // I2C SDA
#define SCL_PIN 19 // I2C SCL
#define SQW_PIN 18 // SQW ピンを接続する GPIO(変更可能
volatile unsigned long lastTime = 0;
volatile unsigned long pulseInterval = 0;
// 割り込み関数:SQWの立ち下がりを検出
void IRAM_ATTR sqwInterrupt() {
unsigned long currentTime = micros();
pulseInterval = currentTime - lastTime;
lastTime = currentTime;
}
// DS3231のSQW出力を1Hzに設定
void setSQWFrequency(uint8_t mode) {
Wire.beginTransmission(DS3231_ADDRESS);
Wire.write(0x0E); // 制御レジスタのアドレス
Wire.write(mode); // 設定値
Wire.endTransmission();
}
void setup() {
Serial.begin(115200);
Wire.begin(SDA_PIN, SCL_PIN); // I2C通信開始
pinMode(SQW_PIN, INPUT_PULLUP); // SQWピンを入力モードに設定
attachInterrupt(digitalPinToInterrupt(SQW_PIN), sqwInterrupt, FALLING); // 割り込み設定
// 1Hzの出力に設定
setSQWFrequency(0x00);
}
void loop() {
if (pulseInterval > 0) {
Serial.print("Pulse Interval: ");
Serial.print(pulseInterval);
Serial.println(" us");
float frequency = 1000000.0 / pulseInterval; // 周波数 (Hz)
Serial.print("Frequency: ");
Serial.print(frequency);
Serial.println(" Hz");
pulseInterval = 0; // 測定値をリセット
}
delay(500);
}

