This note briefly describes how to set up PWM on an ATmega32. In this instance, Timer/Counter 1 (16-bits wide) is used to set up a Fast PWM-mode PWM wave to drive a typical R/C servo. The PWM period is 20 ms (50 Hz), and the pulse width varies from 0.9 ms to 2.1 ms.
With a 16 MHz clock, the full 16-bit count wraps around in about 780 µs. Since we need a PWM period of 20 ms, this is clearly too fast. Prescaling Timer/Counter 1 by dividing the sytem clock by 64 gives us a full-count period of 50 ms. By setting the upper limit of the count using ICR1 to 12500, this results in the desired 20 ms period.
Update: I’ve gotten some math wrong in the preceding paragraph, and I'm not sure what I was thinking. It seems that a full 16-bit count would take 4.096 ms at 16 MHz. Also, the comments in the code regarding prescaling values don’t all agree. Sorry about that. I hope you can still find this useful.
To get the appropriate servo pulse width between 0.9 ms and 2.1 ms, the OCR1A register is loaded with values between 225 and 525.
Circuit Set-up
Please excuse the lack of a schematic; I simply don’t have time to create one right now.
The ATmega32 is set up to run with a 16-MHz crystal oscillator. On the PDIP 40 package, the following pins are connected:
Pin | Description | Connections & Use |
---|---|---|
6 | PB5/MOSI | ISSP |
7 | PB6/MISO | ISSP |
8 | PB6/SCK | ISSP |
9 | Through 10 kΩ resistor to Vcc, N.O. switch to GND, ISSP | |
10 | Vcc | Power, 0.1 µF ceramic cap to GND, ISSP |
11 | GND | Ground, ISSP |
12 | XTAL1 | To 16 MHz crytsal, 10 pF ceramic cap to GND |
13 | XTAL2 | To 16 MHz crytsal, 10 pF ceramic cap to GND |
19 | PD5/OC1A | To Servo signal line |
30 | AVcc | Power, 0.1 µF ceramic cap to GND, ISSP |
31 | GND | Ground, ISSP |
40 | PA0/ADC0 | To LED, LED to 470 Ω resistor, resitor to GND |
Fuse Set-up
The fuses are set for a 16 MHz external crystal oscillator.
Name | Description | Value |
---|---|---|
Fuse Lo | Fuse Low Byte | 11101111b, 0xEF |
Fuse Hi | Fuse High Byte | 10001001b, 0x89 |
Register Set-up
The following registers are set, in this order. Although the order isn’t strictly necessary, some subset of it might be (be specific).
Name | Description | Value |
---|---|---|
DDRA | Port A Direction | 00000001b, 0x01 (PA0 output, rest input) |
DDRA | Port B Direction | 00000000b, 0x00 (all input) |
DDRD | Port D Direction | 00110000b, 0x30 (PD4, PD5 output, rest input) |
TCCR1A | Timer/Counter 1 Configuration A | 10100010b, 0xA2 (OC1A & OC1B set at BOTTOM, clear on compare match, WGM=14 [fast PWM, ICR1 is TOP]) |
TCCR1B | Timer/Counter 1 Configuration B | 00011011b, 0x1B (prescale ClkI/O/64) |
ICR1 | In Fast PWM mode, becomes TOP | 12500d (20 ms PWM period with ClkI/O/64 prescale) |
Code
#include <inttypes.h> #include <avr/io.h> #include <avr/interrupt.h> #include <avr/signal.h> #define kPinInitCompleteLED         PA0 #define kDelay                      10000 #define kReverseDelay               300000 #define kLowerLimit                300                 //  Min 225 == 0.9 ms #define kUpperLimit                450                 //  Max 525 == 2.1 ms int main() {     volatile long       d;     short               dir = 1;         //  Set LED output pins…         DDRA = _BV(kPinInitCompleteLED);     DDRB = 0;                                           //  Port B inputs         //  Set up OCR pins (PD4, PD5) as outputs (00110000)…         DDRD = _BV(PD4) | _BV(PD5);         //  Set up Timer 1. Timer 1 should reset when     //  it reaches TOP = ICR1 (WGM13:0 = 1110b). On     //  compare match clear output, at TOP set (COM1A1:0 = 10b).         TCCR1A = _BV(COM1A1) | !_BV(COM1A0)                 //  Both PWM outputs set at TOP,                 | _BV(COM1B1) | !_BV(COM1B0)            //    clear on compare match                 | !_BV(FOC1A) | !_BV(FOC1B)             //  PWM mode, can't force output                 | _BV(WGM11) | !_BV(WGM10);             //  Fast PWM, TOP = ICR1         TCCR1B = !_BV(ICNC1) | !_BV(ICES1)                  //  Disable input capture noise canceler,                                                         //    edge select to negative.                 | _BV(WGM13) | _BV(WGM12)               //  Fast PWM, TOP = ICR1                 | !_BV(CS12) | _BV(CS11) | _BV(CS10);   //  clk(i/o) / 1024         //  PWM duty cycle…         OCR1A = kLowerLimit;     OCR1B = kLowerLimit;         //  PWM period…         ICR1 = 12500;         //  Show initialization complete…         PORTA = _BV(kPinInitCompleteLED);         //  Loop forever steering left-to-right-to-left…         OCR1A = kLowerLimit;            //    Min value == 0.9 ms     while (1)     {         d = kDelay;         while (d--);                 OCR1A += dir;                 if (OCR1A < kLowerLimit)         {             OCR1A = kLowerLimit;             dir = 1;             d = kReverseDelay;             while (d--);         }                 if (OCR1A > kUpperLimit)    //    Max value == 2.1 ms         {             OCR1A = kUpperLimit;             dir = -1;             d = kReverseDelay;             while (d--);         }             }         return 0; }