Suggestions on how to code an 8-channel PWM using ATMega16A @8MHz?

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

Hi,

 

I need to generate an 8-channel PWM using ATMega16A. As you know, this MCU has only three timers, and I want to avoid using timer0. So Timer1 and 2 would be needed. I have been doing research on this, but haven't had success so far. I would like to hear your suggestions on this. If you have any ideas o any related reference I could read.  Below you can see what I consider my skeleton for the code. I know I have to use interrupts, but still not sure how to implement them. I plan to output the PWM signals through Port A, that is, PA0 to PA7. I attach a picture for you to have an idea. The application of this is to control several RC Servos at 50 Hz PWM frequecy. I would appreciate your help. Thank you. 

 

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/builtins.h>
#include <util/atomic.h>
#include <stdint.h>

void system_init()
{
	// TO DO: Init peripherals
}

uint16_t pwm[8];
//Your variables

//Your interrupt handlers
ISR (...)
{

}
 ISR (...)
 {

 }

//Change pwm values
void set_pwm(uint16_t pwm0, uint16_t pwm1, uint16_t pwm2, uint16_t pwm3, uint16_t pwm4, uint16_t pwm5,
             uint16_t pwm6, uint16_t pwm7)
			 {
				 ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
				 {
					pwm[0]=pwm0;  pwm[1]=pwm1;
					pwm[2]=pwm2;  pwm[3]=pwm3;
					pwm[4]=pwm4;  pwm[5]=pwm5;
					pwm[6]=pwm6;  pwm[7]=pwm7;
				 }
			 }

int main(void)
{
	//Int system
	system_init();

	//Main loop
	while (1)
	{
		set_pwm(1500, 2000, 1000, 1700, 1900, 1100, 1800, 1200);
		__builtin_avr_delay_cycles(8000000);

		set_pwm(1900, 1100, 1800, 1200, 1500, 2000, 1000, 1700);
		__builtin_avr_delay_cycles(8000000);
	}
}

 

 

 

Attachment(s): 

-.Electronics is Science.-

Last Edited: Thu. Dec 7, 2017 - 02:38 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

check out the project section and search for PWM, for example projects with 8 software pwm channels, also search google for AVR 136 application note.

 

Jim

 

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

Looking into references, I found this code which I am trying to understand. What do you think of it?

 

I have a question: What if I don't set a value for OCR1A at the beginning? In the code below, OCR1A is set inside the interruption. 

 

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/builtins.h>
#include <util/atomic.h>
#include <stdint.h>

void system_init()
{
	cli();         //Deshabilito todas las interrupciones 
	DDRA=0xFF;
	TCCR1A |= 1<<WGM11 | 1<<COM1A1; //modo rápido PWM y salida no invertida, ICR1 top
	TCCR1B |= 1<<WGM12 | 1<<WGM13 | 1<<CS11;  //modo rápido PWM y preescalador de 8
	ICR1=19999; //configurar ICR1 para obtener una frecuencia de 50Hz (8000000 / 8 / 50)-1 = 19999
	sei(); //Habilito interrupciones globales 
}

uint16_t pwm[8];
unsigned volatile char z;
//Your variables


//Your interrupt handlers
ISR (TIMER1_COMPA_vect)
{
	PORTA &= ~(1<<z);		//Turn off PA0
	z++;                    //Increment Servo Channel
	if (z > 7) 
	z = 0;
	OCR1A = pwm[z];      //Update PWM duty for next Servo Channel
	
}
 ISR (TIMER1_OVF_vect)
 {
	 PORTA |= (1<<z);		//Turn on Servo Channel (s)
 }

//Change pwm values
void set_pwm(uint16_t pwm0, uint16_t pwm1, uint16_t pwm2, uint16_t pwm3, uint16_t pwm4, uint16_t pwm5, 
             uint16_t pwm6, uint16_t pwm7)
			 {
				 ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
				 {
					pwm[0]=pwm0;  pwm[1]=pwm1; 
					pwm[2]=pwm2;  pwm[3]=pwm3; 
					pwm[4]=pwm4;  pwm[5]=pwm5; 
					pwm[6]=pwm6;  pwm[7]=pwm7; 
				 }
			 }

int main(void)
{
	//Int system
	system_init();
	
	//Main loop
	while (1)
	{
		set_pwm(1500, 2000, 1000, 1700, 1900, 1100, 1800, 1200);
		__builtin_avr_delay_cycles(8000000);
		
		set_pwm(1900, 1100, 1800, 1200, 1500, 2000, 1000, 1700);
		__builtin_avr_delay_cycles(8000000);
	}
}

 

-.Electronics is Science.-

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

It doesn’t quite work like your diagram does it?

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

I changed some parameters like the ICR1 value to obtain a 50 Hz frequency. But that's what I am trying to find out if the changes that I made work. So I am not sure. I think it might work.

-.Electronics is Science.-

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

Why did you change ICR1?. The repetition frequency will now be 50/8! ICR1 needs to be set for the maximum pulse width - actually a little more. 20ms/8

The way it works is:
Compare isr: clear the current pulse output. Load next pulse width.
Ovf isr: timer is now =0. Set new pulse output.
Repeat.....

Last Edited: Thu. Dec 7, 2017 - 07:41 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm confused when you say "The repetition frequency will now be 50/8! ICR1 needs to be set for the maximum pulse width". What do you mean exactly? It means that if I set ICR1=19999 , is it wrong? 

-.Electronics is Science.-

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

You need to send out 8 pulses every 50Hz (20ms). Yes, your value of 19999 is wrong. Try it and see. You can use your PCs sound port as an oscilloscope

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

So, 8 pulses every 50 Hz is 400 Hz. In that case I think I should remove the prescaler 8, so (8000000/400) -1 = 19999, in that case ICR1 can be 19999, right?

-.Electronics is Science.-

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

I’d simply change the value to 2499.

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

So, the process of removing the prescaler is correct, right? Why would you add simply 2499? Is that because 2499 would be my max pulse width? 19999 is still wrong? 

Thank you so much for your help. I appreciate it. I will continue studying this topic, I'm not an advanced AVR programmer, so this has been a struggle. 

-.Electronics is Science.-

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

According to the interwebs, min pulse is 1ms, max pulse 2ms. With a prescaler of 8, you get a resolution of 1us. This is far better than the average servo can achieve. With a prescaler of 1 you get a resolution of 125ns. Clearly overkill, but the result is the same. Try both and decide for yourself.

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

That got me a little confused again, but it's okay. I will see what I can do by myself. Thank you so much. 

-.Electronics is Science.-

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

Tell us what your confusion is so we can try our best to explain.

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

XavierPacheco wrote:

That got me a little confused again, but it's okay. I will see what I can do by myself. Thank you so much. 

 

The key to understanding the code is to understand how the timer is working and, importantly, what gets loaded where and when. Read the datasheep and in particular the table headed 'Waveform Generation Mode Bit Description'.

'This forum helps those who help themselves.'

 

pragmatic  adjective dealing with things sensibly and realistically in a way that is based on practical rather than theoretical consideration.

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

Kartman wrote:
Tell us what your confusion is so we can try our best to explain.

 

Still not clear about the value of ICR1 I have to load for my application. Previously, I was able to get a single PWM @50Hz changing the pulse width from 800uS to 2200us and it worked well. But with this issue of having 8 channels, I have found it complicated. 

-.Electronics is Science.-

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

It helps if you draw yourself a timing diagram. It is hard to juggle many things in your mind at once.
As for the icr1 value either 19999 with a prescale of 1 or 2499 with a prescale of 8. Look at your timing diagram you posted earlier as this shows the timing sequence. The timer ovf is the start of the pulse. The compare is the end of the pulse. The icr1 value sets the time between pulses at 2.5ms. 8 times 2.5ms is 20ms or 50Hz.

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

Kartman wrote:
It helps if you draw yourself a timing diagram. It is hard to juggle many things in your mind at once. As for the icr1 value either 19999 with a prescale of 1 or 2499 with a prescale of 8. Look at your timing diagram you posted earlier as this shows the timing sequence. The timer ovf is the start of the pulse. The compare is the end of the pulse. The icr1 value sets the time between pulses at 2.5ms. 8 times 2.5ms is 20ms or 50Hz.

 

Now I'm getting the idea. In my case I chose 19999 with a prescale of 1. I understood that OVF is the start of the pulse and compare the end. 

 

So, I think that it should work now. I will see if I can draw the time diagram. I suppose now my signals should be present through PA0-PA7.

 

ISR (TIMER1_COMPA_vect)
{
	PORTA &= ~(1<<z);		//Turn off PA0
	z++;                    //Increment Servo Channel
	if (z > 7) 
	z = 0;
	OCR1A = pwm[z];      //Update PWM duty for next Servo Channel
	
}
 ISR (TIMER1_OVF_vect)
 {
	 PORTA |= (1<<z);		//Turn on Servo Channel (s)
 }

 

-.Electronics is Science.-

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

I just compiled the program, it does not throw errors, but when I write it into the MCU, it does nothing. So I will go over the whole code again. 

 

EDITED: The problem was that interrupts were disabled. Now it does something.

-.Electronics is Science.-

Last Edited: Thu. Dec 7, 2017 - 01:06 PM