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 |
|
|
|
|
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 (I used again the one from Conrad electronics)
- 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:
| Name | Bits | meaning |
| flags | 8 |
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
|
| bit | 8 |
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_offset | 16 |
Count offset the last reference second has been started
|
| cur_phase | 16 |
This is the current phase between the reference second and the DCF77 signal
|
| cur_interval | 16 |
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
|
|