PWM on AVR?

Your source for all things Atmel.

Moderator: phalanx

Post Reply
User avatar
roach
Posts: 496
Joined: Mon Oct 17, 2005 11:45 am
Location: Montreal, CA

PWM on AVR?

Post by roach » Mon Mar 13, 2006 9:43 am

A number of "semi-intelligent" modules (servos, motor controllers, GPS modules, sensors, etc) use PWM signals, either to communicate values to the uC, or to receive commands from the uC.

While I understand the principle behind PWM, I'm not sure how to implement a PWM-type communication channel on an AVR. Can anyone point me at some goood example code (preferably in C)?

thanks,

User avatar
leon_heller
Support Volunteer
Posts: 5734
Joined: Sun May 01, 2005 11:20 am
Location: St. Leonards-on-Sea, E. Sussex, UK.

Re: PWM on AVR?

Post by leon_heller » Mon Mar 13, 2006 9:47 am

roach wrote:A number of "semi-intelligent" modules (servos, motor controllers, GPS modules, sensors, etc) use PWM signals, either to communicate values to the uC, or to receive commands from the uC.

While I understand the principle behind PWM, I'm not sure how to implement a PWM-type communication channel on an AVR. Can anyone point me at some goood example code (preferably in C)?

thanks,
PWM is normally used for controlling power, not for communicating data. Some AVRs have PWM modules, they are quite easy to use.

Leon

User avatar
roach
Posts: 496
Joined: Mon Oct 17, 2005 11:45 am
Location: Montreal, CA

Re: PWM on AVR?

Post by roach » Mon Mar 13, 2006 11:17 am

leon_heller wrote:PWM is normally used for controlling power, not for communicating data.
Sorry, I'm using the wrong terms here. I don't mean "communicating", like in Serial comms, but more like sending a control signal. All servos I've seen use PWM to communicate the desired "position" of the servo (see here). The ultrasonic rangefinder sold here on Sparkfun also outputs data in PWM, as do the ADXL accelerometers, while the Dual H-Bridge controller takes PWM as a control signal (closer to what you're describing).

Anyways, the point of all this is how to output a PWM signal from an AVR, and how to "read" a PWM signal. I've seen code examples that use Timer1 (for 10-bit PWM) or Timer0 (for 8-bit). But it's really all gibberish to me. Was wondering if anyone had some newbie-type code examples.
Some AVRs have PWM modules, they are quite easy to use.
I see. Define "easy".

User avatar
leon_heller
Support Volunteer
Posts: 5734
Joined: Sun May 01, 2005 11:20 am
Location: St. Leonards-on-Sea, E. Sussex, UK.

Re: PWM on AVR?

Post by leon_heller » Mon Mar 13, 2006 12:39 pm

roach wrote:
leon_heller wrote:PWM is normally used for controlling power, not for communicating data.
Sorry, I'm using the wrong terms here. I don't mean "communicating", like in Serial comms, but more like sending a control signal. All servos I've seen use PWM to communicate the desired "position" of the servo (see here). The ultrasonic rangefinder sold here on Sparkfun also outputs data in PWM, as do the ADXL accelerometers, while the Dual H-Bridge controller takes PWM as a control signal (closer to what you're describing).

Anyways, the point of all this is how to output a PWM signal from an AVR, and how to "read" a PWM signal. I've seen code examples that use Timer1 (for 10-bit PWM) or Timer0 (for 8-bit). But it's really all gibberish to me. Was wondering if anyone had some newbie-type code examples.
Some AVRs have PWM modules, they are quite easy to use.
I see. Define "easy".
Easy if you have some experience of writing microcontroller software.

Getting data from an accelerometer isn't hard, you just measure the pulse width with a timer.

Leon

wiml
Posts: 487
Joined: Tue Feb 08, 2005 11:45 pm
Location: Seattle, WA, USA
Contact:

Post by wiml » Mon Mar 13, 2006 2:13 pm

You can use the counter/timer modules to generate PWM. The data sheet goes into all the gory details, but basically you can set a pin and start a timer, and configure the timer to reset the pin when it reaches a specified value (or when it overflows, etc).

You can also use software interrupts (have the timer cause an interrupt, have the interrupt tweak the pin) but because interrupt latency can vary (e.g. if you have interrupts disabled in a critical section) this can cause the width of the output pulse to jitter a bit, and that's usually bad.

For receiving PWM, you can presumably use one of the input capture pins --- these can be configured so that when an input pin changes state, it copies the current value of a timer into a capture register (and generates an input). Then your interrupt handler can tell how wide the PWM pulse was by reading the capture register.

Hope this helps :)

User avatar
roach
Posts: 496
Joined: Mon Oct 17, 2005 11:45 am
Location: Montreal, CA

Post by roach » Mon Mar 13, 2006 9:07 pm

leon_heller wrote:Easy if you have some experience of writing microcontroller software.
Ah, THAT's what I must be doing wrong. Thanks for opening my eyes. :roll:

I tried a little "hello world" with PWM, using a sample project direct from the AVR-GCC homepage. I've pretty much got it up and running, though there are some bits that don't work as they should...

Code: Select all

/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <joerg@FreeBSD.ORG> wrote this file.  As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return.        Joerg Wunsch
* ----------------------------------------------------------------------------
*
* Simple AVR demonstration.  Controls a LED that can be directly
* connected from OC1/OC1A (this is PB5 on mega128) to GND.  The brightness of the LED is
* controlled with the PWM.  After each period of the PWM, the PWM
* value is either incremented or decremented, that's all.
*
* $Id: demo.c,v 1.6.2.3 2006/01/05 21:33:08 joerg_wunsch Exp $
*/

#define F_CPU 16000000UL	//CPU-freq
#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

enum { UP, DOWN };

ISR (TIMER1_OVF_vect)		// timer1 overflow interrupt
{
	/* on the atmega128, the clocksource for timer1 (TCCR1B) is set to use the full clock, 
	* in my case 16MHz. As a 10-bit timer, this interrupt should fire 
	* every (2^10) / 16000000 = 0.064 milliseconds (I think). 
	* Therefore, the LED should "ramp" from 0 to full in
	* 0.064 * (2^10) = roughly 65.5 milliseconds.*/
	
	static uint16_t pwm;			//PWM is being used in 10-bit mode, so we need a 16-bit value here (?).
	static uint8_t direction;
	static uint16_t slow_down;
	
	if(++slow_down == 0xff){		//full clock is too fast for this, so we'll slow it down bit.
		slow_down=0;
		switch (direction)		
		{
			case UP:
				if (++pwm == 0x3ff)		//according to the AVR-GCC homepage, this should work 
										//correctly. However, OCR1A seems to be overflowing at around 0x10
					direction = DOWN;	//switch direction
				break;

			case DOWN:	
				if (--pwm == 0)
					direction = UP;		//switch direction
				break;
		}
	}

	//PROBLEM: OCR1A seems to be overflowing around 0x10. 
	//PB5 ramps up to full voltage, then goes dark and starts over.
	OCR1A = pwm;						//because this is an interrupt routine, it is safe to use 16-bit assignment.
}

void ioinit (void)			//initialize pwm and enable interrupts.
{

	TCCR1A = _BV(WGM10) | _BV(WGM11);	//Timer 1 is Phase-correct 10-bit PWM.
	TCCR1A |= _BV(COM1A1);				//Clear OC1A on compare match when up-counting, set OC1A on compare match when down-counting.

	TCCR1B = _BV(CS10);					// full XTAL, no prescalar

	// Set PWM value to 0. 
	OCR1A = 0;

	// Enable OC1 (PB5 on m128) as output. 
	DDRB = _BV (PB5);	//0x20

	// Enable timer 1 overflow interrupt. 
	TIMSK = _BV (TOIE1);

	//enable global interrupts
	sei ();
}

int main (void)
{

	ioinit ();

	// loop forever, the interrupts are doing the rest 

	for (;;)			
	sleep_mode();

	return (0);
}
Problems:
- according to the notes at http://www.nongnu.org/avr-libc/user-man ... oject.html, OCR1A should increment smoothly from 0 to 0x3FF (2^10 - 1), and back down again. However, the LED goes dark and ramps up again at around 0x10. In the AVR Studio simulator, OCR1A seems to be incrementing correctly, the way it should.
- I had to fudge in a "slow-down" factor, since the interrupt was firing every 0.064 milliseconds. From what I can tell, adding a prescalar only speeds up the frequency at which the interrupt fires. Is there any way to slow it down? The original project used a 4MHz xtal, while I'm using 16, but, even taking this into account, the interrupt definitely fires more often than the project page would seem to indicate.

Can anyone tell me what I'm doing wrong?

transcendentnb2
Posts: 54
Joined: Tue Aug 16, 2005 6:05 pm

Post by transcendentnb2 » Mon Mar 13, 2006 9:26 pm

Most common way to slow down a counter is to make your own loop out of it. Change the cycle time of the counter to whatever you want, and make it first run a loop as like a multiplier. Have it count to whatever X you want until you get your desired time delay. This way, the ISR has to be called X times before it is run and the counter is reset. If your variable overflows, then just make another level of looping!


Make sense?

User avatar
roach
Posts: 496
Joined: Mon Oct 17, 2005 11:45 am
Location: Montreal, CA

Post by roach » Mon Mar 13, 2006 9:34 pm

transcendentnb2 wrote:Most common way to slow down a counter is to make your own loop out of it. Change the cycle time of the counter to whatever you want, and make it first run a loop as like a multiplier. Have it count to whatever X you want until you get your desired time delay. This way, the ISR has to be called X times before it is run and the counter is reset. If your variable overflows, then just make another level of looping!


Make sense?
Yep, and this is what I did in the above code. The problem is not "my variable is overflowing, what do I do?", but more "The observed phenomena (the LED) does not correspond with what I know should be happening (the code and AVR studio simulation)".

The LED (on PB5/OC1) "should be" ramping smoothly from, say, 0 to 1023 (being a scale from GND to VLOG), and back down again.

Actually, the LED seems to be "stepping" in (I think) 10 or 12 discreet values, from 0 to something-or-other, then starting over from 0. Makes me think that OC1 should be able to handle, say 10 bits, but can only handle 5, or something. Hence the (probably incorrect) use of the word "overflow".

Lajon
Posts: 50
Joined: Fri Feb 25, 2005 2:22 am

Post by Lajon » Tue Mar 14, 2006 11:13 am

Not sure what you are seeing exactly but note that your slowdown may be a bit much. The actual PWM frequency is, I belive (looking at the data sheet), 16000000 / (2 * 1023) or about 7,8KHz. One cycle of pwm updates (counting up and then down) would be 2048 times slower or about 3.8Hz. Now you slow that down 256 times - it will be cycling every 67 seconds.
From what I can tell, adding a prescalar only speeds up the frequency at which the interrupt fires.
No a prescaler will slow things down.

Regarding other PWM uses, have a look at the data sheet and "Table 61. Waveform Generation Mode Bit Description". The most flexible modes are the ones where TOP can be specified. An example would be RC hobby servo outputs. Using a 16-bit timer in mode 14 you can generate servo outputs without needing any interrupts (using both 16-bit timers you have 6 outputs on a m128). The ICRn register sets the frequency (should be about 50Hz for servos) and the OCRnA, OCRnB and OCRnC registers directly specify the timing for 3 servos:

Code: Select all

   // Setup not shown here:
   // DDR for the OCR1A,B,C pins
   // timer 1 mode 14 with (e.g.) /8 prescaler
   // From Table 59. Compare Output Mode, Fast PWM  use
   //    Clear OCnA/OCnB/OCnC on compare match, set 
   //    OCnA/OCnB/OCnC at TOP
   //
   // Values here for 16Mhz clock and /8 prescaler
   ICR1  = 20000*2;  // 20ms frame time
   OCR1A = 1500*2;  // center 1.5ms
   OCR1B = 1000*2;    
   OCR1C = 2000*2; 
/Lars

User avatar
roach
Posts: 496
Joined: Mon Oct 17, 2005 11:45 am
Location: Montreal, CA

Post by roach » Tue Mar 14, 2006 11:39 am

Thanks, Lars. After scouring the datasheet and AVRBeginners.net, I've almost got it figured out. I ended up using an 8-bit prescalar and fast 8-bit PWM, clearing OSC1 on match and setting it on re-start. I also removed the "slow-down" completely. Made all the code changes, but I'll have to wait until tonight to actually try it out. I still don't understand why OSC1 was "overflowing", though. It seemd to ramp up from zero to some non-MAX value, then start over at zero. Of course, I inferred this from watching the LED. In AVR studio, it all simulated perfectly. Maybe moving from 10 to 8-bits will help...

Also, seeing your example servo control code just set off a little light-bulb in my head. Might use it in the near future to control a pan-tilt head.

thanks!

JC
Posts: 25
Joined: Thu Mar 03, 2005 10:42 pm

Post by JC » Sun Apr 02, 2006 10:49 am

Don't forget about the hardware pwm's. Good for servos, set it and forget it. No interrupt to mess with.


scroll to "Servo control with timer1"

http://members.shaw.ca/climber/avrtimers.html

User avatar
roach
Posts: 496
Joined: Mon Oct 17, 2005 11:45 am
Location: Montreal, CA

Post by roach » Mon Apr 03, 2006 9:44 am

Hi JC,

This is eventually what I ended up doing. Now, however, I'm working on controlling up to 16 servos, and with only 2 timers to work with, it looks like I'll have to do this in software...

Caffeine
Posts: 150
Joined: Thu Feb 17, 2005 2:36 pm

Post by Caffeine » Tue Apr 25, 2006 3:39 am

In answer to one of your other questions, here's some simple code which calculates the acceleration for the ADXL output. I have my accelerometer setup so that 1us = 0.001g, and the AVR setup so the timer runs at 1MHz

Code: Select all

/*
* ----------------------------------------------------------------------------
* Simple ADXL reading, connected to ICP pin
* ----------------------------------------------------------------------------
*/

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>
#include "delay.h"

volatile int up = 0x01;
volatile uint16_t duration;
volatile int GForce;
int ZEROVALUE = 3789;

SIGNAL (SIG_OVERFLOW1) //Timer has overflowed - takes 8ms
{
	// Shouldn't happen in the range the ADXL is spitting out
}

SIGNAL (SIG_INPUT_CAPTURE1 ) // Rising edge detected
{
	if(up ==1)
	{
		TCCR1B =  _BV (CS11);	// 1/8 Prescaler, rising edge detected
		up = 0;
		TCNT1 = 0;
	}
	else
	{
		TCCR1B =  _BV (CS11) | _BV (ICES1);	// 1/8 Prescaler, falling edge detected
		up = 1;
		duration = TCNT1;
	}

}

void ioinit (void) 
{
	// Timer 1 is setup at 1/8 prescaler, with input capture enabled.
	TCCR1B =  _BV (CS11) | _BV (ICES1) | _BV (ICNC1);	// 1/8 Prescaler, input capture + noise cancelling

	timer_enable_int (_BV (TOIE1) | _BV (TICIE1)); // enable timer 1

	// enable interrupts 
	sei ();
}

int main (void)
{
	ioinit();
	PORTB = 0xFF;

	/* loop forever, the interrupts are doing the rest */

	while(1)
	{
		if(duration < 5000)					// Only if the value has changed
		{
			GForce = duration - ZEROVALUE;	// Zero value is the value the ADXL is putting out at 0g
											// Ideally this should be set through some sort of calibration sequence
			duration = 5000;				// Using a ADXL202, the acceleration should never read this high, 
											// so it's a good invalid value to indicate whether the value has changed
		}
	}
	return (0);
}

feluma
Posts: 6
Joined: Tue Sep 29, 2009 8:51 am

Post by feluma » Tue Oct 13, 2009 12:04 pm

Caffeine,

I was trying to use your code with the ATMega328 changing the registers names, but I'm having problems with the SIGNAL instructions.

Could you explain me where these are defined and how work?

Thanks,

Feluma

Liencouer
Posts: 155
Joined: Fri Mar 28, 2008 7:36 am

Post by Liencouer » Wed Dec 02, 2009 10:14 am

a bit of necromancy, dont judge.

the SIGNAL() macro has been replaced with the ISR() macro in GCC.

http://www.nongnu.org/avr-libc/user-man ... rupts.html

Post Reply