One Component Radio Clock Time Transmitter
by sbmull in Circuits > Microcontrollers
35555 Views, 60 Favorites, 0 Comments
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.
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 */ \
;
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 */ \
;
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.
/* 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.
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
}
}
}
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
}
}
}
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!
Have fun!