Using the 16-bit timer on atmega328 to do PWM

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

Hi,

 

I am using timer 1 both to sample using OCR1A overflow interrupt and implement PWM on B2 using OCR1B. The result is a blinking LED and not a dimmed LED. I am doing something wrong.

 

#include <stdint.h>
#include <avr/interrupt.h>

volatile uint8_t timerCount = 1;
ISR(TIMER1_COMPA_vect)
{
    timerCount++;
}

int main(void)
{
    DDRB = DDRB | 1 << DDB2;
    TCCR1A = TCCR1A | 1 << COM1B1;
    TIMSK1 = TIMSK1 | 1 << OCIE1A;
    TCCR1A = (TCCR1A | 1 << WGM10) | 1 << WGM11;
    TCCR1B = (TCCR1B | 1 << WGM13) | 1 << WGM12;
    OCR1A = 15624;
    sei();
    TCCR1B = (TCCR1B | 1 << CS12) | 1 << CS10;
    while( 1)
    {
        if( timerCount >= 1)
        {
            timerCount = 0;
            OCR1B = (2.5 * OCR1A) / 5;
        }
    }
    return 0;
}

 

However, if I move everything over to the 8-bit timer 2 and port D3, things work as expected. The LED is dimmed and not blinking.

 

#include <stdint.h>
#include <avr/interrupt.h>

volatile uint8_t timerCount = 125;
ISR(TIMER2_COMPA_vect)
{
    timerCount++;
}

int main(void)
{
    DDRD = DDRD | 1 << DDD3;
    TCCR2A = TCCR2A | 1 << COM2B1;
    TIMSK2 = TIMSK2 | 1 << OCIE2A;
    TCCR2A = (TCCR2A | 1 << WGM20) | 1 << WGM21;
    TCCR2B = TCCR2B | 1 << WGM22;
    OCR2A = 124;
    sei();
    TCCR2B = ((TCCR2B | 1 << CS22) | 1 << CS21) | 1 << CS20;
    while( 1)
    {
        if( timerCount >= 125)
        {
            timerCount = 0;
            OCR2B = (2.5 * OCR2A) / 5;
        }
    }
    return 0;
}

 

Any suggestions what I am missing. Thanks.

Last Edited: Fri. Feb 12, 2016 - 03:32 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

How fast is the AVR running?

 

How long is one period of timer2?

 

How long is one period of timer1?

 

If the timer2 period is, say, one millisecond then your eye is integrating the on/off and it appears dim.  If the timer1 period is, say, 30ms then indeed your eye will see the individual on/off periods.

 

Dimming?  I see only a 50% duty cycle in your code.

 

LogLog wrote:
OCR2B = (2.5 * OCR2A) / 5;

??? Why do you need to pull floating point into a microcontroller app for a simple /2 ?

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

In both cases the period is 1 second.

 

The 2.5 is just a number I chose to post the question. It could be anything ranging from 0 to 5. 

Last Edited: Fri. Feb 12, 2016 - 02:52 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

LogLog wrote:
In both cases the period is 1 second.

How about answering my first question?

theusch wrote:
How fast is the AVR running?

And the period of an 8-bit timer on an AVR8 will never be one second, unless you are running your AVR at e.g. 1kHz.

 

And of course a 1 second period, 50% duty cycle, will be a blinking LED.

 

LogLog wrote:
It could be anything ranging from 0 to 5.

There is still no need for floating point.  You are manipulating a fraction of a [relatively] small integer number.

 

====================

1)  Tell your AVR clock speed.

2)  Tell what you want to do.

 

Short answer is that you will probably want to run timer1 at /1 with a period faster than 1/60 second for LED dimming.  And remember that the dimming is essentially a log function so your eye may not see much change when low bits are manipulated.

 

 

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

Thanks for making me understand things. In both cases the overall period was 1 second and the AVR clock speed was also the same (16 MHz). However, timer 1 was causing the overflow once every period (and so I saw the LED blinking), but timer 2 was was causing the overflow 125 times every period (and so I was able to see a dimmed LED). I also made a typo in the original code (used 1 instead of 125) which I will now fix.

Last Edited: Fri. Feb 12, 2016 - 03:38 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

LogLog wrote:
In both cases the overall period was 1 second

NO IT IS NOT!!! 

 

Now that we know 16MHz, let's look at the timer2 code first...

 

LogLog wrote:

DDRD = DDRD | 1 << DDD3;

TCCR2A = TCCR2A | 1 << COM2B1;

TIMSK2 = TIMSK2 | 1 << OCIE2A;

TCCR2A = (TCCR2A | 1 << WGM20) | 1 << WGM21;

TCCR2B = TCCR2B | 1 << WGM22;

OCR2A = 124;

sei();

TCCR2B = ((TCCR2B | 1 << CS22) | 1 << CS21) | 1 << CS20;

 

First, a bit on style.  I'm the last person to be an authority, but nearly all C programmers would not do e.g.

TCCR2B = TCCR2B | 1 << WGM22;

but rather

TCCR2B |= 1 << WGM22;

 

My personal preference is to have a single assignment statement for each timer register.  Assignment, not |=.  In a real app that might start over after a noise spike or an uncaught interrupt or similar will not have the I/O registers at the default after-reset values.  So gratuitous | or |= is not as robust, and certainly takes more cycles and code space, than simple assignment.

 

In addition, reading TCCR2B = TCCR2B | 1 << WGM22;  makes me verify C operator precedence -- is << higher precedence than | ?  Yes, it is!  So you are OK--you may have the precedence table memorized by rote.  But consider if you start combining with other  expressions?  Most would add the parentheses TCCR2B = TCCR2B | (1 << WGM22);

 

An sei before the timer is fully set up?

 

[more later]

 

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

Now, decoding all your statements, I come up with the below for timer setup when plugged into the CodeVision Wizard.  [yes PD3 needs to be an output, and the ISR set up, but the ISR has no bearing on this test program...]

// Timer/Counter 2 initialization
// Clock source: System Clock
// Clock value: 15.625 kHz
// Mode: Fast PWM top=OCR2A
// OC2A output: Disconnected
// OC2B output: Non-Inverted PWM
// Timer Period: 8 ms
// Output Pulse(s):
// OC2B Period: 8 ms Width: 4 ms
ASSR=(0<<EXCLK) | (0<<AS2);
TCCR2A=(0<<COM2A1) | (0<<COM2A0) | (1<<COM2B1) | (0<<COM2B0) | (1<<WGM21) | (1<<WGM20);
TCCR2B=(1<<WGM22) | (1<<CS22) | (1<<CS21) | (1<<CS20);
TCNT2=0x00;
OCR2A=0x7C;
OCR2B=0x3E;
...
// Timer/Counter 2 Interrupt(s) initialization
TIMSK2=(0<<OCIE2B) | (1<<OCIE2A) | (0<<TOIE2);

One assignment for each register.  (whether you want to keep the (0 << xxxx) terms or not is again a matter pf preference)  From the comment block, one can see timer mode and etc. without painful datasheet decoding.

 

And we see that the period is actually 8ms, not one second.  And yes, that is fast enough at 125Hz for the eye to see the LED as "dim".

 

[on to timer1 in the next installment...]

 

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

For timer1, it indeed has a period of one second.  And no matter the duty cycle you set, it will not look like a dim LED, but rather a blinking/flashing.

// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: 15.625 kHz
// Mode: Fast PWM top=OCR1A
// OC1A output: Disconnected
// OC1B output: Non-Inverted PWM
// Noise Canceler: Off
// Input Capture on Falling Edge
// Timer Period: 1 s
// Output Pulse(s):
// OC1B Period: 1 s Width: 0.5 s
// Timer1 Overflow Interrupt: Off
// Input Capture Interrupt: Off
// Compare A Match Interrupt: On
// Compare B Match Interrupt: Off
TCCR1A=(0<<COM1A1) | (0<<COM1A0) | (1<<COM1B1) | (0<<COM1B0) | (1<<WGM11) | (1<<WGM10);
TCCR1B=(0<<ICNC1) | (0<<ICES1) | (1<<WGM13) | (1<<WGM12) | (1<<CS12) | (0<<CS11) | (1<<CS10);
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x3D;
OCR1AL=0x08;
OCR1BH=0x1E;
OCR1BL=0x84;

// Timer/Counter 1 Interrupt(s) initialization
TIMSK1=(0<<ICIE1) | (0<<OCIE1B) | (1<<OCIE1A) | (0<<TOIE1);

As you are using the same prescaled clock of 15kHz for each timer, to get the same results you need to set OCR1A to the same value as OCR2A.

 

If you want ~15000 counts in OCR1A, then run at /1 and then there is about a 1 millisecond period, and manipulate OCR1B all you want and you will have a fine dimming LED.  (remember the mention of log earlier -- your eye will only notice changes in the higher bits.)

 

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

Thanks for all the suggestions. I will incorporate them by and by.

 

I would like to know the reasoning behind first enabling sei or turing on the timer. I got that from one of the internet pages/videos, and now I don't remember exactly where.

 

However,  I still maintain that the overall period is 1.

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

Ok. Timer 2 is 8 ms, but I count up to 125 before doing anything, that's why I said that the overall period is 1 second.