Raspberry PIでRTC(DS3231)を使った
Raspberry PIでなんやかんややっていると、HTTPSやWifiの証明書あり認証とかで時計の重要性に気がつくことが多い。
で、NTPで確実に同期取れるの待つsystemd用サービスとか作って対処したりしていたけど、やっぱリアルタイムクロックあったほうが良いよなーと思ったので使い方を覚えてみることにした。
使ったRTCモジュール
Amazonでも売ってたこれを使った。モジュールはDS3231。I2Cで接続するもの。
試したOS
Raspbian Buster Liteの2019-09-26バージョンでパッケージを最新まで上げたやつ。
設定方法
まずraspi-configでI2Cを有効にする。
続いて、fake-hwclockを無効化する。
$ sudo systemctl disable fake-hwclock $ sudo apt-get -y remove fake-hwclock
RTCへの時刻設定とRTCからシステムクロックへの反映については、ざっと調べた感じudevの/lib/udev/rules.d/85-hwclock.rules
が次のようになっていて、rtcデバイスを見つけたら/lib/udev/hwclock-set
を実行してくれるみたい。
# Set the System Time from the Hardware Clock and set the kernel's timezone # value to the local timezone when the kernel clock module is loaded. KERNEL=="rtc0", RUN+="/lib/udev/hwclock-set $root/$name"
ただし、/lib/udev/hwclock-set
はデフォルトだと/run/systemd/system
を見つけて処理せずに中断してしまう様子。
#!/bin/sh # Reset the System Clock to UTC if the hardware clock from which it # was copied by the kernel was in localtime. dev=$1 if [ -e /run/systemd/system ] ; then exit 0 fi if [ -e /run/udev/hwclock-set ]; then exit 0 fi if [ -f /etc/default/rcS ] ; then . /etc/default/rcS fi # These defaults are user-overridable in /etc/default/hwclock BADYEAR=no HWCLOCKACCESS=yes HWCLOCKPARS= HCTOSYS_DEVICE=rtc0 if [ -f /etc/default/hwclock ] ; then . /etc/default/hwclock fi if [ yes = "$BADYEAR" ] ; then /sbin/hwclock --rtc=$dev --systz --badyear /sbin/hwclock --rtc=$dev --hctosys --badyear else /sbin/hwclock --rtc=$dev --systz /sbin/hwclock --rtc=$dev --hctosys fi # Note 'touch' may not be available in initramfs > /run/udev/hwclock-set
なので次のように/run/systemd/system
のチェックをコメントアウトした。
#if [ -e /run/systemd/system ] ; then # exit 0 #fi
で、あとはRTCをOS起動時にI2Cで有効化するだけなのだけど、検索してみると/etc/rc.local
やsystemdなどでコマンドを実行するやり方を見かける。
例えば/etc/rc.local
で行う場合は次みたいなやつ。
#!/bin/sh -e # # rc.local # # This script is executed at the end of each multiuser runlevel. # Make sure that the script will "exit 0" on success or any other # value on error. # # In order to enable or disable this script just change the execution # bits. # # By default this script does nothing. echo ds3231 0x68 > /sys/class/i2c-adapter/i2c-1/new_device # Print the IP address _IP=$(hostname -I) || true if [ "$_IP" ]; then printf "My IP address is %s\n" "$_IP" fi exit 0
echo ds3231 0x68 > /sys/class/i2c-adapter/i2c-1/new_device
を追加して、DS3231をRTCとして認識させるわけだけど、これでやるとログが次のようになる。
Dec 7 16:48:03 raspberrypi systemd-modules-load[169]: Inserted module 'i2c_dev' Dec 7 16:48:03 raspberrypi systemd[1]: Starting Flush Journal to Persistent Storage... Dec 7 16:48:03 raspberrypi systemd[1]: Started Create Static Device Nodes in /dev. Dec 7 16:48:03 raspberrypi systemd[1]: Starting udev Kernel Device Manager... Dec 7 16:48:03 raspberrypi systemd[1]: Started Set the console keyboard layout. Dec 7 16:48:03 raspberrypi systemd[1]: Started Flush Journal to Persistent Storage. Dec 7 16:48:03 raspberrypi systemd[1]: Reached target Local File Systems (Pre). Dec 7 16:48:03 raspberrypi systemd[1]: Started udev Coldplug all Devices. ****** 長いのでちょっと省略 ****** Dec 7 16:48:11 raspberrypi systemd[1]: Starting /etc/rc.local Compatibility... Dec 7 16:48:11 raspberrypi systemd[1]: Condition check resulted in fast remote file copy program daemon being skipped. Dec 7 16:48:11 raspberrypi kernel: [ 40.700114] i2c i2c-1: new_device: Instantiated device ds3231 at 0x68 Dec 7 16:48:11 raspberrypi systemd[1]: Starting Permit User Sessions... Dec 7 16:48:11 raspberrypi kernel: [ 40.838077] rtc-ds1307 1-0068: registered as rtc0 Dec 7 16:48:11 raspberrypi systemd[1]: Starting OpenBSD Secure Shell server... Dec 7 16:48:11 raspberrypi systemd[1]: Started /etc/rc.local Compatibility. Dec 7 16:48:12 raspberrypi systemd[1]: Started Permit User Sessions. Dec 7 16:48:12 raspberrypi systemd[1]: Started Serial Getty on ttyAMA0. Dec 7 16:48:12 raspberrypi systemd[1]: Started Getty on tty1. Dec 7 16:48:12 raspberrypi systemd[1]: Reached target Login Prompts. Dec 7 22:20:06 raspberrypi systemd[1]: Starting Daily apt download activities... Dec 7 22:20:06 raspberrypi systemd[1]: Started OpenBSD Secure Shell server. Dec 7 22:20:06 raspberrypi avahi-daemon[304]: Server startup complete. Host name is raspberrypi-3.local. Local service cookie is 410738562.
OSが起動してから、ディスクに保存されていた最終日時でログが記録されていき、/etc/rc.local
が実行されてからようやく正しい日時で記録されるようになる。なので、/etc/rc.local
が動く前の日時は当てにならない。
そこで、/boot/overlays/README
に次のようにRTCの例が書いてあるので、これに従って設定する。今回はds1307をds3231に置き換えるだけで良い。
Using Overlays ============== Overlays are loaded using the "dtoverlay" config.txt setting. As an example, consider I2C Real Time Clock drivers. In the pre-DT world these would be loaded by writing a magic string comprising a device identifier and an I2C address to a special file in /sys/class/i2c-adapter, having first loaded the driver for the I2C interface and the RTC device - something like this: modprobe i2c-bcm2835 modprobe rtc-ds1307 echo ds1307 0x68 > /sys/class/i2c-adapter/i2c-1/new_device With DT enabled, this becomes a line in config.txt: dtoverlay=i2c-rtc,ds1307 This causes the file /boot/overlays/i2c-rtc.dtbo to be loaded and a "node" describing the DS1307 I2C device to be added to the Device Tree for the Pi. By default it usees address 0x68, but this can be modified with an additional DT parameter: dtoverlay=i2c-rtc,ds1307,addr=0x68 Parameters usually have default values, although certain parameters are mandatory. See the list of overlays below for a description of the parameters and their defaults.
/boot/config.txt
にdtoverlay=i2c-rtc,ds3231
を追加して保存する。
これでOSを起動すると、ログは次のようになる。OSの起動の頭からRTCが効いて正確に日時が扱えているのがわかる。
Dec 7 22:25:47 raspberrypi systemd-modules-load[157]: Inserted module 'i2c_dev' Dec 7 22:25:47 raspberrypi systemd[1]: Starting Flush Journal to Persistent Storage... Dec 7 22:25:47 raspberrypi systemd[1]: Started Apply Kernel Variables. Dec 7 22:25:47 raspberrypi systemd[1]: Started Flush Journal to Persistent Storage. Dec 7 22:25:47 raspberrypi systemd[1]: Started Create Static Device Nodes in /dev. Dec 7 22:25:47 raspberrypi systemd[1]: Starting udev Kernel Device Manager... Dec 7 22:25:47 raspberrypi systemd[1]: Started Set the console keyboard layout. Dec 7 22:25:47 raspberrypi systemd[1]: Reached target Local File Systems (Pre). Dec 7 22:25:47 raspberrypi systemd[1]: Started udev Coldplug all Devices. Dec 7 22:25:47 raspberrypi systemd[1]: Starting Helper to synchronize boot up for ifupdown... Dec 7 22:25:47 raspberrypi systemd[1]: Started Helper to synchronize boot up for ifupdown. Dec 7 22:25:47 raspberrypi systemd[1]: Started udev Kernel Device Manager. ****** あとは省略 ******
/boot/overlays/README
にはI2CのRTCについて次のように記述されている。ここに載っているモジュールであれば同様のやり方で設定できると思う。
Name: i2c-rtc Info: Adds support for a number of I2C Real Time Clock devices Load: dtoverlay=i2c-rtc,<param>=<val> Params: abx80x Select one of the ABx80x family: AB0801, AB0803, AB0804, AB0805, AB1801, AB1803, AB1804, AB1805 ds1307 Select the DS1307 device ds1339 Select the DS1339 device ds3231 Select the DS3231 device m41t62 Select the M41T62 device mcp7940x Select the MCP7940x device mcp7941x Select the MCP7941x device pcf2127 Select the PCF2127 device pcf2129 Select the PCF2129 device pcf8523 Select the PCF8523 device pcf8563 Select the PCF8563 device rv3028 Select the Micro Crystal RV3028 device addr Sets the address for the RTC. Note that the device must be configured to use the specified address. trickle-diode-type Diode type for trickle charge - "standard" or "schottky" (ABx80x only) trickle-resistor-ohms Resistor value for trickle charge (DS1339, ABx80x, RV3028) wakeup-source Specify that the RTC can be used as a wakeup source backup-switchover-mode Backup power supply switch mode. Must be 0 for off or 1 for Vdd < VBackup (RV3028 only)