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.
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?