Is it possible to generate an exact 15 kHz clock pulse using an Arduino?

You can get pretty close if you program Timer 1 directly (not through the library), and have it run with the prescaler set to 1. Ideally, you want the period of the timer in clock cycles to be:

F_CPU / 15 kHz = 16,000 kHz / 15 kHz ≈ 1066.67 CPU cycles

If you round this to the nearest integer, you get

F_CPU / 1,067 = 16,000 kHz / 1,067 ≈ 14.9953 kHz

This is about 0.03% too slow, well within the tolerance of the ceramic resonator clocking the Uno.

Here is my attempt at this. I have tested it.

constexpr float PWM_FREQUENCY = 15e3;  // 15 kHz
constexpr uint16_t PERIOD = round(F_CPU / PWM_FREQUENCY);

void setup() {
    // Configure Timer 1 for 15 kHz PWM on pin 9 = PB1 = OC1A.
    DDRB  |= _BV(PB1);    // set pin as output
    TCCR1B = 0;           // stop timer
    TCCR1A = _BV(COM1A1)  // non-inverting PWN on OC1A
           | _BV(WGM11);  // mode 14: fast PWM, TOP = ICR1
    TCCR1B = _BV(WGM12)   // ditto
           | _BV(WGM13)   // ditto
           | _BV(CS10);   // clock @ F_CPU
    ICR1   = PERIOD - 1;  // period
    OCR1A  = PERIOD / 4;  // duty cycle
}

void loop(){}

Since the timer1 library only accepts whole numbers for the µs parameter you get a error. You could skip using the library and configure the timer directly. Or you could have a look at the source code of the library, and see that you can kind of bypass the limitation it has by only calculating a more accurate value for the ICR1 register.

Look at cycles = ((F_CPU/100000 * microseconds) / 20);. If you were to insert your 66.66666µs into this, instead of 66µs, you'd get 533 instead of 528.

This value is then used to set the PWM frequency in line 213. So you'd need to overwrite the ICR1 register with the more accurate value.

Your final code should look like:

pinMode(9,OUTPUT);
Timer1.initialize(66);
ICR1 = 533;
Timer1.pwm(9,255);

PS you might need to tweak this 533 value to get an even more accurate resulting frequency.


A better way to do the above average 15 kHz (or any other frequency) is with a phase accumulator scheme. There are no IF tests; on each tick of an interrupt, you add a step to an accumulator and output the state of its MSB. This can give incredible resolution, and is probably the best you can do. But though the average frequency will be dead-on, the jitter can be terrible. I use this method in my open-source DaqPort sketch https://www.daqarta.com/dw_rraa.htm#00ff, which is used by DaquinOscope https://www.daqarta.com/dw_rroo.htm and Arduino_Oscillators https://www.daqarta.com/dw_rrss.htm mini-apps running in Daqarta. There's also a Jitter_Tbl macro to compute the jitter for arbitrary sample rates and output frequencies. Although not relevant here, this phase accumulator method can also go to exceptionally low frequencies, into the micro-hertz range, and the jitter is extremely small there.