ミルク色の記録

やったこと、やってみたこと

Raspberry PIでRTC(DS3231)を使った

Raspberry PIでなんやかんややっていると、HTTPSWifiの証明書あり認証とかで時計の重要性に気がつくことが多い。

で、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.txtdtoverlay=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)