Pulse width modulation (PWM) on AVR Studio
Asked Answered
B

2

9

I'm trying to use PWM for an LED on an ATmega8, any pin of port B. Setting up timers has been a annoying, and I don't know what to do with my OCR1A. Here's my code, and I'd love some feedback.

I'm just trying to figure out how use PWM. I know the concept, and OCR1A is supposed to be the fraction of the whole counter time I want the pulse on.

#define F_CPU 1000000  // 1 MHz

#include <avr/io.h>
#include <avr/delay.h>
#include <avr/interrupt.h>

int main(void){

    TCCR1A |= (1 << CS10) | (1 << CS12) | (1 << CS11);
    OCR1A = 0x0000;
    TCCR1A |= ( 0 << WGM11 ) | ( 1 << WGM10 ) | (WGM12 << 1) | (WGM13 << 0);
    TCCR1A |= ( 1 << COM1A0 ) | ( 0 << COM1A1 );
    TIMSK |= (1 << TOIE1); // Enable timer interrupt
    DDRB = 0xFF;
    sei(); // Enable global interrupts
    PORTB = 0b00000000;

    while(1)
    {
        OCR1A = 0x00FF; //I'm trying to get the timer to alternate being on for 100% of the time,
        _delay_ms(200);
        OCR1A = 0x0066; // Then 50%
        _delay_ms(200);
        OCR1A = 0x0000; // Then 0%
        _delay_ms(200);
    }
}

ISR (TIMER1_COMA_vect)  // timer0 overflow interrupt
{
    PORTB =~ PORTB;
}
Belongings answered 28/2, 2012 at 2:20 Comment(3)
Answered, will post it tomorrow (after the 8 hours).Belongings
For a deeper understanding, I recommend reading the datasheet: atmel.com/Images/doc2486.pdfSharenshargel
Don't have much to add to the discussion just small note that toggling a pin is faster if you do PINB = 0xff instead of PORTB = ~PORTBMcguigan
B
6

You need to initialize your OCR1A with these two lines:

TCCR1A = (1 << WGM10) | (1 << COM1A1);
TCCR1B = (1 << CS10) | (1 << WGM12);

And then use this:

OCR1A = in

And know that the range is 0-255. Count your percentages, and there you have it!

#define F_CPU 1000000  // 1 MHz
#include <avr/io.h>
#include <avr/delay.h>
#include <avr/interrupt.h>

int main(void){
    TCCR1A = (1 << WGM10) | (1 << COM1A1);
    TCCR1B = (1 << CS10) | (1 << WGM12);
    DDRB = 0xFF;
    sei(); // Enable global interrupts
    PORTB = 0b00000000;

    while(1)
    {
        OCR1A = 255;
        _delay_ms(200);
        OCR1A = 125;
        _delay_ms(200);
        OCR1A = 0;
        _delay_ms(200);
    }
}
Belongings answered 28/2, 2012 at 21:34 Comment(1)
I would rather poll the timers value or have an interrupt set up to change the compare register`s value.Alitaalitha
S
6

No, this is not the way how you should do a PWM. For example, how do you set a PWM rate of, for example, 42% with it? Also, the code size is big, it can be done in a much more efficient way. Also, you waste a 16 bit timer to do 8 bit operations. You have 2x 8 bit timers (Timer/Counter 0 and 2), and one 16 bit timer, Timer/Counter 1.

It's also a bad idea to set unused portpins to output. All portpins which are not connected to anything, should be left as inputs.

The ATmega8 has a built-in PWM generator on timers 1 and 2, there is no need in simulating it through software. You don't even have to set your ports manually (you only have to set the corresponding portpin to output)

You don't even need any interrupt.

#define fillrate OCR2A 


 //...

 // main()

PORTB=0x00;
DDRB=0x08;  //We use PORTB.3 as output, for OC2A, see the atmega8 reference manual

// Mode: Phase correct PWM top=0xFF
// OC2A output: Non-Inverted PWM
TCCR2A=0x81;
// Set the speed here, it will depend on your clock rate.
TCCR2B=0x02;

// for example, this will alternate between 75% and 42% PWM
while(1)
{
    fillrate = 191; // ca. 75% PWM
    delay_ms(2000);

    fillrate = 107; // ca. 42% PWM
    delay_ms(2000);
}

Note that you can use another LED with another PWM, by using the same timer and setting OCR2B instead of OCR2A. Don't forget to set TCCR2A to enable OCR2B as output for your PWM, as in this example only OCR2A is allowed.

Sharenshargel answered 23/3, 2012 at 7:35 Comment(3)
Of course, if you absolutely need to use Timer1, because your hardware is already complete, you can do the same thing with TCCR1 and OCR1 instead of 2. Of course, I hardcoded the values for the timer initialization to illustrate what bits are set, you can use fancy macros instead.Sharenshargel
Where do you find register TCCR2A? I only find TCCR2 in the datasheet. Also, would like to mention that a much more readable way is to left shift the bits you want to set like this: TCCR2 = (1 << FOC2) | (1 << CS20); which is the same as doing TCCR2 = 0x81; but now you can actually read which bits are setAlitaalitha
Your post made me realise I must have OCR1B greater than 0 to see non-trivial PWM output. Thanks.Ximenes

© 2022 - 2024 — McMap. All rights reserved.