Leap second time-stamps for leap-seconds.list

This function generates time-stamps in the format used in leap-seconds.list (on e.g. NTP or PTP servers). The format is integer seconds since 1900-01-01T00:00+00.

import datetime
import pytz
 
 
def generate_ntp_timestamp(year,month,day,hour,minute,second):
    t = datetime.datetime(year,month,day,hour,minute,second,tzinfo=pytz.utc)
    #NTP leap-seconds list wants seconds since 1900
    epoch_start = datetime.datetime(1900,1,1,0,0,0,tzinfo=pytz.utc)
    delta = t-epoch_start
    delta_s = delta.total_seconds()
    return int(delta_s)
 
 
# test that this generates OK values.
# from existing leap-seconds.list
# 2918937600	27	# 1 Jul 1992
# 2950473600	28	# 1 Jul 1993
# 2982009600	29	# 1 Jul 1994
# 3029443200	30	# 1 Jan 1996
# 3076704000	31	# 1 Jul 1997
# 3124137600	32	# 1 Jan 1999
# 3345062400	33	# 1 Jan 2006
# 3439756800	34	# 1 Jan 2009
print "1 Jan 2009: ", generate_ntp_timestamp(2009,1,1,0,0,0)
print "1 Jan 2006: ", generate_ntp_timestamp(2006,1,1,0,0,0)
print "1 Jul 1997: ", generate_ntp_timestamp(1997,7,1,0,0,0)
#output:
#1 Jan 2009:  3439756800
#1 Jan 2006:  3345062400
#1 Jul 1997:  3076704000

PICDIV frequency divider

I put together a PICDIV frequency divider for use with a Rubidium clock.

I used an LTC6957-3 to convert the 10 MHz sine-wave from the clock to a CMOS logic signal (square wave). The LTC6957-3 has two outputs, one is routed to a BNC connector output, the other is used as the clock for a PIC12F675. The PIC runs pd09.asm which outputs a 20 us long pulse every second - i.e. it divides the 10 MHz input frequency by 1e7. The PIC is programmed through a 5-pin 100 mil ICSP header.

Here are some test-signals with a SRS PRS-10 as the source, and recorded on a Rigol scope.

The outputs behave as expected, but the 1PPS from the PIC is only 700 mVpp into 50R - a bit low. When terminated to 1 MOhm the rise-time is much worse so this is best avoided. Perhaps a buffer or level-translator would be a good addition.

Finally phase-noise measurements on the 10 MHz CMOS output, performed with a 3120A phase-noise probe.

I tried shielding the circuit with aluminium foil and powering it from a +12 VDC lead-acid battery - however the three measurement runs look roughly similar. Perhaps the LM317 regulator is not a great choice here, and both the LTC sine-to-square chip and the PIC should have more bypass caps and decoupling (inductors, ferrites?). In any case the phase-noise is 10-20x better than the measurement noise from a typical counter (SR620 or 53230A), so any issues only show up with high-end phase-noise probes.

Five colours of noise

Update: now with the colours matching in all graphs:

colorednoise

Time-series generated with colorednoise (following Kasdin&Walter), power-spectral-densities and Allan deviations computed with allantools, and compared to theoretical predictions in IEEE1139-2008.

colorednoise

The PSD lines and MDEV lines seem spot-on, but are the ADEV lines systematically a bit low?

Code here: example_noise_slopes.py

Hadamard total deviation in allantools

Following on from modified total deviation mtotdev() the Hadamard total deviation htotdev() algorithm is very similar but instead of phase data takes frequency data. Now included in allantools.

Here's a comarison against Stable32 and the NIST SP 1065 table values.

There's no bias-correction for now, and apparently it is customary not to use htotdev(m=1) at an averaging-factor of 1, but instead use ohdev(m=1). This is why the 'raw' value from allantools at tau=1 in the plot below is a factor of 2 too low.

htotdev_2016-03-27Both mtotdev() and htotdev() are very slow algorithms - help with making them faster would be appreciated!

Sub-second synchronization of cron jobs

cron_syncWe use cron-jobs to control lab instruments once a minute in the lab. I noticed that specifying 'once a minute' to cron produces a start-time for the job with quite a bit of variability. Usually the job starts at 1s past the minute, but sometimes 2s. There's also a random delay of 20 ms to 50 ms (but sometimes up to 300 ms). This probably depends a lot on the hardware. So if we assume the cron-job starts 1s past the minute the worst error I saw in the test above is 1.3 seconds.

Here's a very simple synchronization idea: first ask for a time-stamp, then wait until we are at an inter-second boundary, then continue. This makes the cron-job continue at the inter-second boundary with a maximum error of 6 ms, a fair improvement over 1.3 seconds without any sync code.

import datetime
import time
 
dt1 = datetime.datetime.now() # time-stamp when cron decides to run us
usecs_to_go = 1e6-dt1.microsecond # we want to run at an inter-second boundary
secs_target = 5 # we want to run at 5 seconds past minute
secs_to_go = secs_target - dt1.second -1 # amount of waiting to do.
time.sleep( secs_to_go+ usecs_to_go/1.0e6 )
dt2 = datetime.datetime.now() # now we are synced!?
 
fn = '/home/user/my_cron_log.txt' 
 
# write to logfile for later analysis.
with open(fn,'a+') as f:
    f.write(str(dt1) +" %02d.%06d" % (dt1.second, dt1.microsecond)  + ' run!\n')
    f.write(str(dt2) +" %02d.%06d" % (dt2.second, dt2.microsecond)  + ' sync!\n')
    f.write('\n')

The way to enable this to run from crontab is to add something like this to crontab (use crontab -e):

# m h  dom mon dow   command
  * *  *   *   *     /usr/bin/python /home/user/cron_sync.py

Where cron_sync.py is the name of the script above.

I'd be interested in hearing how this works on other hardware, or if there are some alternative solutions.