Precise 500Hz generator for Uno Rev3

This code generates a precise 500Hz square wave on digital pin 8. On my Arduino Uno Rev3, it is precisely 500hz with a 50% duty cycle (by scope). I owe this precision to ideas from Amanda Ghassaei, Joop Brokking and Nick Gammon.

The code uses ASM to disable TIMER0 which prevents interrupts from affecting the accuracy, and direct port manipulation to set and invert the output pin. delayMicroseconds is used to set a precise timed delay between switching, and the delay value (994) allows for the loop and port change work.

If I’ve missed anything, please contribute your thoughts and advice. Thanks to all those who add their expertise on these fora and help us all learning the intricacies of microcontrollers for the fun of it. Special thanks to those mentioned above whose ideas have allowed me to develop a better understanding of it all.

/*  This program outputs a 500Hz square wave on Digital pin 8.*/
/*  By steve_mcdonald on 12/04/2019 */

void setup() {
  DDRB = (1 << 0);                // Set PB0 to output (D8)
  cli();                                // Disable interrupts
  TIMSK0 &= -(1 << TOIE0);   // Set Timer 0 interrupt off
  sei();                               // Enable interrupts
}

void loop() {
  PORTB = PORTB ^ 1;          // Invert PB0 (D8)
  delayMicroseconds(994);    // Wait 1 millisecond
}

Why not use take over timer1 or timer2 and generate the output with hardware output compare so you dont need to use any interrupts or disable timer0 interupts?

Ah! More to learn about and experiment with - thanks for the suggestion.

I suspect I'll still want to disable TIMER0 though, as it lengthens the pulse duration every time it fires. Or am I missing something?

Thanks for the input.

steve_mcdonald:
Ah! More to learn about and experiment with - thanks for the suggestion.

I suspect I'll still want to disable TIMER0 though, as it lengthens the pulse duration every time it fires. Or am I missing something?

Thanks for the input.

If you use the output compare of a timer (the same mechanism used to generate PWM for analogWrite), that is happening without the intervention of the processor, or needing to run any interrupt.

You would take over timer1 entirely, configure it as you wanted (probably prescaler of 1, WGM mode 14 so ICR1 is TOP, set to 31, the appropriate OCR1 register set to 15, and PWM output on the pin you want it on) EDIT: oops, I read that as 500kHz, not 500Hz - the code MartinL posted below looks better.

Consult the datasheet for the details.

Here's some code that outputs 500Hz PWM, on the Uno's digital pins D9 and D10, using timer 1:

// Set-up hardware PWM on the Arduino UNO at 500Hz on digital pins D9 and D10
void setup() { 
  pinMode(9, OUTPUT);                               // Set digital pin 9 (D9) to an output
  pinMode(10, OUTPUT);                              // Set digital pin 10 (D10) to an output
  TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM11);  // Enable PWM outputs for OC1A and OC1B on digital pins 9, 10
  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS11);     // Set fast PWM and prescaler of 8 on timer 1
  ICR1 = 3999;                                      // Set the PWM frequency to 500Hz
  OCR1A = 1999;                                     // Set duty-cycle to 50% on D9
  OCR1B = 999;                                      // Set duty-cycle to 25% on D10
}

void loop() {}

Thanks DrAzzy and MartinL for your advice. I like the elegance of your code MartinL. I saved a couple of hundred bytes by setting the output directly and using pin 9 only because I only need the one output. The signal looks really nice, stable and precise.

// Set-up hardware PWM on the Arduino UNO at 500Hz on digital pins D9 and D10
void setup() {
  DDRB = (1<< 1);                                   // Set PB1 to output (D9)
  TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM11);  // Enable PWM outputs for OC1A and OC1B on digital pins 9, 10
  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS11);     // Set fast PWM and prescaler of 8 on timer 1
  ICR1 = 3999;                                      // Set the PWM frequency to 500Hz
  OCR1A = 1999;                                     // Set duty-cycle to 50% on D9
}

void loop() {}