Here are some code snippets from a couple of my projects that show how to use PWM on the AVRs. This will give you an idea of what you're in for. Notice how it's a bit lower level than the Arduino library but you'll have much more control. You'll understand what all those oddly named variables are once you read the datasheet.
This is from an AVR ATmega324p project. PWM was used to sound a tone on a piezo buzzer...
void SetupTimer1()
{
// Timer1, which is a 16-bit timer, will operate in 'phase correct pwm' mode
// (mode 11, specifically.) The timer will have the 8 MHz clock as its input
// and will not be prescaled. Output compare pin OC1A will be controlled by
// this timer. This pin is connected to one of the piezo buzzer's pins. (The
// other piezo buzzer pin is connected directly to ground.) The buzzer can
// be set to a specific frequency by setting this timer's OCR1A (aka TOP)
// register.
//
// The buzzer's frequency and OCR1A (TOP) are calculated as follows:
//
// f = 2,000,000 / OCR1A
//
// OCR1A = 2,000,000 / f
//
// It is important that PD5 (OC1A) is set low when the buzzer is off. This
// prevents the buzzer from being driven with a DC voltage--which would be
// the case if the pin were set high.
// The timer will be setup so that it is explictly disabled...
OCR1A = 0;
TCCR1A = 0; // No clock source. Timer is stopped.
TCCR1B = 0;
TIMSK1 = 0; // No ISRs are used by this timer.
PORTD &= (uint8_t)~_BV(PD5); // Make sure the OC1A pin is set low.
}
inline void SoundTone(uint16_t OCR1A_Value)
{
OCR1A = OCR1A_Value;
// COM1A1:0 = 01 (Toggle OC1A on compare match.)
// COM1B1:0 = 00 (Normal port operation for OC1B, OC1B disconnected.)
// WGM13:0 = 1011 (Waveform generation mode 11, PWM phase correct. TOP is OCR1A.)
// CS12:0 = 001 (Clock source is CLKi/o / 1. No prescaling.)
TCCR1A = (uint8_t)(_BV(COM1A0) | _BV(WGM11) | _BV(WGM10));
TCCR1B = (uint8_t)(_BV(WGM13) | _BV(CS10));
}
inline void SilenceTone()
{ // Currently, this is the same as DisableTimer1().
TCCR1A &= (uint8_t)~(_BV(COM1A1) | _BV(COM1A0)); // Disconnect OC1A from the pin.
TCCR1B &= (uint8_t)~(_BV(CS12) | _BV(CS11) | _BV(CS10)); // No clock source. Timer is stopped.
PORTD &= (uint8_t)~_BV(PD5); // Make sure the OC1A pin is set low.
}
Here's another example. This time I made the code a little more modular and created a C++ PWM class. The methods are static (class methods) since the class is essentially a singleton and calling object methods have extra overhead. This is from an AVR ATmega324p project as well.
File: PWM.h...
#ifndef _PWM_H_
#define _PMW_H_
#include <avr/io.h>
class PWM
{
public:
static void Init();
static inline void SetCurrentDutyCycle(int8_t NewDutyCycle);
static int8_t GetCurrentDutyCycle() { return OCR0B; }
};
inline void PWM::SetCurrentDutyCycle(int8_t NewDutyCycle)
{
OCR0B = (NewDutyCycle > 100) ? 100 : ((NewDutyCycle < 0) ? 0 : NewDutyCycle);
}
#endif // __PWM_H_
File PWM.cpp...
#include "PWM.h"
void PWM::Init()
{
OCR0A = 100; // OCR0A is TOP.
OCR0B = 100; // 100% (high) width.
DDRB |= _BV(PB4); // Pin 4 of port b is OC0B. Set it as output.
TCCR0A = _BV(COM0B1) | _BV(WGM00); // Phase correct PWM (mode 5, TOP is OCR0A.) Non-inverting mode.
TCCR0B = _BV(WGM02) | _BV(CS01); // Prescale by 8.
}