An MSF Atomic Clock for the Raspberry Pi

The 60kHz MSF transmission is an accurate time signal controlled by an atomic clock belonging to the National Physical Laboratory. You can find their website  here.

The project is connected to GPIO 15 input pin 1 of my Raspberry Pi interface board. There is nothing special about this pin, its location is simply convenient with my layout..

The MSF clock projectPlease be aware that this project will only work in the British Isles, because the transmitter is not very powerful. The antenna should be aligned with Anthorn, which is a place more or less in the middle of Britain. Here in Northern Ireland, the long side of the antenna would have to face East.

Circuit description

The heart of the project is a small circuit board retrieved from a radio controlled alarm clock bought at a Lidl store. This was originally for a Frankfurt DSF77 receiver, but with a change of antenna and a 60kHz crystal, it proved to be the most reliable MSF receiver I've come across.

I have used a 7404 NOT gate as a buffer. This also allowed me to produce an inverted signal and to customize the pulse sent to the LED.

The oscilloscope trace

The Circuit diagram of this project is on the right. Notice the two links, which allow me to display and decode a normal or an inverted signal.

The Program

The MSF clock takes exactly one minute to transmit the date and time. The timing diagram of three seconds is shown below.

Timing diagram

Every second carries one bit. To show a 0 the carrier is turned off for 100ms and for a one it is turned off for 200ms. Second 0 of the sequence is the marker second, when the carrier is turned off for 500ms. Bits 52 to 59 can carry a secondary bit, used for BST indication and parity checking. When this bit is set, the carrier is off for 300ms - shown in red in the drawing above.. However, my program makes no use of this feature. The coding scheme is shown below.

The MSF cyle

The red numbers indicate the binary coded decimal place values of the set bits.

An explanation may be useful.

Say the date and time is  the 21st of March 2012 at 19:39 hours. This is espressed as a binary coded decimal sequence with the place values:
 80   40   20   10   8   4   2   1

Depending on the largest number to be expected the digits used range from eight for the year to (0-99) to three for the day of the week (0-6)

Year . . . . . . . . . . no 80, no 40, no 20, one 10, no 8, no 4, one 2, no 1.  10 + 2 = 12. The 2000 is assumed
Month . . . . . . . . no 10, no 8, no 4, one 2, one 1.  2+1 = 3 = March
Day . . . . . . . . . . one 20, no 10, no 8, no 4, no 2, one 1.  20+1 = 21.
Day of week . . . no 4, one 2, one 1. 2+1 = 3 = Wednesday.  Apparently the week starts with Sunday.

I leave the amazed reader to work out the hour and the minute.


Below is a link to an archive containing an application called !Atomic_Pi. I wrote this in 1999 and called it !OvalTime. I have converted it for the Raspberry Pi.

There is also BASIC program called MsfPi which is much more interesting. It shows the incoming bit graphically and in bit form, can read an inverted signal and also shows the time difference between GMT and atomic time, which is +300ms at the moment.

Link to pi_msf


A copy of the above mentioned program is below, for those of you who like a good read!


 
   10 REM MSF_atomic
   20 REM Atomic clock signal demonstration
   30 REM (c) Jochen Lueg
   40 REM http://roevalley.com
   50 REM Limavady, November 2012
   60 REM Version Pi_1.0
   70
   80 ON ERROR PRINT REPORT$;" at line ";ERL  : END
   90
  100
  110 OSCLI"RMensure GPIO 0.40 ERROR  Please install the GPIO module"
  120 PROCsetupGPIO
  130 ON ERROR PROCerror
  140 MODE 1280,720,32
  141 MOUSE OFF
  150 PROCport_setup
  160 PROCinit
  170 PROCcheck_for_inversion
  180 PRINTTAB(1,1);"Waiting for the beginning of the sequence"
  190
  200 IF Invert% = 1 THEN
  210   PROCfind_start_inverted
  220   PROCprint_info
  230   PROCread_pulse_inverted
  240 ENDIF
  250
  260 IF Invert% = 0 THEN
  270   PROCfind_start_noninverted
  280   PROCprint_info
  290   PROCread_pulse_noninverted
  300 ENDIF
  310
  320 *QUIT
  330 END
  340
  350
  360 DEFPROCsetupGPIO
  370 SYS"GPIO_EnableI2C",0
  380 SYS"GPIO_ExpAsGPIO",2
  390 SYS"GPIO_WriteMode",15,0
  400 ENDPROC
  410
  420
  430
  440 DEFPROCprint_info
  441 COLOUR 15
  450 PRINTTAB(1,3);"Incoming MSF bits"
  460 PRINTTAB(104,5)"! = S announce"
  470 PRINTTAB(104,4)"S = Summertime"
  480 PRINTTAB(1,4)"                                                                            "
  490 ENDPROC
  500
  510
  520 DEFPROCcheck_for_inversion
  530 LOCAL Low%,High%
  540 Low%=0:High%=0
  542
  550 PRINT " It will take 3 seconds to check for signal inversion"
  560 PRINT
  570 REPEAT
  580   SYS"GPIO_ReadData",15 TO Byte%
  590 UNTIL Byte%=1
  600 FOR J%=1 TO 3
  610   TIME=0
  620   REPEAT
  630     SYS"GPIO_ReadData",15 TO Byte%
  640   UNTIL Byte%=0
  650   High%+=TIME
  660   TIME=0
  670   REPEAT
  680     SYS"GPIO_ReadData",15 TO Byte%
  690   UNTIL Byte%=1
  700   Low%+=TIME
  710 NEXT
  720 IF Low%>High% Invert%=1 ELSE Invert%=0
  730 CLS
  740 IF Invert%=1 PRINT" Inverted signal"
  750 IF Invert%=0 PRINT" Non-inverted signal"
  760 ENDPROC
  770
  780
  790 DEFPROCread_pulse_noninverted
  800 PROClegend
  810 REPEAT
  820   SYS "GPIO_ReadData",15 TO Byte%
  830   IF G%=1 X%+=1:PLOT 5,X%/S%,Byte%*200 :IF X%/S%>2500 X%=0:CLG:MOVE 0,0
  840 UNTIL Byte%=0
  850 TIME=0
  860
  870 REPEAT
  880   COLOUR Col1%
  890   REPEAT
  900     SYS "GPIO_ReadData",15 TO Byte%
  910     IF G%=1 X%+=1:PLOT 5,X%/S%,Byte%*200 :IF X%/S%>2500 X%=0:CLG:MOVE 0,0
  920   UNTIL Byte%=1 AND TIME>5
  930   Low%=TIME
  940   TIME=0
  950   REPEAT
  960     SYS "GPIO_ReadData",15 TO Byte%
  970     IF G%=1 X%+=1:PLOT 5,X%/S%,Byte%*200 :IF X%/S%>2500 X%=0:CLG:MOVE 0,0
  980   UNTIL Byte%=0 AND TIME>5
  990   High%=TIME
 1000   IF High%<15 THEN
 1010     IF C%<17 DUT%+=1:IF C%<9 Sign$="+" ELSE Sign$="-"
 1020     Low%+=High%
 1030     TIME=0
 1040     REPEAT
 1050       SYS "GPIO_ReadData",15 TO Byte%
 1060       IF G%=1 X%+=1:PLOT 5,X%/S%,Byte%*200 :IF X%/S%>2500 X%=0:CLG:MOVE 0,0
 1070     UNTIL Byte%=1 AND TIME>5
 1080     Low%+=TIME
 1090     TIME=0
 1100     REPEAT
 1110       SYS "GPIO_ReadData",15 TO Byte%
 1120       IF G%=1 X%+=1:PLOT 5,X%/S%,Byte%*200 :IF X%/S%>2500 X%=0:CLG:MOVE 0,0
 1130     UNTIL Byte%=0 AND TIME >5
 1140     High%=TIME
 1150   ENDIF
 1160   TIME=0
 1170   IF Low%<16 Time%(C%)=0
 1180   IF Low%>16 Time%(C%)=1
 1190   IF Low%>26 AND Low%<35 BitB%=1 ELSE BitB%=0
 1200   IF Low%>45 C%=0 :SWAP Col1%,Col2%:DUT%=0
 1210   PROCdecode(C%)
 1220   C%+=1
 1230   PROCcheck_keyboard
 1240 UNTIL FALSE
 1250 ENDPROC
 1260
 1270
 1280 DEFPROCread_pulse_inverted
 1290 PROClegend
 1300 REPEAT
 1310   SYS "GPIO_ReadData",15 TO Byte%
 1320   IF G%=1 X%+=1:PLOT 5,X%/S%,Byte%*200 :IF X%/S%>2900 X%=0:CLG:MOVE 0,0
 1330 UNTIL Byte%=0 AND TIME>5
 1340 TIME=0
 1350 REPEAT
 1360   COLOUR Col1%
 1370   REPEAT
 1380     SYS "GPIO_ReadData",15 TO Byte%
 1390     IF G%=1 X%+=1:PLOT 5,X%/S%,Byte%*200 :IF X%/S%>2500 X%=0:CLG:MOVE 0,0
 1400   UNTIL Byte%=0 AND TIME>5
 1410   High%=TIME
 1420   TIME=0
 1430   REPEAT
 1440     SYS "GPIO_ReadData",15 TO Byte%
 1450     IF G%=1 X%+=1:PLOT 5,X%/S%,Byte%*200 :IF X%/S%>2500 X%=0:CLG:MOVE 0,0
 1460   UNTIL Byte%=1 AND TIME >5
 1470   Low%=TIME
 1480   IF Low%<15 THEN
 1490     IF C%<17 DUT%+=1:IF C%<9 Sign$="+" ELSE Sign$="-"
 1500     High%+=Low%
 1510     TIME=0
 1520     REPEAT
 1530       SYS "GPIO_ReadData",15 TO Byte%
 1540       IF G%=1 X%+=1:PLOT 5,X%/S%,Byte%*200 :IF X%/S%>2500 X%=0:CLG:MOVE 0,0
 1550     UNTIL Byte%=0 AND TIME > 5
 1560     High%+=TIME
 1570     TIME=0
 1580     REPEAT
 1590       SYS "GPIO_ReadData",15 TO Byte%
 1600       IF G%=1 X%+=1:PLOT 5,X%/S%,Byte%*200 :IF X%/S%>2500 X%=0:CLG:MOVE 0,0
 1610     UNTIL Byte%=1 AND TIME >50
 1620     Low%=TIME
 1630   ENDIF
 1640   TIME=0
 1650   IF High%<16 Time%(C%)=0
 1660   IF High%>16 Time%(C%)=1
 1670   IF High%>26 AND High%<35 BitB%=1 ELSE BitB%=0
 1680   IF High%>45 C%=0 :SWAP Col1%,Col2%:DUT%=0
 1690   PROCdecode(C%)
 1700   C%+=1
 1710   PROCcheck_keyboard
 1720 UNTIL FALSE
 1730 ENDPROC
 1740
 1750
 1760 DEFPROClegend
 1770 Col1%=63 : REM White
 1780 Col2%=60 : REM Cyan
 1781 COLOUR 15
 1790 PRINTTAB(1,TabY_exp%)"| DUT1 positive | DUT1 negative |      Year     |  Month  |    Day    | DoW |    Hour   |    Minute   |  !  Frame  S  |"
 1800 C%=1
 1810 DUT%=0
 1820 ENDPROC
 1830
 1840
 

 1850 DEFPROCcheck_keyboard
 1860 IF INKEY(-84) G%=G% EOR 1
 1870 IF INKEY(-49) S%=500:CLG:MOVE 0,0  :X%=0
 1880 IF INKEY(-50) S%=250 :CLG:MOVE 0,0 :X%=0
 1890 IF INKEY(-18) S%=164  :CLG:MOVE 0,0 :X%=0
 1900 IF INKEY(-19) S%=100  :CLG:MOVE 0,0 :X%=0
 1910 IF INKEY(-20) S%=50  :CLG:MOVE 0,0 :X%=0
 1920 IF INKEY(-53) S%=25  :CLG:MOVE 0,0 :X%=0
 1930 IF INKEY(-98) CLG:MOVE0,0:X%=0
 1940 ENDPROC
 1950
 1960
 1970 DEFPROCdecode(C%)
 1980 IF C%=25 THEN
 1990   Year%=2000+80*Time%(17)+40*Time%(18)+30*Time%(19)+10*Time%(20)+8*Time%(21)+4*Time%(22)+2*Time%(23)+Time%(24)
 2000   Decoded$=  STR$(Year%)+" "
 2010   PRINTTAB(4,TabY_decoded%)Decoded$
 2020 ENDIF
 2030 IF C%=30 THEN
 2040   Month%=10*Time%(25)+8*Time%(26)+4*Time%(27)+2*Time%(28)+Time%(29)
 2050   IF Month%>0 AND Month%<13 THEN Decoded$=Month$(Month%)+" " ELSE Decoded$="BadSignal"
 2060   PRINTTAB(9,TabY_decoded%)Decoded$
2070 ENDIF
 2080 Decoded$=""
 2090 IF C%=36 THEN
 2100   Day%=20*Time%(30)+10*Time%(31)+8*Time%(32)+4*Time%(33)+2*Time%(34)+Time%(35)
 2110   IF Day%<10 Decoded$=" "
 2120   Decoded$+=STR$(Day%)
 2130   IF Day%=1 OR Day%=21 OR Day%=31 Decoded$+="st "
 2140   IF Day%=2 OR Day%=22 Decoded$+="nd "
 2150   IF Day%=3 OR Day%=23 Decoded$+="rd "
 2160   IF Day%>3 AND Day%<21 Decoded$+="th "
 2170   IF Day%>23 AND Day%<31 Decoded$+="th "
 2180   PRINTTAB(19,TabY_decoded%)Decoded$
 2190 ENDIF
 2200
 2210 IF C%=39 THEN
 2220   DoW%=4*Time%(36)+2*Time%(37)+Time%(38)
 2230   Decoded$=Weekday$(DoW%)+" "
 2240   PRINTTAB(24,TabY_decoded%)Decoded$
 2250 ENDIF
 2260 Decode$=""
 2270 IF C%=45 THEN
 2280   Hour%=20*Time%(39)+10*Time%(40)+8*Time%(41)+4*Time%(42)+2*Time%(43)+Time%(44)
 2290   IF Hour%<10 Decoded$="0"
 2300   Decoded$+=STR$(Hour%)+":"
 2310   PRINTTAB(36,TabY_decoded%)Decoded$
 2320 ENDIF
 2330 Decoded$=""
 2340 IF C%=52 THEN
 2350   Minute%=40*Time%(45)+20*Time%(46)+10*Time%(47)+8*Time%(48)+4*Time%(49)+2*Time%(50)+Time%(51)
 2360   IF Minute%<10 Decoded$="0"
 2370   Decoded$+=STR$(Minute%)+" "
 2380   PRINTTAB(39,TabY_decoded%)Decoded$
 2390 ENDIF
 2400 Decoded$=""
 2410
 2420 IF C%<10 Decode$="0"
 2430 Decode$+=STR$(C%)
 2440 PRINTTAB(42,TabY_decoded%);Decode$;"           "
 2450
 2460 IF C%=53 PRINTTAB(C%*2,TabY_BitB%);BitB%
 2470 IF C%=58 PRINTTAB(C%*2,TabY_BitB%);BitB%
 2480 IF C%>0 PRINTTAB(C%*2,TabY_Pulse%);Time%(C%);""
 2490 IF C%=0 PRINTTAB(120,TabY_Pulse%);Time%(C%);""
 2500 IF C% = 17 PRINTTAB(4,TabY_gmt%)"GMT - UTC = ";Sign$;DUT$(DUT%)
 2510
 2520 ENDPROC
 2530
 2540
 2550 DEFPROCfind_start_inverted
 2560 LOCAL Byte%
 2570 REPEAT
 2580   SYS "GPIO_ReadData",15 TO Byte%
 2590   IF G%=1 X%+=1:PLOT 5,X%/S%,Byte%*200 :IF X%/S%>2500 X%=0:CLG:MOVE 0,0
 2600 UNTIL Byte%=1 AND TIME>4
 2610 REPEAT
 2620   TIME=0
 2630   REPEAT
 2640     SYS "GPIO_ReadData",15 TO Byte%
 2650     IF G%=1 X%+=1:PLOT 5,X%/S%,Byte%*200:IF X%/S%>2500 X%=0:CLG:MOVE 0,0
 2660   UNTIL Byte%=0 AND TIME>4
 2670   High%=TIME
 2680   TIME=0
 2690   REPEAT
 2700     SYS "GPIO_ReadData",15 TO Byte%
 2710     IF G%=1 X%+=1:PLOT 5,X%/S%,Byte%*200:IF X%/S%>2500 X%=0:CLG:MOVE 0,0
 2720   UNTIL Byte%=1 AND TIME>20
 2730   Low%=TIME
 2740   PRINTTAB(1,4)"Low:   ";Low%;"   High:   ";High%;"  Both:  ";High%+Low%;"    "
 2750   PROCcheck_keyboard
 2760 UNTIL High%>40
 2770 X%=0:CLG:MOVE 0,0
 2780 C%=1
 2790 PRINTTAB(1,1);"                                               "
 2800 ENDPROC
 2810
 2820 3600
2830 DEFPROCfind_start_noninverted
 2840 LOCAL Byte%,Off%
 2850 REPEAT
 2860   SYS "GPIO_ReadData",15 TO Byte%
 2870   IF G%=1 X%+=1:PLOT 5,X%/S%,Byte%*200 :IF X%/S%>2500 X%=0:CLG:MOVE 0,0
 2880 UNTIL Byte%=0 AND TIME>4
 2890
 2900 REPEAT
 2910   TIME=0
 2920   REPEAT
 2930     SYS"GPIO_ReadData",15 TO Byte%
 2940     IF G%=1 X%+=1:PLOT 5,X%/S%,Byte%*200 :IF X%/S%>2500 X%=0:CLG:MOVE 0,0
 2950   UNTIL Byte%=1 AND TIME>4
 2960   Low%=TIME
 2970   Low$=STR$(Low%):IF Low%<10 Low$=" "+Low$
 2980   TIME=0
 2990   REPEAT
 3000     SYS "GPIO_ReadData",15 TO Byte%
 3010     IF G%=1 X%+=1:PLOT 5,X%/S%,Byte%*200 :IF X%/S%>2500 X%=0:CLG:MOVE 0,0
 3020   UNTIL Byte%=0  AND TIME >20
 3030   High%=TIME
 3040   PRINTTAB(1,4)"Low:   ";Low$;"   High:   ";High%;"  Both:  ";High%+Low%;"    "
 3050   PROCcheck_keyboard
 3060 UNTIL Low%>40
 3070 REMX%=0:CLG:MOVE 0,0
 3080 C%=1
 3090 PRINTTAB(1,1);"                                               "
 3100 ENDPROC
 3110
 3120
 3130 DEFPROCport_setup
 3140 VDU 28,3,45,157,20
 3150 COLOUR 132
 3160 CLS
 3170 VDU 24,40;240;2520;590;
 3190 GCOL 129
 3200 CLG
 3210 REM Font change
 3220 ORIGIN 40,250
 3230 VDU5
 3240 MOVE 0,330
 3250 PRINT" To stop or start plotting press 'G' for possibly up to a second"
 3260 PRINT" Press 1 to 6 to change time-base"
 3270 GCOL 15
 3280 MOVE 0,0
 3290 X%=0
 3300 OFF
 3310 VDU4
 3311 MOUSE OFF
 3320 ENDPROC
 3330
 3340
 3350 DEFPROCinit
 3360 G%=1
 3370 S%=164
 3380 Decoded$=""
 3390 TabY_exp%=8
 3400 TabY_decoded%=17
 3410 TabX_decoded%=5
 3420 TabX_seconds%=42
 3430 TabY_BitB%=6
 3440 TabY_Pulse%=7
 3450 TabY_gmt%=11
 3460 DIM Time%(100)
 3470 DIM Weekday$(6):Weekday$()="   Sunday","   Monday","  Tuesday","Wednesday"," Thursday","   Friday"," Saturday"
 3480 DIM Month$(12)
 3490 Month$()="","  January"," February","   March ","   April ","    May  ","   June  ","   July  ","  August ","September","  October"," November"," December"
 3500 DIM DUT$(9):DUT$()="0ms","100ms","200ms","300ms","400ms","500ms","600ms","700ms","800ms"
 3510 ENDPROC
 3520
 3530
 3540 DEFPROCerror
 3550 CLS
 3560 PRINT REPORT$;" at line ";ERL
 3570 END
 3580 ENDPROC
 3590
 


Return to interfacing index

Back to the start
Tudor with sign