How-to: Make the Hardware Clock Real Again (RTC via I2C)
-
One of the major shortcomings of the Raspberry is IMHO the lack of a hardware realtime clock (RTC).
The recently released RV3028 RTC module [1] seems to be a decent choice for providing a RTC.
You can get it on a breakout board from the usual Pi Shops (around 15 USD) or you may ask the vendor Micro Crystal for a development board for free (sic!) and also get a few SMD spares (SMD size 3216: 3.2 x 1.5 x 0.8 mm).This mini how-to will guide you to setup either the RTC RV3028 via USB and I2C, or alternatively via I2C directly via GPIO (see last section).
This is how it looks like when using the RV3028 development board, the DigiSpark is directly attached to the Raspberry USB 2.0 port:
Prerequisites
- When using the development board (not needed when you bought a prefab breakout board from your favorite Pi shop):
- A backup battery, anything from 1.5 to 5V will do. Backup current is only ~50nA (!)
- When using I2C via USB: A DigiSpark, or clone [2] and the slightly modified I2C-Tiny-USB firmware [3] for DigiSpark.
Setup DigiSpark
-
Checkout the I2C-Tiny-USB repo. Change into your local I2C-Tiny-USB repo.
-
Build the firmware for the DigiSpark (
cd digispark && make hex
) and flash it to the device via micronucleus [4] (make flash_mn
). -
Wire between DigiSpark, backup battery and RV3028:
DigiSpark Backup Battery RV3028 P0 SDA P2 SCL 5V VDD GND VSS Plus(+) VBackup Minus(-) VSS
Setup Raspberry Pi
-
sudo apt install i2c-tools
-
Edit
/boot/config.txt
, set/enable this line:# disable: Not needed for I2C via USB, if directly connected to GPIO I2C then enable. # dtparam=i2c_arm=on # you may omit the addr= in the next line dtoverlay=i2c-rtc,rv3028,addr=0x52
-
Load kernel modules. Edit
/etc/modules
: Add modulesi2c_tiny_usb
andi2c_dev
-
Reboot the Pi, relogin after reboot.
-
Verify setup:
-
Check the i2c detection:
root@rpi4:/home/pi# dmesg | grep i2c [ 4.193939] i2c /dev entries driver [ 4.195798] usbcore: registered new interface driver i2c-tiny-usb [ 12.910983] usb 1-1.4: Product: i2c-tiny-usb [ 12.913608] i2c-tiny-usb 1-1.4:1.0: version 2.01 found at bus 001 address 009 [ 12.915216] i2c i2c-7: connected i2c-tiny-usb device
-
lsusb
should provide output similar to this:Bus 001 Device 004: ID 0403:c631 Future Technology Devices International, Ltd i2c-tiny-usb interface
-
Check if i2c is present:
sudo i2cdetect -l
. Output should be similar to this:root@retropie:/home/pi# i2cdetect -l i2c-1 i2c bcm2835 (i2c@7e804000) I2C adapter i2c-7 i2c i2c-tiny-usb at bus 001 device 004 I2C adapter
Note down the I2C ID in the second line (7 in this guide, most likely different in your setup).
-
-
Register the i2c device: Copy the following script, adjust the i2c_id you noted down before. Save it to
register-i2c-usb-systemd.sh
and let it runchmod a+x register-i2c-usb-systemd.sh && sudo ./register-i2c-usb-systemd.sh
. Reboot once more.#! /usr/bin/env bash bootscript=/usr/local/sbin/setup-tiny-usb-i2c-rtc.sh servicename=setup-tiny-usb-i2c-rtc # Important: Verify and adjust id with i2cdetect -l on your system i2c_id=7 # prepare payload for service cat > $bootscript <<EOF #! /usr/bin/env bash while [ ! -d /sys/class/i2c-adapter/i2c-$i2c_id ] do sleep 0.4 done echo rv3028 0x52 > /sys/class/i2c-adapter/i2c-$i2c_id/new_device EOF chmod +x $bootscript # prepare systemd service config cat > /etc/systemd/system/$servicename.service <<EOF [Service] ExecStart=$bootscript [Install] WantedBy=default.target EOF systemctl enable --now $servicename
-
Check if RV3028 is visible as I2C device at 0x52:
sudo i2cdetect -y 7
(again, change the I2C ID to match your setup).root@rpi4:/home/pi# i2cdetect -y 7 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- UU -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --
-
Verify RTC presence in kernel log. Note: Initial failed load via GPIO I2C (-121) can be ignored.
root@rpi4:/home/pi# dmesg | grep rv3028 [ 6.046748] rtc-rv3028: probe of 1-0052 failed with error -121 [ 13.244617] rtc-rv3028 7-0052: registered as rtc0 [ 13.244686] i2c i2c-7: new_device: Instantiated device rv3028 at 0x52
-
One-Time Changes
-
Reprogram some EEPROM values, save the following to
configure-rv3028.sh
and adjust to your needs. Let it run:chmod a+x configure-rv3028.sh && sudo ./configure-rv3028.sh
:#! /usr/bin/env bash # Page numbers refer to # "RV-3028-C7-App-Manual.pdf", rev1.4, November 2021 # https://www.microcrystal.com/fileadmin/Media/Products/RTC/App.Manual/RV-3028-C7_App-Manual.pdf # RV 3028 usually registers to this address i2c_id='0x52' function wait_for_EEBusy_done() { busy=$((0x80)) while ((busy == 0x80)); do status=$(i2cget -y 1 $i2c_id 0x0E) busy=$((status & 0x80)) done } # temp. remove kernel module rmmod rtc_rv3028 2&> /dev/null wait_for_EEBusy_done # disable auto refresh # 0Fh - Control 1, p. 23 register=$(i2cget -y 1 $i2c_id 0x0F) writeback=$((register | 0x08)) i2cset -y 1 $i2c_id 0x0F $writeback # enable BSM in level switching mode # 37h - EEPROM Backup, p. 39 register=$(i2cget -y 1 $i2c_id 0x37) writeback=$((register | 0x0C)) i2cset -y 1 $i2c_id 0x37 $writeback # update EEPROM # 27h - EE Command, p. 34 i2cset -y 1 $i2c_id 0x27 0x00 i2cset -y 1 $i2c_id 0x27 0x11 # disable CLKOE, CLKSY and set FD = 111 # 35h - EEPROM Clkout, p. 37 register=$(i2cget -y 1 $i2c_id 0x35) writeback=$((register & ~0xC0)) writeback2=$((writeback | 0x07)) i2cset -y 1 $i2c_id 0x35 $writeback2 # update EEPROM i2cset -y 1 $i2c_id 0x27 0x00 i2cset -y 1 $i2c_id 0x27 0x11 # Clear CLKF # 0Eh - Status, p. 22 register=$(i2cget -y 1 $i2c_id 0x0e) writeback=$((register & ~0x40)) i2cset -y 1 $i2c_id 0x0e $writeback # update EEPROM i2cset -y 1 $i2c_id 0x27 0x00 i2cset -y 1 $i2c_id 0x27 0x11 wait_for_EEBusy_done # reenable auto refresh register=$(i2cget -y 1 $i2c_id 0x0F) writeback=$((register & ~0x08)) i2cset -y 1 $i2c_id 0x0F $writeback wait_for_EEBusy_done modprobe rtc_rv3028
-
Set the proper time and date on your Pi. Then run
sudo hwclock -w
. -
Try to access the RTC:
sudo hwclock -r --verbose
. Should give you the current time in UTC. -
Disable fake hardware clock:
sudo apt -y remove fake-hwclock sudo systemctl disable fake-hwclock
-
Make sure real hwclock/RTC is read on bootup. Edit
/lib/udev/hwclock-set
:sudo nano /lib/udev/hwclock-set
# Disable/remove lines at the beginning of file so that they read like this: # if [ -e /run/systemd/system ] ; then # exit 0 # fi
Done: Rejoice in your success. SMI:)LE. Enjoy your favorite beverage.
Epilog: Setup Directly via GPIO Header
-
If you use a prefab RV3028 breakout board, wire SDA, SCL (same at GPIO), VDD (GPIO: 5V or 3.3V) and VSS (GPIO: GND).
-
Only load
i2c_dev
in/etc/modules
-
Do enable
dtparam=i2c_arm=on
inconfig.txt
(see above). -
i2cdetect -l
should give you one i2c adapter:i2c-1 i2c bcm2835 (i2c@7e804000) I2C adapter
-
The RTC should be registered automagically at 0x52, no need for the systemd script.
-
Apply everything from the above section One-time Changes.
References
[1] https://www.microcrystal.com/en/products/real-time-clock-rtc-modules/rv-3032-c7/
[2] https://digistump.com/wiki/digispark
[3] I2C via USB on DigiSpark: https://github.com/Gemba/I2C-Tiny-USB/tree/fb_digispark_enhancements
[4] https://github.com/micronucleus/micronucleus
[5] RV3028 programming reference: https://www.microcrystal.com/fileadmin/Media/Products/RTC/App.Manual/RV-3028-C7_App-Manual.pdf - When using the development board (not needed when you bought a prefab breakout board from your favorite Pi shop):
Contributions to the project are always appreciated, so if you would like to support us with a donation you can do so here.
Hosting provided by Mythic-Beasts. See the Hosting Information page for more information.