3

I'm trying to understand how to fully use the COMPB vector ISR of Timer0 on the ATMega328 (standard Arduino core). I realize it will make delay() and millis() not work but that's ok by me. I've setup some test code that should output a 244Hz signal on D4 and 488Hz on D5 (note, this is running at 8MHz). But D5 (COMPB) is always what D4 (COMPA) is. Any thoughts? Code below:

#include <avr/interrupt.h>

volatile bool stateA = false;
ISR(TIMER0_COMPA_vect)
{
    if (stateA)
        PORTD |= _BV(PIND4);
    else
        PORTD &= ~(_BV(PIND4));

    stateA = !stateA;
}

volatile bool stateB = false;
ISR(TIMER0_COMPB_vect)
{
    if (stateB)
        PORTD |= _BV(PIND5);
    else
        PORTD &= ~(_BV(PIND5));

    stateB = !stateB;
}

void setup()
{
    DDRD |= _BV(PIND4) | _BV(PIND5); 

    //set timer0 interrupt at 2kHz
    TCCR0A = 0;// set entire TCCR2A register to 0
    TCCR0B = 0;// same for TCCR2B
    TCNT0 = 0;//initialize counter value to 0
    // set compare match register for 2khz increments
    OCR0A = 255;//244Hz
    OCR0B = 127;//488Hz
    // turn on CTC mode
    TCCR0A |= (1 << WGM01);
    // Set CS01 and CS00 bits for 64 prescaler
    TCCR0B |= (1 << CS01) | (1 << CS00);
    // enable timer compare interrupt
    TIMSK0 |= (1 << OCIE0B) | (1 << OCIE0A);

    interrupts();

}

Update: After thinking about it, I'm realizing that it's not that I need two separate frequencies (though I just kinda wanted to know how, so this is good stuff) but I just need two separate interrupts, only using Timer0 (1 & 2 are already in use elsewhere). They can be the same frequency, most likely. I probably could put all the functionality I need in the same TIMER0_COMPA_vect, however I was trying to use that in a library I was writing and was trying to keep the functionality separate... this may not be possible.

3
  • 2
    You could optimize your code a little bit by replacing if (stateA) ... with PORTD ^= _BV(PIND4); (exclusive or of this bit) and removing those volatile state variables.
    – jfpoilpret
    Apr 3, 2014 at 4:12
  • I don't see the point why you would need 2 interrupts at the same frequency, you can do your stuff in the same ISR.
    – jfpoilpret
    Apr 3, 2014 at 18:25
  • Because some of the stuff is in a library and I want to keep it separate from other logic
    – Adam Haile
    Apr 3, 2014 at 20:40

4 Answers 4

1

I think your code did not set TIMSK0 properly.

Instead of:

TIMSK0 |= (1 << OCIE0B) | (1 << OCIE0A);

You should use:

TIMSK0 = (1 << OCIE0B) | (1 << OCIE0A);

Why? Because TIMSK0 is already initialized by Arduino libraries before your setup() is called.

If you take a look at hardware/arduino/cores/arduino/wiring.c and search for the init() function, you will see that it contains the following lines:

    // enable timer 0 overflow interrupt
#if defined(TIMSK) && defined(TOIE0)
    sbi(TIMSK, TOIE0);
#elif defined(TIMSK0) && defined(TOIE0)
    sbi(TIMSK0, TOIE0);
#else
    #error  Timer 0 overflow interrupt not set correctly
#endif

With these lines, the TOIE0 bit of TIMSK0 is set, but your setup() does not clear it.

3
  • This shouldn't actually interfere though. Timer 0 overflow is only for the Arduino timing functions. Apr 3, 2014 at 4:14
  • That's what I thought, but I think it is still preferrable to ensure you set all Timer0 registers properly. Timer functions are a bit complex and you may be bitten by some strange behavior you did not expect, if you don't care for it.
    – jfpoilpret
    Apr 3, 2014 at 4:19
  • See update above
    – Adam Haile
    Apr 3, 2014 at 13:47
1

Your code doesn't do what you think it does. The timer always runs at 244Hz, and OCR0B only matches twice per period, causing it to have the same frequency but a different phase. What you need to do is set OC0B on compare match and clear it in the overflow ISR. OCR0A you can simply set to toggle on match.

TCCR0A = _BV(COM0A0) | _BV(COM0B1) | _BV(COM0B0) | _BV(WGM01);
TIMSK0 |= _BV(OCIE0A);

 ...

ISR(TIMER0_COMPA_vect)
{
  PORTB &= ~_BV(PD5);
}

Also, OC0A is D6, not D4.

8
  • Your code doesn't seem to match what you are saying to do... could you maybe clarify a little?
    – Adam Haile
    Apr 3, 2014 at 4:12
  • CTC resets the timer when it matches OCR0A. We set COM0A[1:0] to 0b01 in order to tell OC0A to toggle. For OC0B we set it in the middle of the timer (COM0B[1:0]=0b11, OCR0B ~= OCR0A/2) period, and then clear it at the end (via the ISR). This gives it twice the frequency of OC0A. Apr 3, 2014 at 4:17
  • Sorry, but some code would be really helpful, I'm still a bit lost.
    – Adam Haile
    Apr 3, 2014 at 4:18
  • The code I gave integrates directly into the code you already have, simply replace the assignment to TCCR0A as well as the whole ISR. Apr 3, 2014 at 4:20
  • As well as the overflow interrupt enable that I just added (since you won't have the Arduino libraries to do it for you). Apr 3, 2014 at 4:26
1

Actually, after reading the ATmega328P datasheet again, I am not sure that you can use both OCR0A and OCR0B in normal timer mode.

In particular, since you use CTC mode, I think that TCNT0 will be cleared every time its value reaches OCR0A i.e. 127.

Since you need 244Hz and 488Hz (exactly twice 244Hz), I think you can operate with only one of OCR0A or OCRB0 and do the work in one ISR only:

#include <avr/interrupt.h>

volatile bool setBothPins = false;
ISR(TIMER0_COMPA_vect)
{
    if (setBothPins)
        // D4 is changed only once (244Hz)for every 2 changes of D5 (488Hz)
        PORTD ^= _BV(PIND4);
    PORTD ^= _BV(PIND5);

    setBothPins = !setBothPins;
}

void setup()
{
    DDRD |= _BV(PIND4) | _BV(PIND5); 

    //set timer0 interrupt at 2kHz
    TCCR0A = 0;// set entire TCCR2A register to 0
    TCCR0B = 0;// same for TCCR2B
    TCNT0 = 0;//initialize counter value to 0
    // set compare match register for 2khz increments
    OCR0A = 127;//488Hz
    // turn on CTC mode
    TCCR0A |= (1 << WGM01);
    // Set CS01 and CS00 bits for 64 prescaler
    TCCR0B |= (1 << CS01) | (1 << CS00);
    // enable timer compare interrupt
    TIMSK0 = (1 << OCIE0A);

    interrupts();
}
2
  • Just checked it on my UNO, seems to work fine (with double frequencies, as the UNO runs on 16MHz).
    – jfpoilpret
    Apr 3, 2014 at 5:21
  • See update above
    – Adam Haile
    Apr 3, 2014 at 13:47
1

It is possible to use the Timer0 compare interrupts without affecting the millis() and micros() functions. Simply set the compare interrupts to occur at 1/3rd and 2/3rds of the overflow period. That way they won't conflict with each other because there will be some time between each of the three interrupts (overflow, compare A and compare B). You can't change the frequency though because that would change the update of millis() and micros(). Instead, you could use software post-scalers within the ISRs, something like this:

volatile bool     isr_t0a_flag = false;
volatile uint32_t isr_t0a_delta_time = 0;
#define           TIMER_0_COMPARE_A_POST_SCALER 50

volatile bool     isr_t0b_flag = false;
volatile uint32_t isr_t0b_delta_time = 0;
#define           TIMER_0_COMPARE_B_POST_SCALER 100

void setup()
{
  Serial.begin(115200);
  Serial.print("\n\n");
  
  // Find out what Timer0 has been initialised to.
  Serial.println(TCCR0A);  // 3 = Fast PWM mode with a fixed timeout of 255.
  Serial.println(TCCR0B);  // 3 = 64 prescaler.
  Serial.println(OCR0A);   // 0
  Serial.println(OCR0B);   // 0
  Serial.println(TIMSK0);  // 1 = Timer overflow interrupt enabled for millis()/micros().
  Serial.print("\n\n");

  cli(); // Disable interrupts.

  //
  // Set Timer0 compare interrupts
  //
  // Clock frequency     = 16 MHz
  // Prescaler           = 64
  // Timeout             = 255
  //
  // Overflow interrupt frequency = 16 MHz / 64 / (255 + 1) = 977 Hz (1024 us)
  // Overflow interrupt frequency =  8 MHz / 64 / (255 + 1) = 488 Hz (2048 us)
  //
  OCR0A  = 85;              // Set 1/3rd phase shift for compare A.
  OCR0B  = 171;             // Set 2/3rds phase shift for compare B.
  TIMSK0 |= (1 << OCIE0A);  // Enable compare A interrupt.
  TIMSK0 |= (1 << OCIE0B);  // Enable compare B interrupt.

  sei(); // Enable interrupts.

  // Verification.
  Serial.println(TCCR0A);  // 3   = Fast PWM mode with a fixed timeout of 255.
  Serial.println(TCCR0B);  // 3   = 64 prescaler.
  Serial.println(OCR0A);   // 85  = 1/3rd phase shift for compare A.
  Serial.println(OCR0B);   // 171 = 2/3rds phase shift for compare B.
  Serial.println(TIMSK0);  // 7   = All 3 interrupts enabled.
  Serial.print("\n\n");
}

void loop()
{
  uint32_t t0a_delta_time = 0;
  uint32_t t0b_delta_time = 0;

  ////////////////////////////
  // BEGIN CRITICAL SECTION

  cli();  // Disable interupts.

  bool t0a_flag = isr_t0a_flag;
  if (t0a_flag)
  {
    t0a_delta_time = isr_t0a_delta_time;
    isr_t0a_flag = false;
  }

  bool t0b_flag = isr_t0b_flag;
  if (t0b_flag)
  {
    t0b_delta_time = isr_t0b_delta_time;
    isr_t0b_flag = false;
  }

  sei();  // Enable interrupts.

  // END CRITICAL SECTION
  ////////////////////////////

  if (t0a_flag)
  {
    Serial.print("T0a ");
    Serial.println(t0a_delta_time);
  }

  if (t0b_flag)
  {
    Serial.print("T0b ");
    Serial.println(t0b_delta_time);
  }
}

ISR(TIMER0_COMPA_vect)
{
  static byte count = 0;
  count++;
  if (count >= TIMER_0_COMPARE_A_POST_SCALER)
  {
    count = 0;
    static uint32_t previous_time = 0;
    uint32_t time = micros();
    uint32_t delta_time = time - previous_time;
    previous_time = time;
    isr_t0a_delta_time = delta_time;
    isr_t0a_flag = true;
  }
}

ISR(TIMER0_COMPB_vect)
{
  static byte count = 0;
  count++;
  if (count >= TIMER_0_COMPARE_B_POST_SCALER)
  {
    count = 0;
    static uint32_t previous_time = 0;
    uint32_t time = micros();
    uint32_t delta_time = time - previous_time;
    previous_time = time;
    isr_t0b_delta_time = delta_time;
    isr_t0b_flag = true;
  }
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.