How to build a DCF77 HID USB real-time clock (dcf77-hid-usb-rtc)

This page describes how you can use an Atmel ATmega32U4 (e.g. an Arduino Leonardo, Teensy or clone) and a DCF77 receiver to build a (hot-pluggable) HID USB real-time (radio) clock (I've named it dcf77-hid-usb-rtc), for usage with Linux systems.

Because it is a radio clock, it will always have the exact time, at least as long as it will receive a signal from the german DCF77 time code transmitter in Mainflingen. So it is perfectly usable as a reliable stratum 1 time source for your network (if a resolution of one second fits your needs). As this isn't a scientific paper about accurate and reliable real-time clocks, I will not go further into the details of time keeping. As a first step you might read the Wikipedia article about NTP.

It's my second project in a small series about hot-pluggable USB RTCs. I've described my first project here: How to build an USB real-time clock (usb-rtc). A third one might follow.

The overall cost for one of those thingies I'm describing here is about 25€, which isn't really cheap. But I find it a valuable thingy because the result is a hot pluggable (radio) RTC, usable by almost any device which has USB. So it's very likely you will use it for much longer than the computer you currently want to build or search it for.

To give you an impression about how it might look, here is a picture of the one I've build:

DCF77-HID-USB-RTC

Just to be clear, so that no misunderstandings may arise: I have done the things I'm describing here in my spare time, I wasn't paid by anyone to do this and I don't have any financial interests in publishing these steps. See it as a gift and don't bother me with sentences like "You have to do this and that." or "You have to help me, ...". And, please, forgive me if I won't answer to an e-mail.

Motivation

I own several ARM boards which are meant to be used with Linux but which don't have a RTC. Their developers or their managers seem to live in some timeless universe I can't reach. ;)

No, really, either the hardware developers of those devices just never really use them themselves, they like to save the last 2¢, or they got forced by their managers to save the last 2¢ they can think about. But even while we now live in times where everyone and everything seems to be connected with some clouds, I personally just find it a pain to regulary use a device which doesn't have a real-time clock and must have to depend on some network connection if it wants some useful meaning about the current time.

Examples for such devices I know about are the DockStar or GoFlex Net from Seagate, the devices from Pogoplug, the Rasberry Pi or the BeagleBone, just to name some of them (I don't own all of them and there are now almost countless other linux devices without a functional RTC, it's like a plague).

Also such a RTC is most often only needed at boot time, and might be even replaced by using NTP (if you think you are always connected), the time will likely come where Murphy will tell you that all your assumptions were wrong. And usually Murphy will tell you that at some time you most likely never expected nor wanted it.

Hardware

The hardware consists of a small board with an Atmel ATmega32U4, a DCF77 receiver and a necessary pull-up resistor. Also I'm describing here how to build it using an Arduino Leonardo (clone), you could use almost any hardware which offers USB (client) functionality and has one digital input available.

As DCF77 receiver I'm using one which is available since many years and which I've already bought about 10 years ago. I've bought it at one of the big german electronic shops called Conrad, but similiar receivers should be available elsewhere too. The one I'm using is known for years just by his part number: Conrad 641138.

To use that module, only a power source (2.5V - 15V DC), a free digital input pin, a pull-up resistor and, of course, some software is needed. If you look at the picture on top, the orange cable is VCC (3.3V in my case, but it works with 5V too), the blue cable is GND and the grey cable is the data (connected to D2 on the Arduino). For the pull-up resistor (between VCC and data) I'm using 10k Ω, but according to the datasheet of my DCF77-receiver, anything above 3.3k Ω (5k Ω for 5V) is usable. Check the datasheet for your receiver for that.

Software

The real magic of this project lies in the software. Also radio clocks and other real-time clocks are available since several decades, there never was a standard which described how to connect and use them with a computer. That meant for almost any real-time clock you needed a different driver. Fortunately (and finally) that now seems to come to an end with the standard for HID-sensors. This standard describes, besides an API for many other sensors, an API for time sensors.

The standard still isn't final (e.g. it doesn't describe how to set the time of a time sensor), but it's already perfectly usable with radio clocks, which don't need to be set. The draft is available at usb.org and is currently named HUTRR39b.

Firmware for the Atmel ATmega32U4

For the ease of use, I'm only describing the firmware build using the Arduino prototyping platform. Most of the needed software parts are already available as Arduino libraries, so the only part I needed to implement was the HID specific stuff. The source code should be self-explanatory, so I won't go further into the details. The latest source for the so called Arduino sketch is available from a git repository. To fetch it, just use the following few steps:

user@host ~ $ cd ~/sketchbook # enter the directory which holds Arduino sketches user@host sketchbook $ git clone http://ahsoftware.de/git/DCF77_HID_USB_RTC.git # clone my git repository

To compile it you will need two libraries, the Arduino Time library and the DCF77 library from Thijs Elenb. The downloadable archive for the DCF77 does contain the Time library too, so you don't need to download both. Unfortunately a patch is needed for the DCF77 library version 0.98 to be able to provide milliseconds in our HID report, but for your pleasure, I've included a patch for that in my git repository above. To download and install those libraries, just follow these steps:

user@host ~ $ cd ~/sketchbook/libraries # enter the directory which contains Arduino libraries user@host libraries $ wget https://github.com/downloads/thijse/Arduino-Libraries/DCF77.0.9.8.tar.gz # download libs user@host libraries $ tar xzf DCF77.0.9.8.tar.gz # extract libraries user@host libraries $ cd Time user@host Time $ patch -p1 < ~/sketchbook/DCF77_HID_USB_RTC/dcf77-0.9.8_patch/Time_const.patch # apply a patch user@host Time $ cd ../DCF77 user@host DCF77 $ patch -p1 < ~/sketchbook/DCF77_HID_USB_RTC/dcf77-0.9.8_patch/DCF77.patch # apply my patch user@host DCF77 $ rm ../DCF77.0.9.8.tar.gz # remove archive, not needed anymore

Finally, start the arduino software

user@host DCF77 $ arduino &

and build and upload the firmware for the DCF77-HID-USB-RTC onto your device.

Linux kernel driver (rtc-hid-sensor-time)

Fortunately I've got in touch with a very friendly reviewer and a similiar maintainer (surprise, surprise, those still do exist), and my driver ended up without much problems in the mainline linux kernel. In addition I was able to get some more patches into the mainline kernel to remove the need for specific vendor/product IDs (actually these patches were a prerequisite, I wouldn't have written rtc-hid-sensor-time without having them got into the mainline kernel), so now it's really easy to use your device or even to build a similar one using one of the battery backuped RTCs. All you need is a linux kernel >= 3.9 which includes the rtc-hid-sensor-time driver, either as a module or statically linked in.

Of course it might need some time until such a kernel is available in the distribution of your choice, but you can always build the kernel from source and you won't need additional patches to use your new radio clock with Linux (Windows currently has no drivers available for HID sensors of type time).

A small inconvenience still exists. The rtc-hid-sensor-time driver is used by (or uses) the hid-sensor-hub driver. The current logic in the kernel is such, that when a HID sensor hub device is detected, the hid-sensor-hub driver will be loaded and this driver will afterwards load and call the rtc-hid-sensor-time driver. Unfortunately the hid-sensor-hub driver currently doesn't load other hid-sensor drivers automatically, so you still will need to either link the rtc-hid-sensor-time driver statically into the kernel, or you have to manually load the module. If I will find the time, I might submit patches to fix that.
(Update 2013-08-14: Patch done, will likely be in kernel 3.12 and can be found here https://lkml.org/lkml/2013/7/9/370.)
(Update 2014-07-09: Because I refuse to fulfill silly requests from maintainers (I'm really tired of having to do so just to get my patches accepted by some arbitrary Linux kernel maintainer), some very usefull patches for the kernel will likely never end up in mainline. They can be found here https://lkml.org/lkml/2014/6/13/6.)

Using the device

Using the device is really easy. Just load the rtc-hid-sensor-time driver, attach the device, done. At first, the device will only be identified as every USB Arduino device, and dmesg will tell you a USB ACM device was attached:

user@host DCF77 $ dmesg | tail [ 195.496890] cdc_acm 1-1.2:1.0: ttyACM0: USB ACM device [ 195.497537] usbcore: registered new interface driver cdc_acm [ 195.497546] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters [ 203.215792] usb 1-1.2: USB disconnect, device number 5 [ 203.402882] usb 1-1.2: new full-speed USB device number 6 using ehci-pci [ 203.490469] usb 1-1.2: New USB device found, idVendor=2341, idProduct=8036 [ 203.490480] usb 1-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=0 [ 203.490485] usb 1-1.2: Product: Arduino Leonardo [ 203.490489] usb 1-1.2: Manufacturer: Arduino LLC [ 203.491052] cdc_acm 1-1.2:1.0: This device cannot do calls on its own. It is not a modem. [ 203.491180] cdc_acm 1-1.2:1.0: ttyACM0: USB ACM device

At power on, the device will start to sync with the time it receives "over the air". This will need at least two minutes. If you want, you can have a first look at it, using the usb serial:

user@host DCF77 $ screen /dev/ttyACM0 115200 # use ctrl-a shift-k to quit

The device will "greet" you and you will see some dots with which it indicates that it tries to sync:

Waiting for DCF77 time ... It will take at least 2 minutes until a first update can be processed. .................................................................................

After it has successfully received the time (denoted by that X) the first time, it will detach itself from USB bus and reattach afterwards, to present the host the HID-device which will be identifed as an RTC if the module rtc-hid-sensor-time was loaded previously:

user@host DCF77 $ dmesg | tail [ 203.491180] cdc_acm 1-1.2:1.0: ttyACM0: USB ACM device [ 336.140200] usb 1-1.2: USB disconnect, device number 6 [ 337.351259] usb 1-1.2: new full-speed USB device number 7 using ehci-pci [ 337.438957] usb 1-1.2: New USB device found, idVendor=2341, idProduct=8036 [ 337.438968] usb 1-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=0 [ 337.438974] usb 1-1.2: Product: Arduino Leonardo [ 337.438978] usb 1-1.2: Manufacturer: Arduino LLC [ 337.439713] cdc_acm 1-1.2:1.0: This device cannot do calls on its own. It is not a modem. [ 337.439781] cdc_acm 1-1.2:1.0: ttyACM0: USB ACM device [ 348.446519] HID-SENSOR-2000a0 HID-SENSOR-2000a0.0: rtc core: registered hid-sensor-time as rtc1

As you can see in the above example output, the device needed 336 - 203 = 133 seconds to receive and verify the first timestamp and to detach from the USB bus plus additional 10 seconds to reattach to the USB bus and to get identified as an USB HID RTC.

If you now look again at the serial you will see the actual time, prefixed with an X whenever the internal clock of the ATmega32U4 was synced with the time received over air:

user@host DCF77 $ screen /dev/ttyACM0 115200 # use ctrl-a shift-k to quit (...) 10:20:58 2013-04-14 UTC 10:20:59 2013-04-14 UTC X 10:21:00 2013-04-14 UTC 10:21:01 2013-04-14 UTC 10:21:02 2013-04-14 UTC (...) 10:21:58 2013-04-14 UTC 10:21:59 2013-04-14 UTC X 10:22:00 2013-04-14 UTC 10:22:01 2013-04-14 UTC 10:22:02 2013-04-14 UTC (...)

But, more interesting, the time is now available to the system as with every other real-time clock:

root@host DCF77 # hwclock --utc -r -f /dev/rtc1 So Apr 14 12:24:25 2013 CEST -0.129758 seconds

This means we can now have a look at how to really use it.

At first, the kernel does have two config options which should help with that on systems which don't have any other RTC:

user@host DCF77 $ zgrep HCTOSYS /proc/config.gz CONFIG_RTC_HCTOSYS=y CONFIG_RTC_HCTOSYS_DEVICE="rtc0"

Those options do instruct the kernel to fetch the time from the RTC into the system time at startup. Unfortunatelly that currently (kernel 3.9-rc7) doesn't work with rtc-hid-sensor-time:

user@host DCF77 $ dmesg | grep -i rtc [ 7.529933] drivers/rtc/hctosys.c: unable to open rtc device (rtc0) [ 16.601417] HID-SENSOR-2000a0 HID-SENSOR-2000a0.0: rtc core: registered hid-sensor-time as rtc0

As you can see, the kernel tries to read the rtc before it is registered. And that even was after a reboot and not after power up. The problem after a power up is, the clock needs about 2 minutes to synchronize and isn't visible before. So that currently isn't a solution.

At first this looks like a problem or bug in hctosys which should be fixed, but thinking a second time about it, I actually consider it a feature! Why? Because we don't want that a (malicious) hot-pluggable device can change the system time. That would be security problem.

My solution is that I have writen a patch to enable the driver rtc-hid-sensor-time to set the system time once, if the actual system time is earlier than 1970-01-02. That means a system without any other time source has a full day to boot and and load rtc-hid-sensor-time, which should be enough for even the slowest boot including almost any possible necessary fsck (if such will even be done before loading rtc-hid-sensor-time, which isn't that good). Until it ends up in the mainline kernel, you can find the necessary patch at LKML.

Using this patch dmesg looks like that after power-up:

(...) [ 7.494597] drivers/rtc/hctosys.c: unable to open rtc device (rtc0) [ 7.501385] Waiting 180sec before mounting root device... [ 147.499171] usb 1-1.3: USB disconnect, device number 4 [ 148.760545] usb 1-1.3: new full-speed USB device number 5 using orion-ehci [ 148.873539] usb 1-1.3: New USB device found, idVendor=2341, idProduct=8036 [ 148.880515] usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=0 [ 148.887911] usb 1-1.3: Product: Arduino Leonardo [ 148.892599] usb 1-1.3: Manufacturer: Arduino LLC [ 159.900457] HID-SENSOR-2000a0 HID-SENSOR-2000a0.0: rtc core: registered hid-sensor-time as rtc0 [ 159.910430] HID-SENSOR-2000a0 HID-SENSOR-2000a0.0: hctosys: setting system clock to 2013-04-19 16:43:13 UTC (1366389793) (...)

and like that after an reboot:

(...) [ 7.638970] drivers/rtc/hctosys.c: unable to open rtc device (rtc0) [ 7.645639] Waiting 180sec before mounting root device... [ 16.598759] HID-SENSOR-2000a0 HID-SENSOR-2000a0.0: rtc core: registered hid-sensor-time as rtc0 [ 16.608712] HID-SENSOR-2000a0 HID-SENSOR-2000a0.0: hctosys: setting system clock to 2013-04-19 16:45:06 UTC (1366389906) (...)

Because in both cases it still needs some time until the clock device is found and a timestamp was received, a simple solution is to use rootdelay=180 (as shown above) at the kernel command line to make sure the root FS will be mounted after the system has the correct time (be aware that nothing guarantees that 180s is enough). If you do care about boot times and 180 seconds are to much for you, you have to think about other solutions, e.g. like waiting for the rtc to appear in some startup-script.

The next obvious step is to configure and use a NTP server to provide the stratum 1 time received from the DCF77-HID-USB-RTC as a time source to your network. At first this sounds easy, but unfortunately it isn't that easy. There are some problems with using rtc-hid-sensor-time as a reference clock for ntpd and as a concequence the following passages should be considered as beta and/or TODO.

One of the problems is, that currently the time from our RTC must be polled and that the polled time is in fact the time the Atmel device manages internally by syncing it whenever it received and validated a timestamp it got over the air. As the internal clock of the Atmel will be normally synced once a minute, the difference between the internal clock and the DCF77 time should be very low. Nonetheless it would be better if the device would automatically send a report with the timestamp to the linux system, whenever it has received one. (Update 2013-04-30) I've already included that in the sketch in my git repository. In addition the HID report now does contain milliseconds too. Also rtc-hid-sensor-time (as of kernel 3.9) doesn't know something about milliseconds, it doesn't disturb the driver and sending an HID report once every minute doesn't eat much system time. So look out for further updates to the driver. ;)

The next problem would be the question what the linux system should do with the timestamp it receives by such HID reports. The simple solution might be to just set the system time to the time it received with the HID report. A better approach is to use adjtimex (or something equivalent inside the kernel). That would be easy to implement and I'm thinking about submitting a patch which adds an option to the rtc-hid-sensor-time driver which just does that.

Another solution would be to use a reference clock driver for /dev/rtc for ntpd. This way ntpd would poll our RTC and the system time would be adjusted by ntpd (in small steps). The problem here is, where to get such a driver? A quick web search revealed that someone already has implemented such a driver, but currently I don't know where it is available and (as a concequence) I don't know how it has to be configured.

So I just present a very simple "workaround" for now. Don't assume that this presents the optimal solution, please. It currently works for me, but your mileage may vary.

The first step is to setup a cron job which sets the system time once every few minutes to the time of the RTC. This bears the risk that the system time might jump (even back), but ... To accomplish that, just setup a cron job which calls

hwclock --utc -s -f /dev/rtc1

once every few minutes.

Now instruct ntpd to use the system time as a reference clock and that it is a stratum 1 clock. To do that, add the following to /etc/ntp.conf:

server 127.127.1.0 fudge 127.127.1.0 stratum 1

Done. That's all. As already said, it's not an ideal solution but, for now, it's all I can provide without further spending more of my spare time for this simple tutorial.

All trademarks are the property of their respective owners.
This page was born on 2013-04-13 and has last grown up on 2014-07-09.
(c) 2013, 2014 Alexander Holler
Impressum / imprint