Navigate
Projects
Legal
lang:en   lang:de

 
 
 
time... I like the handy DCF77 signal. In this project no clock should use it, instead the computers in my home network should be served by a precise time reference. Due to the fact most other interfaces are no longer available on modern computers, it uses the USB to forward the prepared DCF77 signal to the host.

What the goal is

 
Shown here is at top left the main DCF77 to USB converter, bottom right is the DCF77 signal receiver

Background

This project was planned as a DCF77 based clock, providing the full timebase to a connected host. I developed other DCF77 based clocks, so, I assumed that also this project would an easy one...
But to display a time for a human eye is much easier than using this time reference for a set of computers.
While I worked on the firmware to ensure the time reference is of a high quality the code increases and at the end I used 90% of the available flash space and still many features are missed. So, at the end I gave up to bring in the whole clock feature.
The firmware is now reduced to receive the DCF77 events only, and to decode the encoded bits in the DCF77 data stream. Processing the DCF77 data bits is done at host's side.
Main feature of the firmware is now to detect the DCF77 events, sorting valid from invalid events and to provide a second reference signal. Its not an easy task, at least with the available DCF77 receivers.
The picture below shows DCF77 interval measurements taken from my DCF77 receiver. The processor runs at 12,0MHz, so it should show 46875 counters per second - if the DCF77 signal would be perfect. But it isn't. Every yellow crossing shows one measure interval over a period of 3000 seconds. That's the reality the firmware must handle.
 
The red line shows the average interval the firmware calculates from this data. Its not a thin line because it displays the difference to the reference of 46875 counts. It also shows my crystal is not running at 12,0 MHz. It's running a little bit faster (results into about 46880 countes per second).
The firmware now calculates an average interval from the previous 300 DCF77 event intervals. But it only works, if it is able to sort out all invalid events. And sometime there are bursts of events from the receiver. Due to the capture feature of the processor the firmware can detect this kind of noise and prevent its usage in the average interval calculation.
After determining the perfect second interval, the second step is to adjust the phase of the internal time reference to the received DCF77 signal. Also here I'm using an average phase difference calculated over a few second intervals to adjust the phase to a fixed value. As smaller the phase difference is, the smaller the adjustment steps are. Due to this, after a while a locked state is entered, where only very small corrections are happend. In the absence of the DCF77 signal, there is no further phase correction, only the perfect second interval should avoid any drift, to keep the host in sync with the DCF77 signal.
I was not able to correct the phase to 0°. In this state the firmware wasn't longer able to distinguish which signal lags in phase. So, it never locks. Solution was to keep the phase difference at a fixed level. With this correction all events occuring in a predictable order. And as this phase is known, it can be suppressed by some simple subtractions later on.

Connection to the host

In another small DCF77 related project I used the RS232 to forward the DCF77 signal to the host. But this kind of interface become extinct, no chance to use it any longer. Nowaday the USB is the interface to use. But I didn't want to use a USB microcontroller for this simple job. I used the V-USB implementation by obdev instead. With V-USB you get a USB low speed capable controller with any simple ATmega controller by adding a few external electronic devices. My clock device must transmit only a few bytes per second. A low speed USB device is able to handle this.

Hardware

Only a low count of devices have to be soldered. Okay, okay, there are SMT devices and some people do not like them. But: DIL is out. With some calm, patience and solderwick everyone should be able to solder SMT devices. But only the CPU is critical. All other devices are huge, aren't they?
 
Top Layer, CAD Bottom Layer, CAD
CAD picture
CAD picture

Look when finished

 

Schematics

Schematics are coming in PostScript format:

Layout

Here the corresponding EAGLE layout file

Bill Of Material

These devices are required
Type Value Footprint Quantity Name(s)
CPU Atmega8 TQFP32 1 IC1
CRYSTAL 12MHz TC26 1 XT1
IC SFH610A DIL4 1 IC2
DIODE BAV99 SOT23 2 D1 D4
DIODE Z3V6 RM2.54 2 D2 D3
LED red RM2.54 1 D6
LED green RM2.54 1 D5
LED yellow RM2.54 1 D7
CAP 100n C0805 4 C1 C2 C3 C7
CAP 27p C0805 2 C4 C5
CAP 10u RM2.54 1 C6
RESISTOR 10k R0805 4 R4 R6 R7 R9
RESISTOR 2k2 R0805 1 R11
RESISTOR 360R R0805 3 R10 R5 R8
RESISTOR 5k6 R0805 1 R3
RESISTOR 68R R0805 2 R1 R2
CONNECTOR HEADER10 (2x5) 2x5x2.54 1 X3
BEAD     1 L1
USB cable Type A to plain wires   1 for example: Cutting an A to A cable
Case T1007 ("Soap")   1 Manufacturer TEKO

What else is required?

  • DCF77 receiver
  • Case for this receiver (I used the "PP 23sw" from Segor electronics)
  • Cable to connect the receiver to the decoder
Note: The DCF77 signal must be high active, e.g. the start of each second must be the rising edge. Otherwise the firmware has to be adapted.

Firmware

Anything else to say about the firmware? Not so much, but:
  • don't forget to save the CPU's flag register in your assembler interrupt routines
  • and also don't forget to avoid concurrent access to internal 16 bit registers

Both took me about 6 weeks working on the firmware. The errors are happend sporadically, so very hard to detect.

The V-USB routines are very sensitive to interrupt delays. So, I mostly avoid to do things in interrupt routines. Only the atomically reading of the reference second event is done in an interrupt routine. Its written entirely in assembler to keep it very short. This routine switches of the interrupts for 25 cycles only.
The remaining features are done in an endless loop, running a state machine. It handles various hardware flags to do its work.

While the endless loop is running, USB events may happen. But they do not disturb any clock feature, due to many time critical things are done in hardware.

Every query the host sends to the clock, it responds with up to two events: The begin of the reference second and the last received data bit from the DCF77 stream. Both events are happend at different points of time, so, the host must send at least two queries per second to not miss any of these events. If it don't do so, DCF77 bits can be missed. The clock reports such a lost with a flag bit (but only reports the last received bit).

Every query will be answered by the clock with an 8 byte report:

NameBitsmeaning
flags8 Bit 4: 1 = a new second since the last report has been started
Bit 1: 1 = at least one DCF77 bit is lost
Bit 0: 1 = DCF77 bit value is valid
bit8 last bit from the DCF77 bit stream if flags bit 0 is 1
0: 0 bit received
1: 1 bit received
8: SYNC event received
15: invalid bit received
stamp_offset16 Count offset the last reference second has been started
cur_phase16 This is the current phase between the reference second and the DCF77 signal
cur_interval16 This is the current value of timer counts for one second

Bit 4 in flags is used to let the host detect the begin of a new second even if this event can't currently handled by the clock due to preocessing the current USB query. For this case the host can detect from stamp_offset and cur_interval the begin of the new second already happened.
How to interpret the data:

  ---------------------------------------------- time flow -------------------------------------------->

            |<----------- cur_interval (equal to one second) ----------------->|
  ref ------|------------------------------------------------------------------|-------------------------
  dcf ------------------------------------|--------------------------------------------------------------
                                          |---------- cur_phase -------------->|--- stamp_offset ---->|
                                                                                                      ^
                                                  this is the point of time we got in the report _____|

ref = Events of the internal reference second
dcf = Events of the DCF77 signal

The correct time is cur_phase + stamp_offset, because the internal reference time follows the DCF signal with a cur_phase delay.

How to determine the point of time, reported via the stamp_offset value:

 USB  _____________________XXXXXXXXXXXXXXX_XX_________________________
 read _______________________XX_______________________________________
                           |<-------------->| ~1.08ms
                         ->|-|<-150us...250us
                           ->|-|<- ~150us to generate the answer

The whole transmission on the USB lines is happened in about 1ms. About 150µs...250µs after the transmission has started, the clock start to generate the answer. E.g. stamp_offset defines this point of time. The next 150µs the firmware needs to generate the whole answer. The remaining time is consumed by the transmission routines.

And at the host?

One time stamp prior the USB query, a second time stamp when the data arrives and the assumption the stamp_offset is equivalent to the middle between these time stamps results into a good time. On my system the NTPD reports a jitter of a few hundreds microseconds. For me it satisfies my requirements.

Building the firmware

To compile the firmware you will need a GNU AVR cross compiler. I used the GCC-4.3.2 in conjunction with the AVR C library 1.6.2 and the binutils 2.19. Furthermore you will need the V-USB sources, at least the 20081126 revision. The source of the firmware and the V-USB configuration file can be downloaded here and here. I'm sure you will need some adaptions at least in the Makefile, because I'm using ptxdist to build the whole project. If you like to get the whole ptxdist project, just drop me a note. For the people who do not want to build the firmware at all: Here is the binary.

Connection to the NTP daemon

The source archive of the NTPD daemon comes with many clock drivers. I was sure to find one in the list to have an example to derive my own driver from. It was nearly true. But my clock device has some special conditions. For example the clock has to be queried two times a second. But the NTPD calls the clock driver less frequent. Okay, could be handled in a thread I thought. But the POSIX NTPD implementation didn't support threads. And I didn't want to break new ground...
Solution was the shared memory clock driver. This clock driver runs as a discrete process and forwards the time via a piece of memory, shared with the NTPD. With this approach the NTPD needs no modification and my clock driver can use any POSIX feature I want (or need).

Sources and configuration

The source archive for the shared memory clock driver (clockread) can be downloaded from here. All you need to do now is to start the NTPD, but add these lines to its configuration file first:
[...]
server 127.127.28.0 mode 0 prefer
fudge 127.127.28.0 stratum 0
[...]
After that just plug in the USB clock and look if the kernel discover it. By the way: To use this USB clock there is no need for any kind of kernel driver. Everything is handled within userland with the help of libusb. When plugging in the USB clock, my kernel states:
[...]
usb 5-1: new low speed USB device using uhci_hcd and address 7
usb 5-1: configuration #1 chosen from 1 choice
hiddev96: USB HID v1.01 Device [kreuzholzen.de DCF77-Clock] on usb-0000:00:1d.3-1
To decode the DCF77 bits and events and to forward the time to the NTPD just start the program clockread now. It gives some information about its internal state via syslog. The state of the NTPD can be queried with the ntpq command if it uses the shared memory clock driver.
[me@host]~$ ntpq -p localhost
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
 LOCAL(0)        .LOCL.          10 l   57   64  377    0.000    0.000   0.004
*SHM(0)          .SHM.            0 l   31   64  377    0.000    0.486   0.287