Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

PWM for servo control

Status
Not open for further replies.

magvitron

Active Member
Hi Im doing a small project for servo control using the avr mega 16. I am doing a phase correct PWM with timer 2 of the avr. the thing is driving me crazy.
here's the code:
F_CPU = 11.0592Mhz
Code:
void t2_fastpwm_init()	// initialization for Phase Correct PWM signal using timer 2
{		
	// WGM2[1:0]= 11, for Fast PWM mode
	// COM2[1:0]= 10, to select non inveting mode
	// CS2[2:0] =010. for prescaler=8
   // TCCR1A  = (0<<COM1A0)|(1<<COM1A1)|(0<<COM1B0)|(0<<COM1B1)|(0<<FOC1A)|(0<<FOC1B)|(1<<WGM11)|(0<<WGM10);
	//TCCR1B =  (0<<ICNC1)|(0<<ICES1)|(1<<WGM13)|(1<<WGM12)|(0<<CS12)|(0<<CS11)|(1<<CS10);
	TCCR2=(1<<FOC2)|(0<<WGM20)|(1<<WGM21)|(0<<COM20)|(1<<COM21)|(2<<CS20);
	DDRD|=(1<<PD7);		// selcet OC2 as output pin
	TIMSK|=(1<<OCIE2);	//enable Output compare interrupt
}
how can i get a 50Hz wave for the control. and how can i control that. i have done the same with timer 1 with 1 Mhz Internal clock. but need this odd frequency for a better UART communication. and timer 1 is used as a counter, so I cant access that too. any help?
 
If you use phase-correct or fast-pwm modes, the closest frequency will be 42Hz. To get a 1ms output pulse, OCR2 would be set to 10 and for a 2ms pulse OCR2 would be 21. You will then only have 12 discrete position steps over this range - is this ok?

If you want an exact 50Hz, you can use CTC mode with a prescaler of 1024, OCR2=TOP=215. You then have to use an compare interrupt to create the pulse, during which you set the prescaler to 128 and OCR2 between 172 and 64 for a pulse between 1ms and 2ms. This allows you to have a better resolution of your pulse width; 109 steps rather than the 12 steps above.

Regarding your code:
FOC2 should be 0 for PWM mode.
You have specified CTC, not phase-correct with WGM20/21 = 0/1
 
hi thanks for the reply dougy83.. i modified the code but its not working :(
Code:
//------------------------- 

#include <avr/io.h> 
#include<avr/interrupt.h>
#include<util/delay.h>
int count;
int main(void) { 

//Enable internal pull ups 

PORTD=0xFF; 
sei();
DDRD=0xFF; 
DDRB=0xff;

//TOP=ICR1; 

//ICR1=20000 defines 50Hz PWM 
TCCR2|=(0<<FOC2)|(1<<WGM20)|(0<<WGM21)|(1<<COM20)|(1<<COM21)|(1<<CS20)|(1<<CS21)|(1<<CS22);
	DDRD|=(1<<PD7);		// selcet OC2 as output pin
	TIMSK|=(1<<OCIE2);	//enable Output compare interrupt

//TCCR2|=(0<<FOC2)|(1<<WGM21)|(0<<WGM20)|(0<<COM21)|(0<<COM20)|(1<<CS20)|(1<<CS20)|(1<<CS20);
OCR2=215;
while(1) { 
//OCR2=62;
  
} 
} 

ISR(TIMER2_COMP_vect)
{
TCCR2|=(0<<FOC2)|(1<<WGM20)|(0<<WGM21)|(1<<COM20)|(1<<COM21)|(1<<CS20)|(0<<CS21)|(1<<CS22);
OCR2=172;
//TCCR2|=(
    
    PORTB ^= 0x01; 
    
}
can you help me??!
 
Hi Im doing a small project for servo control using the avr mega 16. I am doing a phase correct PWM with timer 2 of the avr. the thing is driving me crazy.

how can i get a 50Hz wave for the control. and how can i control that. i have done the same with timer 1 with 1 Mhz Internal clock. but need this odd frequency for a better UART communication. and timer 1 is used as a counter, so I cant access that too. any help?

For servo control you don't need accurate 50Hz. The signal can be faster or slower. The important part is the length of the pulse. That is what controls the servo position. I would try fast PWM with prescaler 256.

The pulse rate is ~169Hz.. That might be too fast for your servo. I can show you a trick how to reduce the frequency if this code does not work. Please try it and report back.

Code:
#include <avr/io.h> 

int main(void) { 
	DDRB|=(1<<PB3); // selcet OC0 as output pin

	// Fast PWM, TOP = 255, Prescale = 256, F_CPU = 11.0592Mhz
	TCCR0 |= (0<<FOC0)|(1<<WGM00)|(1<<WGM01)|(0<<COM00)|(1<<COM01)|(0<<CS00)|(0<<CS01)|(1<<CS02);

	// This gives you ~1ms pulse. 43.2 is accurate for 1ms.
	OCR0=43;
	
		while(1) { } 
}

NOTE: This uses Timer/Counter0, The output will be on pin PB3.
 
Last edited:
Removed for complete re-write.
 
Last edited:
It's not really an issue as the interrupt rate is so low, but if you modify the prescaler rather than skipping output pulses for a constant interrupt rate there will be less time wasted in the interrupt. Additionally, a lower prescaler may be used for the actual pulse to provide finer resolution of the pulse width.

NOTE: This uses Timer/Counter0, because there are no interrupts available for Timer/Counter2. The output will be on pin PB3. The interrupt is needed for the trick to remove pulses from the signal.
There are two interrupts for Timer2 - the overflow (vector 5) and compare (vector 4).
 
There are two interrupts for Timer2 - the overflow (vector 5) and compare (vector 4).

You are right.. I got mixed up with all the pages when reading the datasheet. Sorry about that. I really don't know what happened in my head.

If you want an exact 50Hz, you can use CTC mode with a prescaler of 1024, OCR2=TOP=215. You then have to use an compare interrupt to create the pulse, during which you set the prescaler to 128 and OCR2 between 172 and 64 for a pulse between 1ms and 2ms. This allows you to have a better resolution of your pulse width; 109 steps rather than the 12 steps above.
.. if you modify the prescaler rather than skipping output pulses for a constant interrupt rate there will be less time wasted in the interrupt. Additionally, a lower prescaler may be used for the actual pulse to provide finer resolution of the pulse width.

I don't see how your method gives finer resolution. You still have the same limits.. max OCR2 is 255. My flaw was to use the Timer0, which does not have a prescaler of 128.

My method gives more accurate and consistent pulses, because the ISR call overhead does not affect the timing, unlike in your method. My method is also more convenient and safer, because you can modify OCR2 at anytime and anywhere in the code in order to change the pulse width. In your method you can only modify OCR2 when the interrupt is triggered. Which means that you need a global variable to hold the actual PWM value you want (and you need to disable interrupts every time you modify that global).

Here is my code that uses Timer/Counter2.
Code:
#include <avr/io.h> 
#include<avr/interrupt.h>

// Volatile keyword is important
volatile char count=0;

// We use this ISR to leave only every sixth pulse and remove the rest.
// This reduces the PWM frequency to ~56Hz.
ISR(TIMER2_COMP_vect) {
	if (count){ // Disconnect the OC2 to reduce pulse rate
		TCCR2 &= ~(1<<COM21);
		count--;
	}
	else { // Connect the OC2 (PD7) for this pulse
		TCCR2 |= (1<<COM21);
		count=6;
	}
}

int main(void) { 
	DDRD|=(1<<PD7); // selcet OC0 as output pin
	// Fast PWM, TOP = 255, Prescale = 128, F_CPU = 11.0592Mhz
	TCCR2 |= (0<<FOC2)|(1<<WGM20)|(1<<WGM21)|(0<<COM20)|(1<<COM21)|(1<<CS20)|(0<<CS21)|(1<<CS22);
	// This gives you ~1ms pulse. (86.4 is accurate value).
	OCR2=86;
	
	// Setup interrupt so we can remove pulses from the pulse train
	TIMSK |= OCIE2;
	sei();
	
		while(1) { 
		 
		} 
}
 
Last edited:
I don't see how your method gives finer resolution. You still have the same limits.. max OCR2 is 255. My flaw was to use the Timer0, which does not have a prescaler of 128.
My method give more accurate and consistent pulses, because the ISR call overhead does not affect the timing, unlike in your method. My method is also more convenient and safer, because you can modify OCR2 at anytime. In your method you can only modify OCR2 inside the interrupt. Which means that you need a global variable to hold the actual PWM value you want.
It only gives finer resolution if you're using a prescaler of 256. The pulses are just as accurate with either method; the gaps can be less consistent with the method I outlined, but the frequency can be adjusted more readily. I don't see any added safety in accessing OCR2 directly from main code rather than the interrupt. I don't think it's particularly more convenient either.

In summary, either approach is completely usable.

Code:
#include <stdint.h>

#define XTAL 			11059200
#define LOW_PWM		(uint8_t)(0.001 * XTAL / 128 - 0.5)
#define HIGH_PWM	(uint8_t)(0.002 * XTAL / 128 - 0.5)
#define COUNT_50HZ	(uint8_t)(0.020 * XTAL / 1024 - 0.5)

#define setPwmPercent(pc)	do{pwmValue = LOW_PWM + pc * (HIGH_PWM - LOW_PWM) * 5 / 512;}while(0)

volatile uint8_t pwmValue;

void initPwm()
{
	PORTD &= ~(1<<7);							// output defaults to low when PWM disabled
	DDRD |= (1<<7);
	setPwmPercent(50);						// center pulse width
	TCCR2 = (1<<WGM20) | (1<<WGM21) | 			// fast PWM
		(1<<CS22) | (1<<CS21) | (1<<CS20);	// 1:1024 prescaler
	TIMSK |= (1<<OCIE2);
}

ISR(TIMER2_COMP_vect) 
{
	if(TCCR2 & (1<<COM21))		// just finished a pulse?
	{
		TCCR2 = (1<<WGM20) | (1<<WGM21) | 			// fast PWM
			(1<<CS22) | (1<<CS21) | (1<<CS20);	// 1:1024 prescaler
		OCCR2 = COUNT_50HZ;		// interrupt again in 50ms
		TCNT = 255;				// force rollover
	}
	else
	{
		OCR2 = pwmValue;
		TCCR2 = (1<<WGM20) | (1<<WGM21) | 			// fast PWM
			(1<<COM21) |						// non-inverted PWM
			(1<<CS22) | (1<<CS20);				// 1:128 prescaler		
	}	
}

int main()
{
	initPwm();
	
	while(1)
	{
		setPwmPercent(** some value **);
		.. do some other stuff..
	}

	return 0;
}
 
Last edited:
I don't see any added safety in accessing OCR2 directly from main code rather than the interrupt. I don't think it's particularly more convenient either.

The point was that with your method the user can't access the OCR2 directly. If he does that accidentally it messes up the signal. The Update of
OCR2 in CTC mode is immediate. In Fast PWM it is "double buffered".
 
The point was that with your method the user can't access the OCR2 directly. If he does that accidentally it messes up the signal.
Silly users... they can just use the setPwmPercent() function provided then, and not touch things they don't understand.
 
Silly users...

..and the convenience point. Everytime you need to change the pwm in your method, you need to make that operation atomic:

Code:
cli(); //disable interrupts
setPwmPercent(** some value **);
sei(); // enable interrupts
and forgetting to do that is a big safety issue also.. it messes up the signal again.
 
Last edited:
..and the convenience point. Everytime you need to change the pwm in your method, you need to make that operation atomic:
What, in case only 3 of the 8 bits have been copied over? The byte load and store commands are atomic, there's no need for that craziness.
 
In summary, either approach is completely usable.

Here is the summary.

- Both have the same resolution.
- My code produces accurately timed pulses, your pulses have jitter.
- My ISR is simpler and shorter than yours.
- My code produces half the interrupts than yours.
- In my method it is possible to write directly to the OCR2 register to change the pwm.
- My method is (marginally) safer.

Sorry for battling over this.. again I'm little bored here. Waiting for the OP to answer now.
 
>> - Both have the same resolution.
Yes.
>> - My code produces accurately timed pulses, your pulses have jitter.
This is not an issue. Pulses have the same width.
>> - My ISR is simpler and shorter than yours.
There's one extra line in mine... which can be removed. As far as complexity goes, I had no issue understanding either implementation.
>>- My code produces half the interrupts than yours.
That's incorrect. My code has two interrupts per pulse. Yours requires six interrupts per pulse.
>- In my method it is possible to write directly to the OCR2 register to change the pwm.
That's irrelevant.
>>- My method is (marginally) safer.
That's incorrect. Your definition of safety was also kinda retarded: "what if the user changes OCR2". Well, what if "user" sets TCCR2, DDRD, TIMSK or SFIOR incorrectly? Or if they disable interrupts? Or connect the battery backwards?

>>Sorry for battling over this.. again I'm little bored here. Waiting for the OP to answer now.
Haha.. it seems like it. I'm not sure the OP will necessarily appreciate our excessive additions to his thread however.

Oh, not that I'm picking or anything, but your comment "volatile keyword is important" is incorrect. It's superfluous in that context as count is only used in the ISR. It would be required if accessed from both the main ISR and non-ISR code. You could have (and should have) declared count as a static local variable within the ISR if it's not used elsewhere (for "safety reasons" ;P ).
 
Thanks both of you for the replies.. i found both the codes are working.. :) the odd clock frequency messed me up ( i chose that for a better UART error rate) So i have a question, in avr gcc there are many Interrupt vectors. where can i get a list of all interrupt vectors? 'cse I some times get an error misspelled interrupt handler.. or something.
 
So i have a question, in avr gcc there are many Interrupt vectors. where can i get a list of all interrupt vectors? 'cse I some times get an error misspelled interrupt handler.. or something.

That would be the Avr-libc documentation.. which is the library you are using:
https://www.nongnu.org/avr-libc/user-manual/index.html

Library reference:
https://www.nongnu.org/avr-libc/user-manual/modules.html

Direct link to the Interrupts -reference:
https://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html
 
avr simulator version is AVR Simulator: 1, 0, 2, 1 is that an issue?! i use the older avr studio avr studio 4. I kinda want the newer version, is there any new things in that? excluding the newer cores and things how about the editor?
 
avr simulator version is AVR Simulator: 1, 0, 2, 1 is that an issue?! i use the older avr studio avr studio 4. I kinda want the newer version, is there any new things in that? excluding the newer cores and things how about the editor?

I don't think you will encounter any issues.. at least not before you switch to using some newer chips. ATmega16 is not very new chip.

The lates "Atmel Studio 6" is a lot different from the AVR Studio 4.. some like it some don't.
 
in the code:
Code:
#include <avr/io.h> 
#include<avr/interrupt.h>
#include<util/delay.h>

 
// Volatile keyword is important
volatile char count=0;
 
// We use this ISR to leave only every sixth pulse and remove the rest.
// This reduces the PWM frequency to ~56Hz.
ISR(TIMER2_COMP_vect) {
	if (count){ // Disconnect the OC2 to reduce pulse rate
		TCCR2 &= ~(1<<COM21);
		count--;
	}
	else { // Connect the OC2 (PD7) for this pulse
		TCCR2 |= (1<<COM21);
		count=6;
	}
}
 
int main(void) { 
	DDRD|=(1<<PD7); // selcet OC0 as output pin
	// Fast PWM, TOP = 255, Prescale = 128, F_CPU = 11.0592Mhz
	TCCR2 |= (0<<FOC2)|(1<<WGM20)|(1<<WGM21)|(0<<COM20)|(1<<COM21)|(1<<CS20)|(0<<CS21)|(1<<CS22);
	// This gives you ~1ms pulse. (86.4 is accurate value).
	OCR2=86;
 
	// Setup interrupt so we can remove pulses from the pulse train
	TIMSK |= OCIE2;
	sei();
 
		while(1) 
		{ 
		 OCR2=OCR2+10;
		 _delay_ms(200);
         if(OCR2==160)
		 {
           OCR2=86;
		 }
		} 
}
for the servo to move from 0 to 180; which values do i have to write to OCR2?
is the equation for that: f_desired=f_cpu/2N(1+OCR2) ??
 
for the servo to move from 0 to 180; which values do i have to write to OCR2?
is the equation for that: f_desired=f_cpu/2N(1+OCR2) ??

Most servos can take pulses from 0.75ms to 2.25ms, where the shortest pulse is "0 degrees" and the longest is "180 degrees".

In my code the 0.75ms pulse is:
OCR2 = 65;

and the 2.25ms pulse is
OCR2 = 195;

Anything between those values maps linerily between 0...180 degrees.

EDIT:
The equation would be:
OCR2 = 65 + ((130 * degrees) / 180);

Different models of servos behave little differently, so that is only approximately 0 to 180 degrees.
 
Last edited:
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top