Divide 10 MHz to 1PPS with a Microcontroller
I often find it useful to gate frequency counters using a 1 pulse per second (1PPS) signal derived from a 10 MHz precision frequency reference. However, a divide-by-10-million circuit isn’t trivial to implement. Counter ICs exist which enable divide-by-100 by combining multiple divide-by-2 and divide-by-5 stages (e.g., MC74HC390A around $0.85 each), but dividing 10 MHz all the way down to 1Hz would require at least 4 of these chips and a lot of wiring.
You can clock a microcontroller at 10 MHz and use its timer and interrupt systems to generate 1PPS. For example, an ATTiny202 in an 8-pin SOIC package is available from Mouser (>50k stocked) for $0.51 each. Note that modern series AVRs require a special UDPI programmer.
ATTiny202 ($0.51) | ATTint826 ($0.95) |
---|---|
![]() |
![]() |
This page documents a divide-by-10-million circuit achieved with a single microcontroller to scale a 10MHz frequency reference down to 1PPS. I’m using an ATTiny826 because that is what I have on hand, but these concepts apply to any microcontroller with a 16-bit timer.
ATTiny Breakout Board
Some AVRs come in DIP packages but their pin numbers may be different than the same chip in a SMT package. To facilitate prototyping using designs and code that will work identically across a breadboard prototype and a PCB containing SMT chips, I prefer to build DIP breakout boards using whatever SMT package I intend to include in my final builds. In this case it’s ATTint826 in a SOIC-20 package, and I can easily use this in a breadboard by soldering them onto SOIC-to-DIP breakout boards.
I assembled the breakout board by hand using a regular soldering iron. When working with small packages it helps so much to coat the pins with a little tack flux to facilitate wetting and prevent solder bridges. I’m presently using Chip Quik NC191. Even if flux is advertized as “no-clean”, it’s good practice and makes the boards look much nicer to remove remaining flux with acetone and Q-tips or brushes.
Circuit
-
FTDI breakout board for power: To test this design I’m using a FT232 breakout board just to provide easy access to
GND
andVcc
(5V from the USB rail). -
10 MHz can oscillator: It’s not ovenized or GPS disciplined, but I’m using this as a stand-in for whatever high-precision 10 MHz frequency standard will eventually be used in this circuit. The important thing is just to know that it outputs 0-5V square waves at 10 MHz going into the
EXTCLK
pin of the microcontroller -
UPDI Programmer: I’m using the Atmel-ICE, but a MPLAB Snap would also work here. See Programming Modern AVR Microcontrollers for more information.
-
Output: A LED on an output pin will visualize the 1pps signal
Configuration Change Protection (CCP)
Traditional AVR microcontrollers used fuse bits to set the clock source, but modern series chips can change the clock source from within code. However, modifying the clock source requires temporarily disabling the configuration change protection (CCP) system.
Disabling the CCP only lasts four clock cycles, so the immediate next statement must be assignment of the new value. I use the following function to facilitate this action.
/* Write a value to a CCP-protected register */
void ccp_write(volatile register8_t* address, uint8_t value){
CCP = CCP_IOREG_gc;
*address = value;
}
// Use internal 20 MHz clock with CKOUT pin enabled
ccp_write(&CLKCTRL.MCLKCTRLA, CLKCTRL.MCLKCTRLA | CLKCTRL_CLKOUT_bm);
Do not use compound statements when writing to the CCP register. The code below fails to change clock as one may expect by looking at the code, presumably because the combined OR operation with the assignment exceeds four clock cycles. Instead of direct assignment, use the ccp_write
function described above.
// WARNING: This code does not actually change the clock source
CCP = CCP_IOREG_gc;
CLKCTRL.MCLKCTRLA = CLKCTRL.MCLKCTRLA | CLKCTRL_CLKOUT_bm;
Configuring the Clock Source
Internal 10 MHz clock
This is the configuration I use to achieve a 10 CPU clock using the built-in 20 MHz oscillator.
void configure_clock_internal_10mhz(){
ccp_write(&CLKCTRL.MCLKCTRLA, CLKCTRL.MCLKCTRLA | CLKCTRL_CLKOUT_bm); // 20 MHz internal clock, enable CKOUT
ccp_write(&CLKCTRL.MCLKCTRLB, CLKCTRL_PEN_bm); // enable divide-by-2 clock prescaler
}
External 10 MHz clock
This is the configuration I use to clock the CPU from an external 10 MHz clock source applied to the EXTCLK
pin.
void configure_clock_external(){
ccp_write(&CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_EXTCLK_gc | CLKCTRL_CLKOUT_bm); // external clock, enable CKOUT
ccp_write(&CLKCTRL.MCLKCTRLB, 0); // disable prescaler
}
Configuring the 16-Bit Timer
This is how I configured my ATTiny826’s TCA0 16-bit timer to fire an interrupt every 200 ms.
-
Prescale: By enabling a divide-by-64 prescaler, my 10 MHz input becomes 156,250 Hz.
-
Top: By setting the top of my 16-bit counter at 31,250, I achieve exactly 5 overflows per second (once every 200 ms).
-
Interrupt: By enabling an overflow interrupt, I am able to call a function every 200 ms.
void configure_1pps(){
// 10 MHz system clock with div64 prescaler is 156,250 Hz.
// Setting a 16-bit timer's top to 31,250 means 5 overflows per second.
TCA0.SINGLE.INTCTRL = TCA_SINGLE_OVF_bm; // overflow interrupt
TCA0.SINGLE.CTRLB = TCA_SINGLE_WGMODE_NORMAL_gc; // normal mode
TCA0.SINGLE.PER = 31249UL; // control timer period by setting timer top
TCA0.SINGLE.CTRLA |= TCA_SINGLE_CLKSEL_DIV64_gc; // set clock source
TCA0.SINGLE.CTRLA |= TCA_SINGLE_ENABLE_bm; // start timer
}
Alternatively, multiple timers could be cascaded to achieve a similar effect. Modern AVR series microcontrollers have sections in their datasheet describing considerations for cascading two 16-bit timers to create a single 32-bit timer. Using this strategy one could set the top of the counter to 5 million and arrange an interrupt to toggle an LED, resulting in a 1Hz signal with 50% duty.
Configuring the Interrupt System
This method is called whenever the timer’s overflow interrupt is triggered. Since it’s called 5 times per second, I just need a global counter to count the number of times it was called, and set an output pin to high on every 5th invocation.
uint8_t overflow_count;
ISR(TCA0_OVF_vect)
{
overflow_count++;
if (overflow_count == 5){
overflow_count = 0;
PORTB.OUT = PIN1_bm;
} else {
PORTB.OUT = 0;
}
TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm; // indicate interrupt was handled
}
Do not forget to enable global interrupts in your start-up sequence! This is an easy mistake to make, and without calling this function the overflow function will never be invoked.
sei(); // enable global interrupts
Results
We have achieved a light that blinks exactly once per second with roughly the same precision as the 10 MHz frequency reference used to clock the microcontroller. This output signal is ready to use for precision measurement purposes, such as toggling the gate of a discrete frequency counter.
Resources
-
Full source code: ATTiny826 1pps project on GitHub and specifically main.c
-
Inspecting the header file
iotn826.h
in my Program Files / Atmel folder was very useful for identifying named bit masks stored as enums. There is a similarly named file for every supported AVR microcontroller. -
EEVblog forum: Divide by 10000000
-
EEVblog forum: Divide by 10 prescaler for frequency counter
-
EEVblog forum: 10MHz to 1pps divider
-
EEVblog forum: Easiest way to divide 10MHz to 1MHz?
-
YouTube: Build a DIY Frequency Divider
-
All About Circuits thread: How to convert 10 MHz sine wave to 1Hz TTL (PPS)?
-
10 MHz to 1 Hz frequency divider using discrete 74HC4017D stages by David C. Partridge