|
Adding a Real-Time Clock to the Raspberry PiThis page is heavily based on Uputronics notes and Phil Randal's blog, to whom many thanks. The board is from Uputronics.
Using the Raspberry PiOSThe first step is to enable support for the I2C interface. You may have already done this for other I2C peripherals:
If you have not already added the board, do so now. We can now check that the RTC can be seen by the RPi.
The RTC (0x52) and GPS (0x42) are both visible on the I2C bus. The next step is to tell the OS that the device (rv3028) is present on the bus, and what driver to use for that device.
To check, rerun the i2cdetect command:
The "52" has been replaced with "UU". The next steps remove the "fake-hwclock" driver (used by the Raspberry PiOS to guess a starting data and time) to allow the real hwclock (RTC) to work:
|
#!/bin/bash function wait_for_EEBusy_done { busy=$((0x80)) while (( busy == 0x80 )) do status=$( i2cget -y 1 0x52 0x0E ) busy=$((status & 0x80)) done } rmmod rtc_rv3028 wait_for_EEBusy_done # disable auto refresh register=$( i2cget -y 1 0x52 0x0F ) writeback=$((register | 0x08)) i2cset -y 1 0x52 0x0F $writeback # enable BSM in level switching mode register=$( i2cget -y 1 0x52 0x37 ) writeback=$((register | 0x0C)) i2cset -y 1 0x52 0x37 $writeback # update EEPROM i2cset -y 1 0x52 0x27 0x00 i2cset -y 1 0x52 0x27 0x11 wait_for_EEBusy_done # reenable auto refresh register=$( i2cget -y 1 0x52 0x0F ) writeback=$((register & ~0x08)) i2cset -y 1 0x52 0x0F $writeback wait_for_EEBusy_done modprobe rtc_rv3028 |
Now that you have a working RTC you may want to update your NTP so that it works in the absence of the GPS. Note that the time-of-day accuracy will only be as good as that of the RTC, and of its initial setting. If possible, get a GPS/PPS setting first. The RTC super-capacitor battery will likely last a month or more.
You need to tell NTP use use the computer time as a reference-clock, even though it is setting the computer time. This allows it to provide NTP services to itself or other networked devices. This will not be UTC, of course, so please be very careful is using this mode. If other sources become available it will drop using the RTC.
Add these lines to your ntp.conf:
server 127.127.1.0 minpoll 4 maxpoll 4 fudge 127.127.1.0 time1 0.0 refid RTC
To see how the RTC behaves when not disciplined by the OS, I wrote a small program. Quick and Dirty! The program is written in Lazarus/FreePascal and gains access to the I2C port using the free TMS-LCL-HW-Pack component. Only the core I2C access component is required. In operation, the program polls the RTC "second" byte looking for a change. When the change is detected the system clock CPU temperature are read, and recorded to a file for further analysis. Note that to run this program you need to disable the OS from grabbing hold of the RTC and trying to correct it every 11 minutes. This is easily achieved by entering:
sudo rmmod rtc_rv3028
as used in Phil Randal's script above.
There's also a graphing program I wrote in Delphi 2009, a rather dated Pascal variant now! You can see that as the RTC is being operated above its turnover temperature (25 °C) it's low in frequency and hence the second transition occurs later and later in time. From the plots below you can see that the offset rate is greater during the working day - especially on Jan 30 with the sun shining in and heating the room - and lesser during the evening and night when the CPU temperature is lower. This agrees with the RV-3028 datasheet. The RPi was unboxed, and mounted vertically to try and improve the airflow across the GPS/RTC board. The average offset for the whole period was 0.138 seconds per day, that's 1.59 ppm.
Program: rtcckecker.pas |
---|
unit RTCckecker; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls, TAGraph, TMSLCLRaspiHW; type { TFormRTCchecker } TFormRTCchecker = class (TForm) ButtonClose: TButton; ButtonTest: TButton; MemoLog: TMemo; TimerSample: TTimer; procedure ButtonCloseClick (Sender: TObject); procedure ButtonTestClick (Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure FormShow (Sender: TObject); procedure TimerSampleTimer(Sender: TObject); private I2C: TTMSLCLRaspiI2C; procedure TakeSample; public end; var FormRTCchecker: TFormRTCchecker; implementation {$R *.lfm} { TFormRTCchecker } procedure TFormRTCchecker.ButtonCloseClick (Sender: TObject); begin Close; end; procedure TFormRTCchecker.ButtonTestClick (Sender: TObject); begin TakeSample; end; procedure TFormRTCchecker.FormCreate (Sender: TObject); begin I2C := TTMSLCLRaspiI2C.Create (Self); I2C.I2CPort := 1; I2C.I2CAddress := $52; I2C.Open; end; procedure TFormRTCchecker.FormDestroy (Sender: TObject); begin I2C.Close; I2C.Free; end; procedure TFormRTCchecker.FormShow (Sender: TObject); begin MemoLog.Clear; MemoLog.Lines.LoadFromFile ('rtc.log'); end; procedure TFormRTCchecker.TakeSample; var b0, b1: byte; t: TDateTime; st: string; cpu: TStringList; begin b0 := I2C.GetByteRegister (0); repeat Sleep (1); b1 := I2C.GetByteRegister (0); until b1 <> b0; t := Now; st := IntToHex (b1, 2); st := st + FormatDateTime (' yyyy-mm-dd hh:nn:ss.zzz', t); try cpu := TStringList.Create; cpu.LoadFromFile ('/sys/class/thermal/thermal_zone0/temp'); st := st + Format (' %.3f', [StrToFloat (cpu.Strings [0]) / 1000]); finally cpu.Free; end; // ShowMessage (st); MemoLog.Lines.Add (st); end; procedure TFormRTCchecker.TimerSampleTimer (Sender: TObject); begin TakeSample; MemoLog.Lines.SaveToFile ('rtc.log'); end; end. |
|