External Links

Creative Science Centre

 

DCF

The following is a description of experimenting with DCF. This is a time signal originating from Germany but covers most of Europe. It is a very simple signalling system but you would not think so looking at the data that is available. The signal is transmitted at 77.5KHz which is on the old long wave band. There is also a UK transmitter using 60KHz but as far as I know there is no ready made receiver made for this.

As an aside there are lots of ways of obtaining an accurate time standard, internet via GSM, DPS of course gives accurate time and it should also be possible to use the data that is transmitted with radio now.

To start with I used the BN641138 DCF receiver from Conrad. I don’t think that this is available from anywhere else.

The coding is very simple; one bit is transmitted every second, a 0 is 100ms long and a 1 is 200ms long

At the start of the minute a pulse is missed and so the 1000mS above will be nearer 2000mS.

As a simple scheme for testing we wait while the signal is high, then when it goes low we start a timer. When the signal goes high again at T: if it is > 100mS then it is a 1 else it is a 0. We don’t do anything then for another 600 or so mS. To detect the start of a minute, whilst waiting for the signal to go low at A, it it takes linger then a second that that is the start.

The main problem with this scheme is that it relies on a good signal, in practice (looking at the signal with a scope), there are lots of spurious transitions. I was able to get a good signal in the north of the UK but only by careful positioning of the receiver.

(** Program will work but needs updating to reflect the new functions available in rookie **)

http://www.byvac.com/mBlib/flb/Projects/dcf/dcf.bas

// dcf time code
// 100 ms is 0 200ms is 1

#option only on   // only load functions that don't already exists

// include all of the registers although only some are needed
#include "http://byvac.com/mBlib/flb/Library/PIC32MX3_Family/BRegisters/MX3_reg_timer.bas"
// interrupts hold the flag addresses
#include "http://byvac.com/mBlib/flb/Library/PIC32MX3_Family/BRegisters/MX3_reg_interrupt.bas"

// MX3
constant TRISD            0xBF8860C0
constant TRISDCLR         0xBF8860C4
constant TRISDSET         0xBF8860C8
constant TRISDINV         0xBF8860CC
constant PORTD            0xBF8860D0

constant TIMERON 1 << 15 // on bit

// *****************************************************************************
// clocked with PCLK that is running at 40MHz, prescale 256 = 156.25kHz (6.4uS)
// setting PR2 1/6.4uS = 156250 should see the counter reset every second so
// using 1562500 is 10 seconds.
// *****************************************************************************
function dcf_init()
	// timer_on | prescale_256 | 32_bit_on | Internal_clock
	@T2CON = 0x8078
	// port rd1 as i/p
	@TRISDSET = 2
endf

// just to look at timer values
function xa()
	@PR2 = 0xffffffff
	dcf_init()
	while comkey?(2) = 0
		print "\n",@TMR2
		while @TMR2 < 1562500; wend
		@TMR2=0
	wend
endf

dim tcs(61)
function xc()
dim j=0, limit = 50000, bit
dim started = 0
	dcf_init()
	while comkey?(2) = 0
		@TMR2 = 0
		while @TMR2 < 100000; wend
		while (@PORTD & 2) = 2; wend
		if @TMR2 > 400000 then
			if started = 1 then
				print "Finished"
				return
			endif
			print "\nSTART",
			j = 0
			started = started + 1
		endif
	print "\n",@TMR2,
		@TMR2 = 0
		while (@PORTD & 2) = 0; wend
		print  j, @TMR2
		if @TMR2 > limit
			print format$(" %d",1)
			bit = 1
		else
			print format$(" %d",0)
			bit = 0
		endif
		tcs(j) = bit
		j = j + 1
		if j > 60; j=0; endif
	wend
endf

function dcf_calc(parityField,start,end)
dim j, value=0, parity = 0, count = 1
	for j = start to end
		value = value + tcs(j) * count
		parity = parity + tcs(j)
		if count = 8 then
			count = 10
		else
			count = count + count
		endif
	next
	if parityField <> 0 then
		parity = parity + tcs(parityField)
		if (parity & 1) <> 0 then
			print "\nminutes parity error"
		endif
	endif
	return value
endf

function xd()
dim h, m
dim dom=0, dow=0,month=0,year=0
	m = dcf_calc(28,21,27)
	h = dcf_calc(35,29,34)
	dom = dcf_calc(0,36,41)
	dow = dcf_calc(0,42,44)
	month = dcf_calc(0,45,49)
	year = dcf_calc(58,50,57)
	print "\n",h,m
	print "\n",dom,month,year,dow
endf

function aa(start,end)
dim j
	for j = start to end
		print "\n",j,tcs(j)
	next
endf

The code is very simple and only serves to test the theory and to see if the signal is suitable for reception. To run it make sure that the port you are using is the same, or change it in dcf_init() then type xc(). The count from TRM2 is shown so that comparisons can be made. If it goes perfectly then the global array tcs() will be filled with the data, each index representing a bit value this can be decoded with xd().

Further

A more advanced scheme would be this one. An interrupt on the falling edge of the signal starts timers T4 and T2/3. Any timers can be used but the T2/T3 combination needs to be able to count for more than 1 second without overflowing. when the signal goes high again, T4 can be examined. If it is much less than 100mS or much greater than 200mS then the bit can be rejected, otherwise a 0 or 1 can be stored to the array. The interrupt needs to be switched off now for at least 600mS to avoid any spurious signals, T2/3 can be used to switch the interrupt back on say at 800mS. If when the next interrupt arrives T2/T3 is greater then 1200mS then this is the start of a new minute, or more accurately the next falling edge of the signal is second zero of the new minute.

Contributions

There are two contributions from Ross, both don’t use interrupts. The first is straightforward and simple so that it is easy to see what’s going on:

http://www.byvac.com/mBlib/flb/Projects/dcf/MSF_MX1.bas

// MSF Decoder
// for MX1 and standard 20 x 4 LCD by RSH
//
// MSF source is inverted buffered output from Symtrik module (on eBay);
// module output is inverted compared to NPL data sheet on MSF format.
// Decoder assumes input in NPL-specified format - see TAF001v06 July 2007.
//
// Idea is to find the minute start, then ignore the next 16 seconds
// as these are DUT data and do not concern us. The time code, (which
// is BCD), starts at second #17, uses bit "A", has bit "B" always 1,
// and is 35 seconds long.
// Bit "A" lasts 100 ms, starting 100 ms after start of second marker,
// so input is sampled 150 ms after input goes low: low is a "1".

#option only on   // only load functions that don't already exist
// include all of the registers although only some are needed
#include "E:/BV500/LibraryFiles/PIC32MX1_Family/BRegisters/MX1_reg_timer.bas"

// Assumes LCD_1_setup (LCD drive routines) is in Flash memory

// B registers for MX1:
constant ANSELB     0xBF886100
constant TRISB      0xBF886110
constant TRIBCLR    0xBF886114
constant TRISBSET   0xBF886118
constant TRISBINV   0xBF88611C
constant PORTB      0xBF886120
constant TIMERON 1 << 15 // on bit
// *****************************************************************************
// clocked with PCLK that is running at 40MHz, prescale 256 = 156.25kHz (6.4uS)
// setting PR2 1/6.4uS = 156250 should see the counter reset every second so
// using 1562500 is 10 seconds.  (So 400 ms = 62500; 150 ms = 23438)
// *****************************************************************************

function msf_init()
    // timer_on | prescale_256 | 32_bit_on | Internal_clock
    @T2CON = 0x8078
    // port RB1 as i/p
    poke(ANSELB+CLR, 1 << 1) // set for input
	  @TRISBSET = 2
endf

function wait_LO()   //  returns when input goes low (ie 1)
  while (@PORTB & 2) = 2; wend
endf

function wait_HI()   // returns when input goes high (ie 0)
  while (@PORTB & 2)= 0; wend
endf

dim status$[20]

function show_status()
  lcd_xy(0,3)
  lcd_puts("                    ")
  lcd_xy(0,3)
  lcd_puts(status$)
endf

function find_start()   // finds the minute start and waits 16 seconds
   msf_init()
   status$="waiting minute start"
   show_status()
   while comkey?(2) = 0  // make sure there is an exit!
      wait_LO()
      @TMR2 = 0
      wait_HI()
      if @TMR2 > 65000   // 400 ms is 62500, minute start goes low for 500 ms
        status$="waiting 16s for code"
        show_status()
        @TMR2 = 0
        while @TMR2 < 2460000; wend  // wait 16 seconds
        status$="getting time code"
        show_status()
        break
      endif
    wend
 endf

dim treg(36) // this array holds the bit values

function get_time()   // reads the 35 time bits
  dim pointer
  find_start()
  for pointer=0 to 34
    wait_LO()
    @TMR2=0
    while @TMR2<23500; wend   //wait 150 ms
    if (@PORTB & 2) =  2
      treg(pointer) = 0
    else
      treg(pointer) = 1
    endif
    wait_HI()
  next
endf

 dim y, mo, mth$, dom, dow, dow$, h, m,

 function calc_time()    // also calculates month name and day of week
    y = 80*treg(0)+ 40*treg(1)+ 20*treg(2)+ 10*treg(3)+ 8*treg(4)+ 4*treg(5)+ 2*treg(6)+ treg(7)
   mo = 10*treg(8) + 8*treg(9)+ 4*treg(10)+ 2*treg(11)+ treg(12)
  dom = 20*treg(13)+ 10*treg(14)+ 8*treg(15)+ 4*treg(16)+2*treg(17)+ treg(18)
  dow = 4*treg(19)+ 2*treg(20)+ treg(21)
    h = 20*treg(22) + 10*treg(23)+ 8*treg(24)+ 4*treg(25)+ 2*treg(26)+ treg(27)
    m = 40*treg(28)+ 20*treg(29)+ 10*treg(30)+ 8*treg(31)+ 4*treg(32)+ 2*treg(33)+ treg(34)
  select(mo)
    case(1)
    mth$="January"
    break
    case(2)
    mth$="February"
    break
    case(3)
    mth$="March"
    break
    case(4)
    mth$="April"
    break
    case(5)
    mth$="May"
    break
    case(6)
    mth$="June"
    break
    case(7)
    mth$="July"
    break
    case(8)
    mth$="August"
    break
    case(9)
    mth$="September"
    break
    case(10)
    mth$="October"
    break
    case(11)
    mth$="November"
    break
    case(12)
    mth$="December
  endselect
  select(dow)
    case(0)
    dow$="Sunday"
    break
    case(1)
    dow$="Monday"
    break
    case(2)
    dow$="Tuesday"
    break
    case(3)
    dow$="Wednesday"
    break
    case(4)
    dow$="Thursday"
    break
    case(5)
    dow$="Friday"
    break
    case(6)
    dow$="Saturday"
  endselect
 endf

 function lcd_title()
   lcd_xy(4,0)
   lcd_puts("Time by MSF")
  endf

  function write_time()
    dim date$[20], time$[20]
    status$="                    "
    show_status()    // clear the status
    date$= " "+int2str(dom)+" "+mth$+" 20"+y
    time$=dow$+"   "+int2str(h)+":"+m
    lcd_xy(0,1)
    lcd_puts("                    ")
    lcd_xy(2,1)
    lcd_puts(date$)
    lcd_xy(0,2)
    lcd_puts("                    ")
    lcd_xy(2,2)
    lcd_puts(time$)
  endf

 function msf()   // This is the main loop
    lcd_init()
    lcd_nocurs()
    lcd_title()
  while comkey?(2) = 0  // make sure there is an exit!
    find_start()
    get_time()
    calc_time()
    write_time()
    wait(8000) // allow time to pass
   wend
 endf  

The second is more complete. It now counts seconds (almost correctly) and has parity checking, plus adding leading zeros where necessary. The main new feature is that it also deals with the problem of all such decoders, namely that by the time the information is received, the minute to which it applies is almost over. The solution is to increment the minute count, dealing with carries, and present this immediately at the start of the next minute. The same problem occurs with a DCF decoder, of course – no amount of error checking will fix bad data.

http://www.byvac.com/mBlib/flb/Projects/dcf/MSF_MX1_3.bas

// MSF Decoder for MX1 ByPic controller and standard 20 x 4 LCD display by RSH
// ByPic, libraries and LCD drive code by Jim Spence
// V.3   17 December 2012

// V.1 cleans up code, adds parity checking and a partial seconds count.
// V.2 improves documentation, increments the time immediately after the start of the minute.
// V.3 extends the seconds count to the first 16 seconds, eliminates the display of garbage
// during the first minute, and fixes display errors after midnight.

// MSF source is the output from Symtrik module (eBay supplier),
// buffered and inverted, as module output is inverted compared to NPL data sheet on MSF format.
// Decoder assumes input in NPL-specified format - see TAF001v06 July 2007.

// Idea is to find the minute start, then ignore the next 16 seconds
// as these are DUT data and do not concern simple time displays.
// The minute start is marked by a 500 ms low period.
// The time code (which is BCD) starts at second #17, uses bit "A",
// always has bit "B" set to 1, and is 35 seconds long.
// Seconds are not transmitted; these are counted incrementally.
// A major disadvantage with these systems is that it takes nearly a minute to
// receive the information for that minute, thus the display is only correct
// for a few seconds (this is really bad if the parity bits are also used).
// This code gets round the problem by taking the data and waiting for the next
// minute start, then incrementing the minute (and dealing with any carries)
// then displaying the time immediately, with a running count for the seconds.
//
// Bit "A" lasts 100 ms, starting 100 ms after start of second marker, so for
// time code the level is sampled 150 ms after input goes low: low denotes a "1".
//
// The decoder is totally dependent on a good signal. Daytime signal is quite
// variable, so a parity check is a good idea - however, parity is also very
// erratic when the signal is poor! It is amazing how often the data are garbage,
// yet the parity check is OK, and further error checking is impractical.
// The parity bits are 54B to 57B, so this gets tricky - mainly keeping
// track of where they are, and sampling after 250 ms. Parity bits
// combined with the data provide an odd number of bits set to 1.

#option only on   // only load functions that don't already exist
// include all of the registers although only some are needed
#include "E:/BV500/LibraryFiles/PIC32MX1_Family/BRegisters/MX1_reg_timer.bas"

// Assumes LCD_1_setup (LCD drive routines) is in Flash memory

// B registers for MX1:
constant ANSELB     0xBF886100
constant TRISB      0xBF886110
constant TRIBCLR    0xBF886114
constant TRISBSET   0xBF886118
constant TRISBINV   0xBF88611C
constant PORTB      0xBF886120
constant TIMERON 1 << 15 // on bit
// *****************************************************************************
// clocked with PCLK that is running at 40MHz, prescale 256 = 156.25kHz (6.4uS)
// setting PR2 1/6.4uS = 156250 should see the counter reset every second so
// using 1562500 is 10 seconds.  (So 400 ms = 62500; 150 ms = 23438; 250 ms = 39052)
// *****************************************************************************

dim y, mo, mth$, dom, dow, dow$, h, mi, ey, em, ed, et
dim status$[20] , inh
dim treg(45)                  // this array holds the time and parity bits

function msf_init()
    // timer_on | prescale_256 | 32_bit_on | Internal_clock
    @T2CON = 0x8078
    // port RB1 as i/p
    poke(ANSELB+CLR, 1 << 1) // set for input
	  @TRISBSET = 2
endf

function wait_LO()   //  returns when input goes low (ie 1)
  while (@PORTB & 2) = 2; wend
endf

function wait_HI()   // returns when input goes high (ie 0)
  while (@PORTB & 2)= 0; wend
endf

function show_status()  // puts status on bottom line
  lcd_xy(0,3)
  lcd_puts("                    ")
  lcd_xy(0,3)
  lcd_puts(status$)
endf

function get_time()   // reads the 35 time bits and the 4 parity bits
  dim pointer, sec=17
  for pointer=0 to 40
    wait_LO()
    @TMR2=0
    lcd_xy(15,2)             // display (fake) seconds count
    lcd_puts(":"+ int2str(sec))
    sec=sec + 1
    if pointer < 35       // time code bits "A"
      while @TMR2<23500; wend   //wait 150 ms
      if (@PORTB & 2) =  2
        treg(pointer) = 0
      else
        treg(pointer) = 1
      endif
    else                  // parity code bits "B"
      while @TMR2<39060; wend   //wait 250 ms
        if (@PORTB & 2) =  2
          treg(pointer) = 0
        else
          treg(pointer) = 1
        endif
    endif
    wait_HI()
  next
endf

 function calc_time()    // also calculates month name and day of week
 dim j,py=0,pm=0,pd=0,pt=0
    y = 80*treg(0)+ 40*treg(1)+ 20*treg(2)+ 10*treg(3)+ 8*treg(4)+ 4*treg(5)+ 2*treg(6)+ treg(7)
   mo = 10*treg(8) + 8*treg(9)+ 4*treg(10)+ 2*treg(11)+ treg(12)
  dom = 20*treg(13)+ 10*treg(14)+ 8*treg(15)+ 4*treg(16)+2*treg(17)+ treg(18)
  dow = 4*treg(19)+ 2*treg(20)+ treg(21)
    h = 20*treg(22) + 10*treg(23)+ 8*treg(24)+ 4*treg(25)+ 2*treg(26)+ treg(27)
   mi = 40*treg(28)+ 20*treg(29)+ 10*treg(30)+ 8*treg(31)+ 4*treg(32)+ 2*treg(33)+ treg(34)
  select(mo)
    case(1);  mth$="January"
    case(2);  mth$="February"
    case(3);  mth$="March"
    case(4);  mth$="April"
    case(5);  mth$="May"
    case(6);  mth$="June"
    case(7);  mth$="July"
    case(8);  mth$="August"
    case(9);  mth$="September"
    case(10); mth$="October"
    case(11); mth$="November"
    case(12); mth$="December"
  endselect
  select(dow)
    case(0); dow$="Sunday"
    case(1); dow$="Monday"
    case(2); dow$="Tuesday"
    case(3); dow$="Wednesday"
    case(4); dow$="Thursday"
    case(5); dow$="Friday"
    case(6); dow$="Saturday"
  endselect
  for j=0 to 7    // year parity check
    py=py+treg(j)
  next
  ey = ((treg(37) + py) & 1)
  for j=8 to 18   // month parity check
    pm=pm+treg(j)
  next
  em = ((treg(38) + pm) & 1)
  for j=19 to 21    // dow parity check
    pd=pd+treg(j)
  next
  ed = ((treg(39) + pd) & 1)
  for j=22 to 35    //time parity check
    pt=pt+treg(j)
  next
  et = ((treg(40) + pt) & 1)
endf

 function lcd_title()
   lcd_xy(2,0)
   lcd_puts("Time by MSF     v3")
  endf

  function write_time()
    dim date$[20], time$[20], h$, mi$
    date$= int2str(dom)+" "+mth$+" 20"+int2str(y)
    if (ey + em) <> 2
      date$=date$+" ?"   // show parity error as a query
    endif
    if mi > 9       // fix leading zero for minutes
      mi$ = int2str(mi)
    else
      mi$ = "0" + int2str(mi)
    endif
    if h > 9
      h$ = int2str(h)
    else
      h$ = "0" + int2str(h)
    endif
      time$ = h$ + ":" + mi$
    if (ed + et) <> 2
      time$=time$+"   ?"   // show parity error as a query
    endif
    lcd_xy(0,1)
    lcd_puts("                    ") // clear the line
    lcd_xy(1,1)
    lcd_puts(date$)
    lcd_xy(0,2)
    lcd_puts("                    ") // clear the line
    lcd_xy(0,2)
    lcd_puts(dow$)
    lcd_xy(10,2)
    lcd_puts(time$)
  endf

 function time_increment()   // increments minutes immediately
  mi = mi + 1
  if mi = 60   // fix minute overflow
    mi = 0
    h = h+1
    if h = 24  //fix hour overflow
      h = 0    // leave day of week to change 1 minute later!
    endif
  endif
  write_time()
 endf

function find_start()   // finds the minute start and waits 16 seconds
   dim k, v$
   msf_init()
   status$="waiting minute start"
   show_status()
   while comkey?(2) = 0  // make sure there is an exit!
      wait_LO()
      @TMR2 = 0
      wait_HI()
      if @TMR2 > 65000   // 400 ms is 62500, minute start goes low for 500 ms
        if inh = 1
          time_increment() // increment the time and display it
        endif
        status$="waiting 16s for code"
        show_status()
          k = 0            // wait 16 seconds whilst displaying seconds count
          while k < 16
            wait_LO()
            @TMR2 = 0
            while @TMR2 < 56000; wend   wait about 350 ms to avoid "B" bit transitions
            wait_HI()
            if k < 9
              v$ = ":0" + int2str(k+1)  // fix leading zero
            else
              v$ = ":" + int2str(k+1)
            endif
            lcd_xy(15,2)              // display the seconds count
            lcd_puts(v$)
            k = k + 1
          wend
          status$="getting time code"
        show_status()
        return
      endif
    wend
 endf

 function msf()   // This is the main loop
    lcd_init()
    lcd_nocurs()
    lcd_title()
    inh = 0      // inhibit time display the first time round
  while comkey?(2) = 0  // make sure there is an exit!
    find_start() // first call has inh = 0 so no garbage display
    inh = 1     // now allow time display on minute start
    get_time()
    calc_time() // time is written within find_start
  wend
 endf