atmega328p: OCR1B takes value from OCR1A

Go To Last Post
8 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello. I'm practicing with the timer1 CTC mode on atmega328p (arduino) in order to toggle two LEDs. One every 1 sec and the other every 100 ms. But even though i have given different values at the OCR1B and OCR1A registers the LEDs toggle both every second.
Like OCR1B takes the value from OCR1A. Have i done something wrong in my code? Maybe the turn in which i initiate the timers registers?

Here is my code:

#include 
#include 

int main(void)
{
	DDRD = 0b11000000;

	OCR1A = 0x3D08;
	OCR1B = 0x061A;
	TCCR1B |= 1<<WGM12;
	TIMSK1 |= 1<<OCIE1A | 1<<OCIE1B;
	TCCR1B |= 1<<CS12 | 1<<CS10;
   sei();

	while(1)
	{
	}

	return 0;
}

ISR(TIMER1_COMPA_vect)
{
	PORTD ^= 1<<PIND7;
}

ISR(TIMER1_COMPB_vect)
{
	PORTD ^= 1<<PIND6;
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Think about how CTC works. You have set WGM12 so timer1 is in CTC mode with OCR1A as "TOP". That means the frequency of the timer will be set by the OCR1A register. It will count from 0x0000 to 0x3D08 in each cycle. That is what sets the frequency. Sure, as TCNT1 passes 0x061A on the way up, it will match with OCR1B and trigger that interrupt but all that means is that the LED switching points are out of phase - but they are still at the same frequency set by OCR1A.

Bottom line is that you cannot achieve what you want the way you are doing it. However because 1s is just ten lots of 100ms there is a slightly simpler/more obvious way:

#include 
#include 

int main(void)
{
   DDRD = 0b11000000;

   OCR1A = 0x061A;
   TCCR1B |= 1<<WGM12;
   TIMSK1 |= 1<<OCIE1A;
   TCCR1B |= 1<<CS12 | 1<<CS10;
   sei();

   while(1)
   {
   }

   return 0;
}

ISR(TIMER1_COMPA_vect)
{
   static count;
   PORTD ^= 1<<PIND6; // do this every 100ms
   count++;
   if (count == 10) {
      PORTD ^= 1<<PIND7; // only do this every 1000ms
      count = 0;
   }
}

That is have the ISR happen every 100ms. Toggle the 100ms LED in every interrupt but toggle the 1s LED only after every 10 interrupts.

This is a common thing to do in MCUs when you have multiple things to do at different rates. In this case you could just use the 100ms interrupt because 1,000ms happens to be 100ms * 10.

But say you'd want 230ms and 740ms. In that case set the interrupt to be every 10ms then:

ISR(TIMER1_COMPA_vect)
{
   static count6, count7;
   count6++;
   count7++;
   if (count6 == 23) {
      PORTD ^= 1<<PIND6; // every 230ms
      count = 0;
   }
   if (count7 == 74) {
      PORTD ^= 1<<PIND7; // every 740ms
      count = 0;
   }
}

More generally set the interrupt rate to a common denominator of all the periods you want to achieve and for each function count to N times that denominator.

A bit of a contrived example but if you wanted events at 91ms, 221ms and 299ms then recognize that 13 is a common denominator:

 91 = 13 *  7
221 = 13 * 17
299 = 13 * 23

So set the interrupt for every 13ms then count to 7, 17 and 23.

EDIT: corrected maths - must learn 13 times table!

Last Edited: Fri. May 31, 2013 - 12:36 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

CTC mode depends on OCR1A alone.
If OCR1B > OCR1A then you will never get a COMPB interrupt.

The mega328 has three timers. So you can have 3 independent CTC periods.

In practice, you might just as well put two outputs on Timer1. e.g. OCR1A doing 1000ms and adding 100ms to OCR1B on every COMPB. (remember to check for OCR1A > OCR1B)

Alternatively, do CTC for 100ms and simply toggle OC1B on the 10th IRQ.

Neither method needs two ISR()s. A single ISR() is all you need. Let the hardware do OC1A pin.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK, i understood what you say! I was confused because i have read many tutorials about CTC and PWM and couldn't find out the difference. Specifically he schematics help very much. But i think that the schematics about the CTC show the TCNT increase towards time while the schematics about PWM show the electrical pulse. Please correct me if i'm wrong.
The reason i used both interrupts is because i wanted to see if i can have two different frequencies using only one timer.
Thank you both very much about the help :D Some things you noticed are not found in the tutorials!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

The reason i used both interrupts is because i wanted to see if i can have two different frequencies using only one timer.

Well, tell more about the "frequencies". Always 50% duty cycle? Then yes, you can use the "leapfrog" approach; link below.

Consider with your slow (in AVR terms) frequencies. You could do n of these with a single timer, and use 1ms or 5ms or 10ms or whatever "ticks". Use the master tick counter to apply your waveforms using "soft timers".

https://www.avrfreaks.net/index.p...

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
The reason i used both interrupts is because i wanted to see if i can have two different frequencies using only one timer.
Yes you can. Set the timer to use normal mode, set to have compare match interrupts for both OCRA and OCRB. In each interrupt, add the amount needed to get the proper frequency for that pin to the current value of the corresponding OCR register:

#include 
#include 

#define HALF_PERIOD_A 0x3D08
#define HALF_PERIOD_B 0x061A
int main(void)
{
   DDRD = 0b11000000;

   OCR1A = HALF_PERIOD_A;
   OCR1B = HALF_PERIOD_B;
   TIMSK1 = 1<<OCIE1A | 1<<OCIE1B;
   TCCR1B = 1<<CS12 | 1<<CS10;
   sei();

   while(1)
   {
   }

   return 0;
}

ISR(TIMER1_COMPA_vect)
{
   OCR1A += HALF_PERIOD_A;
   PORTD ^= 1<<PIND7;
}

ISR(TIMER1_COMPB_vect)
{
   OCR1B += HALF_PERIOD_B;
   PORTD ^= 1<<PIND6;
}

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Steve also presents the "leapfrog" method. In a real application, I'd suggest using the actual OC output pins and let the timer do the work of changing the pin(s). This will eliminate jitter (within reason) when the interrupt cannot be immediately serviced.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

i still dont get it, i have same question, and i used code like that, because i though it's simple if i want to change the OCR1x value on the fly, rather than used static data to compare it.

what i don't get is

Quote:
Sure, as TCNT1 passes 0x061A on the way up, it will match with OCR1B and trigger that interrupt but all that means is that the LED switching points are out of phase - but they are still at the same frequency set by OCR1A.

can explain in more simple english, because im bad in english.

and why used

ISR(TIMER1_COMPA_vect) 
{ 
   OCR1A += HALF_PERIOD_A; 
   PORTD ^= 1<<PIND7; 
} 

ISR(TIMER1_COMPB_vect) 
{ 
   OCR1B += HALF_PERIOD_B; 
   PORTD ^= 1<<PIND6; 
}

instead using this

ISR(TIMER1_COMPA_vect) 
{ 
   PORTD ^= 1<<PIND7; 
} 

ISR(TIMER1_COMPB_vect) 
{ 
   PORTD ^= 1<<PIND6; 
}

ps: sory for my english