Enrico Rossi

Avr atmega16 fast pwm

This is a sample code to demonstrate how to generate a fast pwm wave with atmel atmega16 micro controller and the AVR LibC distributed in Debian 6.0 aka squeeze.

A basic knowledge of the counter in atmel cpu is required, for more info see the datasheet downloadable from atmel web site.

Let’s begin with the problem: I have a 4 Mhz micro controller and I want to create a 500 hz wave with variable PWM duty cycle. I use the 16 bit counter_1 because It is the only one I can set the frequency and the PWM duty cycle. The other 2 counters can be used in fast pwm too, but the frequency have to be related to the clock speed and the prescaler factor, not the one I freely choose.

The steps I follow are:

  • Prepare the port with counter pins out

  • Prepare the prescaler and choose the TOP value to obtain a 500hz wave. With an 4Mhz clock prescaled by 8 I have:

    nstep = (4000000hz / 8) / 500hz = 1000

    The counter start from BOTTOM (0) and ends after 1000 steps (TOP), then generate and interrupt (overflow) in which I change the duty cycle level, and restart again from BOTTOM.

  • Set the level of the PWM toggle between BOTTOM and TOP, I’ve used from 5% (50) to 95% (950).

  • enable the interrupt routine, start the counter and then do an infinite loop.

/* Copyright (C) 2010 Enrico Rossi
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include <stdint.h>
#include <stdlib.h>
#include <avr/interrupt.h>
#include <avr/io.h>

/* from 50 to 950 */
uint16_t duty_cycle;

/* IRQ routine at 500hz this is called 500 times x second */
	/* if the duty is at 95% the restart from 5% */
	if (duty_cycle > 950)
		duty_cycle = 50;

	/* increment the duty cycle by 1 step at the time wich is
	   0.1%, in this way I will complete 100% in 2 seconds (1000 step) */

	/* set the level where the output pin OC1A will toggle */
	OCR1A = duty_cycle;

	/* set the level where the output pin OC1B will toggle
	   this will be the inverse of OC1A level */
	OCR1B = 1000 - duty_cycle;

void counter_setup(void)
	/* Pin OC1A & B set to output */
	DDRD |= _BV(PD4) | _BV(PD5);

	/* Clear OC1A & B on compare match, set it at BOTTOM.
	   Waveform generation mode on 14, see datasheet */
	TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM11);
	TCCR1B = _BV(WGM13) | _BV(WGM12);

	/* TOP value*/
	ICR1 = 1000;

	/* start with a duty of 50% */
	OCR1A = 500;
	OCR1B = 500;

	/* enable interrupt on timer overflow */

int main(void)
	/* prepare everything */

	/* start with a duty cycle of 50% */
	duty_cycle = 500;

	/* enable interrupt */

	/* start the counter with prescaler = 8 */
	TCCR1B |= _BV(CS11);

	/* Infinite loop doing nothing, everything is
	 handled by the IRQ routine called 500 times x second. */
	for (;;);

	/* just for correct looking code, disable irq and terminate */

compile with:

avr-gcc -I/usr/avr/include/ -Wall -Wstrict-prototypes -pedantic -mmcu=atmega16 -O2 -D F_CPU=4000000UL -o pwm.elf main.c
avr-objcopy -j .text -j .data -O ihex pwm.elf pwm.hex

and if you have a stk500 connected to the ttyUSB0 for example, write the code with

avrdude -c stk500v1 -p atmega16 -P /dev/ttyUSB0 -e -U flash:w:pwm.hex

Attaching an stk500 development board’s led 0 and 1 to the OC1A and OC1B pin, the two led should alternatively fade on and off. Or in alternatives, with a scope like DSO Nano the waveform can be watched changing the pwm’s duty cycle.