2

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.

  • 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 '14 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 '14 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 '14 at 20:40
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.

| improve this answer | |
  • This shouldn't actually interfere though. Timer 0 overflow is only for the Arduino timing functions. – Ignacio Vazquez-Abrams Apr 3 '14 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 '14 at 4:19
  • See update above – Adam Haile Apr 3 '14 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.

| improve this answer | |
  • Your code doesn't seem to match what you are saying to do... could you maybe clarify a little? – Adam Haile Apr 3 '14 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. – Ignacio Vazquez-Abrams Apr 3 '14 at 4:17
  • Sorry, but some code would be really helpful, I'm still a bit lost. – Adam Haile Apr 3 '14 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. – Ignacio Vazquez-Abrams Apr 3 '14 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). – Ignacio Vazquez-Abrams Apr 3 '14 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();
}
| improve this answer | |
  • Just checked it on my UNO, seems to work fine (with double frequencies, as the UNO runs on 16MHz). – jfpoilpret Apr 3 '14 at 5:21
  • See update above – Adam Haile Apr 3 '14 at 13:47

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