One Component Radio Clock Time Transmitter

33K5929

Intro: One Component Radio Clock Time Transmitter

Believe it or not, you can generate a time signal to set your WWVB controlled radio clocks with just an attiny45, wire antenna, and a battery. Upload the code to an attiny45, put a 20" or so wire on pin 6 and power with 5v.  You'll need to put your radio clock close to pick up the signal best.



STEP 1: The WWVB Time Signal and 60Khz Carrier

The WWVB time signal is a 60khz carrier modulated by reducing the carrier in a particular sequence to encode the time.  Wikipedia has a good article which I used to design this project: https://en.wikipedia.org/wiki/WWVB

The attiny45/85 has a fast timer that can be set to generate a square wave at 60khz like this:

/* Initalize Fast PWM on OCR1A*/
DDRB |= _BV(PB1); // Set PWM pin as output
PLLCSR |= _BV(PLLE); // Start PLL
_delay_us(100);      // Wait till PLL stablizes p. 9
PLLCSR |= _BV(PCKE); // Set Clock source to PLL
OCR1C = 132; // Set OCR1C to top p. 91 (60kkHz)
OCR1A = 66;  // Set beginning OCR1A value (50% duty cycle)
TCCR1  |= _BV(CS12);   /* Set clock prescaler to 8   */
TCCR1 |= _BV(PWM1A)   /* Enable PWM based on OCR1A  */ \
   |  _BV(COM1A0)   /* Set PWM compare mode p. 89 */ \
   ;


STEP 2: Modulating the 60khz Carrier

To modulated the carrier, just reduce the duty cycle of the wave by changing OCR1A to less than 66.  I set the other timer on the attiny45 to fire an interrupt 61 times a second like this:

/* Initalize CTC interupt on timer0 at 61hz */
TCCR0A  |= _BV(WGM01); //pg. 82  Mode 2 CTC OCR0A TOP
OCR0A |= 127;   // 8mhz / ((127+1) * 1024 prescale) = 61hz
TCCR0B |= _BV(CS00) | _BV(CS02);  // set prescaler to 1024
TIMSK |= _BV(OCIE0A);// enable compare match interrupt
sei(); // Enable interupts

This gives 61 times slices in each second to reduce the power of the carrier for modulation.  

STEP 3: Encoding the Modulation With a Time Signal

Next I wrote a routine which would keep track of the 61 time slices of each second and change to duty cycle of the carrier to encode the time signal.  Each case represents a second of the minute long time signal.  Each parameter can be set.  I have a few defines that can be used to easily change the hour and minutes.  You can add other defines too.

ISR(TIMER0_COMPA_vect){

switch (slot) {

  case 0 : { signal = 2;break;}

  case 1 : { signal = ((minute_tens >> 2) & 1);break;} // min 40
  case 2 : { signal = ((minute_tens >> 1) & 1);break;} // min 20
  case 3 : { signal = ((minute_tens >> 0) & 1);break;} // min 10

  case 5 : { signal = ((minute_ones >> 4) & 1);break;} // min 8
  case 6 : { signal = ((minute_ones >> 2) & 1);break;} // min 4
  case 7 : { signal = ((minute_ones >> 1) & 1);break;} // min 2
  case 8 : { signal = (minute_ones & 1);break;}   // min 1

  case 9 : { signal = 2;break;}

  case 12 : { signal = ((hour_tens >> 1) & 1);break;} // hour 20
  case 13 : { signal = ((hour_tens >> 0) & 1);break;} // hour 10

  case 15 : { signal = ((hour_ones >> 4) & 1);break;} // hour 8
  case 16 : { signal = ((hour_ones >> 2) & 1);break;} // hour 4
  case 17 : { signal = ((hour_ones >> 1) & 1);break;} // hour 2
  case 18 : { signal = (hour_ones & 1);break;}  // hour 1

  case 19: { signal = 2;break;}

  case 26: { signal = 1;break;}  //
  case 27: { signal = 1;break;}  // Day of year 60
  case 29: { signal = 2;break;}  //

  case 31: { signal = 1;break;}  //
  case 32: { signal = 1;break;}  // Day of year 6
  case 37: { signal = 1;break;}  //
  case 39: { signal = 2;break;}

  case 42: { signal = 1;break;}  //
  case 43: { signal = 1;break;}  // DUT1 = 0.3
  case 49: { signal = 2;break;}

  case 50: { signal = 1;break;}  // Year = 08
  case 55: { signal = 1;break;}  // Leap year = True
  case 59: { signal = 2;break;}

  default: { signal = 0;break;}
}

switch (signal) {

  case 0: {
  // 0 (0.2s reduced power)
  if (timer < 12) {OCR1A = 6;}
   else {OCR1A = 66;}
  } break;
  case 1: {
  // 1 (0.5s reduced power)
  if (timer < 30) {OCR1A = 6;}
   else {OCR1A = 66;}
  } break;
  case 2: {
  // Marker (0.8s reduced power)
  if (timer < 48) {OCR1A = 6;}
   else {OCR1A = 66;}
  } break;
}

timer++;   // Advance timer
if (timer == 61) { // Check to see if at end of second
  timer = 0;   // If so reset timer
  slot++;   // Advance data slot in minute data packet
  if (slot == 60) {
   slot = 0; // Reset slot to 0 if at 60 seconds
   minute_ones++; // Advance minute count
  }
}
}

STEP 4: The Hex Files and C Code

The hex files for direct uploading of your microcontroller can be downloaded below.  Also included is the C source file.

Have fun!

24 Comments

Using attiny85 i get the fallowing error "expected unqualified-id before 'volatile' " for this line of code "TIMSK |= _BV(OCIE0A);// enable compare match interrupt ".

I'm well on my way to implementing a device similar to this using a Particle Photon that can retrieve the current time via NTP. This article was a big help in that effort. It's pretty much my primary reference, along with the Wikipedia article on WWVB.

I would like to point out, though, that the code block for the switch statement is incorrect. Everywhere it says ">> 4", it should say ">> 3". Otherwise, those slots will always evaluate to zero.

Thanks!

Great Article. What is the reason for slicing the second into 61? I would have thought that you want to slice by 10 and change the duty cycle for 8 slices for marker, 2 slices for 0, and 5 slices for 1.

Thanks.

That is a good question. Look in the code comments and you will see this calculation: 8mhz / ((127+1) * 1024 prescale). That yields 61.05. So by setting the counter to 127, you get an integer number of slices at an adequate resolution. There are other solutions.

I'm sure it's not an issue, but that .05 bothers me. That means that every minute your modulated signal differs from an actual minute by (.05*60)/61 seconds, or roughly 50ms per minute. Meaning, if you started out in sync with WWVB, after 20 minutes you'd have transmitted 1199 bits when you should be at 1200, and WWVB would be 1 bit ahead. I'm sure the markers bring everything back in sync with anything receiving, but ideally the frames would add up to exactly one minute. This can be accomplished with a prescale of 512 and counter of 125, giving exactly 125 interrupts per second. Realistically the internal resonator isn't accurate enough to matter, but hey...whole numbers are prettier :p

Thanks. I love the simplicity and the elegance of this. As you know, depending on your location and how the radio is facing, you don't always get the optimal WWVB signal. This can be annoying right after the DST change where one or more of radios stay behind. This is a great way of nudging them to the right time until they catch up.

Tnx for this excellent piece of code and description. I wonder, is this something one could port to or re-implement using Arduino, like the pro-mini or similar, for example? Do you know of any such implementations?

Yes I think so. Almost any microcontroller with PWM should be able to do it. I do not know of any other implementations with AVR's (like the arduino), but it would be possible.

I want to try this project for my watch. What are the needed parts? Software?

Go farther, start with a GPS with 1PPS output, and you can be your own WWV, even where the WWV signal can't reach. Second, you could use something like a neopixel to flash the timecode (and a higher resolution tick), aligned to the UTC second so you could also timestamp video with high accuracy. (Or send tones like the 5/10/20Mhz time services do).

You are more than welcome to extend this project. For myself, adding more would subtract from its beauty-elegance-simplicity.

I was thinking about synchronizing clocks on a sailing boat using this idea. I have several brass clocks with cheap mechanisms I could replace with cheap radio controlled mechanisms but they would not get synchronized out to sea. Also maybe it would be nice to set them to local time. The boat has a computer connected to GPS so there is a time signal. Soon after thinking about this I thought maybe some one has done it and then found your article. Well done! I would be interested if anyone has extended the project to take time from GPS.

Sorry for this elementary question, but how are you making the 60kHz signal with Timer1?

I am assuming that using the internal oscillator, you start with 8 MHz and you use a prescale of 8. So the timer clock is at 1 MHz, right?

So each count is 1 microsecond. So with 66 as OCR1A, and clearing the counter with 132, don't you toggle PB1 at 66 us?

Sorry for the noob question, and thanks in advance.

No need to apologize! In this case the Datasheet is your friend. Check out page 91 of the datasheet for the attiny85. There are listed there settings for timer1 for 20khz to 500khz. For 60khz, it is PCK/8 and OCR1C=132. The formula is Fpwm = Fclk/(OCR1A+1) (page 90).

Note that the PLL is being used, so the base clock is at 64mhz. We are using a 8 prescaler so the clock for the timer is 8mhz.

8mhz/(132+1) => 60,150hz.

I hope that helps and thanks for your interest in my project!

This is really cool. My first thought was "ok, now how do we get it to pull the current time from a timeserver so we don't have to hardcode the time" and brought the code over to the Arduino IDE to see if I could translate the code for an Uno (atmega328). After googling the myriad errors I understand now what PLL is and see that the atmega828 doesn't support it at the hardware level. Does this mean one would have to use an algorithm in place of the hardware PLL or is this chip just unsuitable for the task? If so, do you think the attiny45 or 85 have enough pins to interface with an ethernet shield to get the time (pardon my ignorance, I'm not familiar with the attiny).

Thanks for your comment! I see no reason why the code would not work on an atmega. The critical part is getting the timer to pulse at 60Khz. You will need to tinker with the timer settings on the atmega to find a configuration that works.

Also, if the ethernet shield code uses interrupts, you'll need to make sure they do not interfere with the time code modulation interrupt.

If the ethernet shield uses spi, and I think it does, you only need three datalines, and that many are still available on the attiny. So I would say, it is possible... if you can get the code to work in the dataspace.

Sounds like a fun challenge!

So it doesn't necessarily need PLL as the clock source?

Yes, the PPL is not critical. It only provides an elegant and simple way to generate a 60kHz squarewave. Also, you can tweek a couple of numbers and make it a 1, 2 or 4 Mhz transmitter too ;). At 1Mhz you can listen to the signal on an AM or shortwave radio.

Whats the usb ISP you're using to program the attiny??

It is a bus pirate. I like it because I can use a terminal interface to turn the power on and off. I also use a homemade USBtiny which is faster for programming. Mine is an older version that has a case. The newer ones are plain pcb's with headers.

More Comments