maximum interrupt-based PWM frequency at 500 Hz I need to control a 24 kHz ultrasonic transducer and decided to use an arduino uno I have lying around.

Using tone() or the atmega's hardware pwm is not very suitable because I need two 50/50 square waves with a phase difference of pi (which is equal to two XOR'ed pins) to switch the output polarity of the h-bridge synchronously. As the uC dosen't have anything else to do I wrote the following sketch where I just wait for an interrupt and toggle pins 2&amp;3:

bool do_task=false;
void timer_init () {
  TCCR0A |= (1 &lt;&lt; WGM01);
  OCR0A = 83;
  TIMSK0 |= (1 &lt;&lt; OCIE0A);
  TCCR0B |= (1 &lt;&lt; CS01);
}
ISR (TIMER0_COMPA_vect) {
  do_task=true;
}

bool state=false;
void toggle() {
  state=!state;
  digitalWrite(2,state);
  digitalWrite(3,!state);

}

void setup() {
  pinMode(2,OUTPUT);
  pinMode(3,OUTPUT);
  timer_init();
}

void loop() {
  if(do_task) {
    do_task=false;
    toggle();
  }
}


I expected the output to have a frequency of ~24 kHz, but it is 500 Hz instead.

From my understanding:


16 MHz CPU frequency
timer0 prescaler is 8 (TCCR0B |= (1 &lt;&lt; CS01))
output compare value (OCR0A) is 83


would result in a frequency of 16 MHz / 8 / 83 = 24096,4 kHz -but as I mentioned- it's 500 Hz and doesn't seem to depend on the prescaler.
You wrote:


  Using [...] the atmega's hardware pwm is not very suitable because I
  need two 50/50 square waves with a phase difference of pi


I believe hardware PWM is the best solution to your problem. The PWM
outputs can be set to either “inverting” or “non-inverting” mode.
Set the two channels of a single timer to the same duty cycle, with one
in non-inverting mode and the other one in inverting mode, and you have
your complementary outputs.

Example using Timer 1:

const float SIGNAL_FREQUENCY = 24e3;
const uint16_t HALF_PERIOD = round(F_CPU / SIGNAL_FREQUENCY / 2);
const uint16_t PERIOD = 2 * HALF_PERIOD;

int main()
{
    /* Set the PWM pins as outputs. */
    DDRB |= _BV(PB1);  // digital  9 = PB1 = OC1A
    DDRB |= _BV(PB2);  // digital 10 = PB2 = OC1B

    /* Configure Timer 1. */
    ICR1   = PERIOD   - 1;
    OCR1A  = PERIOD/2 - 1;
    OCR1B  = PERIOD/2 - 1;
    TCCR1A = _BV(COM1A1)  // non-inverting PWM on OC1A
           | _BV(COM1B0)  // inverting PWM on OC1B
           | _BV(COM1B1)  // ditto
           | _BV(WGM11);  // mode 14: fast PWM, TOP = ICR1
    TCCR1B = _BV(WGM12)  // ditto
           | _BV(WGM13)  // ditto
           | _BV(CS10);  // clock at F_CPU, start timer
}