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)?
roach:
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_heller:
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](http://www.robotstore.com/download/Servo_Motor_Timing_1.pdf)). The ultrasonic rangefinder sold [[here](http://www.sparkfun.com/commerce/product_info.php?products_id=639) on Sparkfun also outputs data in PWM, as do the [[ADXL accelerometers](http://www.sparkfun.com/commerce/product_info.php?products_id=647), while the [[Dual H-Bridge controller](http://www.sparkfun.com/commerce/product_info.php?products_id=318) 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".](http://www.sparkfun.com/commerce/product_info.php?products_id=318)](http://www.sparkfun.com/commerce/product_info.php?products_id=647)](http://www.sparkfun.com/commerce/product_info.php?products_id=639)](http://www.robotstore.com/download/Servo_Motor_Timing_1.pdf)
leon_heller:
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](http://www.robotstore.com/download/Servo_Motor_Timing_1.pdf)). The ultrasonic rangefinder sold [[here](http://www.sparkfun.com/commerce/product_info.php?products_id=639) on Sparkfun also outputs data in PWM, as do the [[ADXL accelerometers](http://www.sparkfun.com/commerce/product_info.php?products_id=647), while the [[Dual H-Bridge controller](http://www.sparkfun.com/commerce/product_info.php?products_id=318) 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".[/quote]
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.
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.
leon_heller:
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…
/*
* ----------------------------------------------------------------------------
* "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.
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!
transcendentnb2:
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”.
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:
// 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;
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.
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…
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
/*
* ----------------------------------------------------------------------------
* 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);
}