ATmega328p - 4 PWM outputs

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

Hello everyone,

I'm recently trying the ATmega328p because of it's PWM capabilities.
I'm trying to obtain 4 PWMs at around 350Hz, for this, I have already used the timer1 with custom TOP (ICR1). I'm trying to do something similar for timer2 but it doesn't seem to work since the TOP is set by OCR2A and OCR2B, but I don't know how to setup the duty of the PWM.

Here's my code, with timer1 PWM working, based on the code on this website: https://sites.google.com/site/qeewiki/books/avr-guide/pwm-on-the-atmega328

#include 


int main(void)
{
	//TIMER1
	
	DDRB |= (1 << DDB1)|(1 << DDB2);
	// PB1 and PB2 is now an output

	ICR1 = 25;
	// set TOP to 180

	OCR1A = 10;
	// duty 177

	OCR1B = 10;
	// duty 177
	
	TCCR1A |= (1 << COM1A1)|(1 << COM1B1);
	// set none-inverting mode

	TCCR1A |= (1 << WGM11);
	TCCR1B |= (1 << WGM12)|(1 << WGM13);
	// set Fast PWM mode using ICR1 as TOP
	
	TCCR1B |= (1 << CS12);
	// 256 presc

	//TIMER2
	
	DDRD |= (1 << DDD3);
	DDRB |= (1 << DDB3);
	// PD3 and PB3 are now outputs
	
	OCR2A = 2;
	OCR2B = 2;
	// TOP

	TCCR2A |= (1 << COM2A1);
	TCCR2B |= (1 << COM2B1);
	// set none-inverting mode

	TCCR2A |= (1 << WGM21) | (1 << WGM20);
	TCCR2B |= (1 << WGM22);
	// set fast PWM Mode

	TCCR2B |= (1 << CS21)| (1 << CS22);
	// set prescaler to 256 and starts PWM 
	
	
while (1);
{
	// we have a working Fast PWM
}

}

Thanks for any help!

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

Perhaps you should dump timer1. Timer1 is a 16-bit timer while timer0 and timer2 are both 8-bit timers. I have successfully utilized timer 0 and 2 to get 4 Fast-PWM outputs from atmega328p. I am using the 4 channels to drive christmas led lights.

Here is my PWM code:

enum PWM_CHANNEL {
	
	// Name by color
	CHAN_RED		= 1,
	CHAN_GREEN		= 1<<1,
	CHAN_YELLOW		= 1<<2,
	CHAN_BLUE		= 1<<3,
	
	// Name by index
	CHAN_1			= 1,
	CHAN_2			= 1<<1,
	CHAN_3			= 1<<2,
	CHAN_4			= 1<<3,
	
	// Name by port
	CHAN_PD6		= 1,
	CHAN_PD5		= 1<<1,
	CHAN_PB3		= 1<<2,
	CHAN_PD3		= 1<<3,
	
	// Name by register
	CHAN_OC0A		= 1,
	CHAN_OC0B		= 1<<1,
	CHAN_OC2A		= 1<<2,
	CHAN_OC2B		= 1<<3
	
};


void pwm_initialize() {
	
	// Set mode of operation to FastPWM
	TCCR0A |= (1<<WGM00 | 1<<WGM01);
	TCCR2A |= (1<<WGM20 | 1<<WGM21);
	
	// Set clock source (prescaler)
	TCCR0B |= (1<<CS01);
	TCCR2B |= (1<<CS21);
	
	// Set to 50% duty cycle
	OCR0A = 0x80;
	OCR0B = 0x80;
	OCR2A = 0x80;
	OCR2B = 0x80;
	
	// 4 PWM channel outputs
	DDRB |= 1<<PB3; // OC2A
	DDRD |= 1<<PD3; // OC2B
	DDRD |= 1<<PD5; // OC0B
	DDRD |= 1<<PD6; // OC0A	
	
}

// Enable PWM channels
void pwm_enable(enum PWM_CHANNEL channel) {
	if (channel & CHAN_OC0A) TCCR0A |= 1<<COM0A1;
	if (channel & CHAN_OC0B) TCCR0A |= 1<<COM0B1;
	if (channel & CHAN_OC2A) TCCR2A |= 1<<COM2A1;
	if (channel & CHAN_OC2B) TCCR2A |= 1<<COM2B1;
}

// Disable PWM channels
void pwm_disable(enum PWM_CHANNEL channel) {
	if (channel & CHAN_OC0A) TCCR0A &= ~(1<<COM0A1);
	if (channel & CHAN_OC0B) TCCR0A &= ~(1<<COM0B1);
	if (channel & CHAN_OC2A) TCCR2A &= ~(1<<COM2A1);
	if (channel & CHAN_OC2B) TCCR2A &= ~(1<<COM2B1);
}

// Sets the channel brightness
void pwm_dutycycle(enum PWM_CHANNEL channel, uint8_t dutycycle) {
	if (channel & CHAN_OC0A) OCR0A = dutycycle;
	if (channel & CHAN_OC0B) OCR0B = dutycycle;
	if (channel & CHAN_OC2A) OCR2A = dutycycle;
	if (channel & CHAN_OC2B) OCR2B = dutycycle;
}

You might want to customize the clock prescaler to get the proper frequency (see the init routine). call pwm_initialize to start the PWM, then pwm_enable and finally use pwm_dutycycle to set the duty cycle (or LED brightness in my case). The "channel" parameter is a bit field, you can use it to update multiple channels simultaneously.

Hope this helps.