Controlling an ESC using timer2

(if you know how an ESC / TIMER works, I guess you can jump to the code below.. in summary, I expected it to send a 1ms pulse, then wait for 16ms; but my guess is that it's sending more than 1ms)

--

I'm trying to make a program to control an ESC. I want to output to the ESC to be a pulse of
1000 microseconds + [0 - 1000 microseconds based on the speed]
and then a delay of 16ms.

I simulated this in a loop() and it works.

Now I'm trying to do this using TIMER2 (8 bits). The general idea is:

  1. Put the timer in comparator mode
  2. Set the timer clock divider to 64, which means it will be incremented each 4us;
  3. Set the ESC PIN to HIGH and the comparator to 250 (ie, interrupt after 1ms)
  4. Set the comparator to the desired "speed" (0 for 0, 125 for 50%, 250 for 100%), which will wait between 0-1ms (still high)
  5. Set the ESC PIN to LOW, the clock divider to 1024 and set the comparator to 255, so it will wait ~16ms
  6. Go to 2

But this is not working; I don't have an oscilloscope, but my best hypothesis is that when the speed=0 it's sending a pulse longer than 1ms, otherwise the ESC should at least be arming, and it isn't.

void setup() {
  pinMode(6, OUTPUT);

  TCCR2A = 0;
  TCCR2B = 0;
  TIMSK2 = (TIMSK2 & B11111000) | B00000010;
  TCCR2B = (TCCR2B & B11111000) | 0x04;
  TCNT2 = 0;
  OCR1A = 100;
}

int speed = 0;
void loop() {
  delay(20);
  return;
}

ISR(TIMER2_COMPA_vect) {
  static int state = 0;

  state++;

  switch(state) {
    case 1:
      TCCR2B = (TCCR2B & B11111000) | 0x04; // 4us

      digitalWrite(6, HIGH);
      OCR2A = 250; // count to 250*4us = 1ms
      break;
    case 2:
      OCR2A = speed / 4;  // count up to 250*4us = 1ms
      break;
    case 3:
      digitalWrite(6, LOW);
      TCCR2B = (TCCR2B & B11111000) | 0x07; // 64us
      OCR2A = 255; // count to 64*256us ~= 16ms
      state = 0;
      break;
  }

  TCNT2 = 0;
}

Why don't you use Timer 1 (16 bits)? That way you can do the whole sequence in one shot without needing to constantly mess with the prescaler selection.

Because TIMER1 it's in use already :confused:

The biggest problem I see is that you shouldn't be writing absolute values to the Output Compare register when you update it. Rather, set it to a value relative to its current value. For example, instead of this:
OCR2A = 250;
Do this:
OCR2A += 250;
Don't worry, the 8-bit value will wrap around 0 and work just fine.

Another issue is that you're going through all the trouble of using the Timer to tighten up your timing and then you're using 'digitalWrite()' in the ISR!!! That's slow, slow, slow. The best thing you can do is switch to one of the output pins that are directly controlled by the Output Compare hardware (read about it in the datasheet). Assuming you're using an Uno, that's PB3 (Pin 11) for Output Compare Unit A or PD3 (Pin 3) for Output Compare Unit A. If you can't use either of those two pins for your output, at least switch over to using Direct Port manipulation to set / reset the output.

Finally, and I don't know the answer to this, are you sure that the all the counter hardware behaves smoothly and glitch-free when you switch the pre-scaler setting?

Unless this is just a learning exercise why not use the ServoTimer2 library which sorts out all the complicated timing for you?

Steve

I was hoping this would be easy and I was making some obvious mistake. Having my own code would allow me to keep control of the Timer2 ISR instead of delegating to a library in case I want to do something else with it. In any case, I ended up switching to ServoTimer2 as suggested and it works fine :slight_smile:
Thanks