megaAVR Experiments - A PWM example

Yet another example with the ATmega128. This one is a complete application testing the 16-bit fast PWM output generated from Timer/Counter 1. What it does is take analog readings from ADC0 then output as PWM to drive a LED. Also the voltage level at the ADC input is indicated by 8 LEDs connected to PORTA - sort of like a LED bar graph. Note that I did this example on a different board which has got a 16 MHz crystal, but the micro is the same.

Here is the complete application:

/*****************************
 * file name: adc_to_pwm.c
 *****************************/
 	
#include <avr/io.h>
#include <stdint.h>
#define F_CPU 16000000UL //16MHz
#include <avr/interrupt.h>

void FPWM_Init(void);
void ADC0_Init(void);

// Unsigned 16-bit int value to hold
// the ADC and PWM value 0 ~ 1023
uint16_t PWM_Value = 1023;

int main(void)
{
	DDRA = 0xFF;	// Use PORTA to indicate ADC value
	DDRB = 1<<DDB5;	// Enable OC1A as PWM output

	sei();	// Enable interrupts

	FPWM_Init();	// Initialize Timer/Counter1 as fast PWM
	ADC0_Init();	// Initialize ADC to take input from channel 0

	// Interrupts do the rest
	while(1)
		;
}

///////////////Fast PWM mode initialization////////////////////
// 
// Config. Timer/Counter1 to work in fast PWM mode:	
// * Frequency: 16MHz/1024 = 15.625KHz
// * Resolution: 10 bit	(top value = 0x03FF)
//
///////////////////////////////////////////////////////////////
void FPWM_Init(void)
{			 
	// Use fixed top value 0x03FF (WGM13:WGM10 = 0b0111)
	// No Prescaling (CS12:CS10 = 0b001)
	TCCR1A = (1<<COM1A1)|(1<<WGM11)|(1<<WGM10);
	TCCR1B = (1<<WGM12)|(1<<CS10);
	OCR1A = 1023;	// LED initially off
	TIMSK = 1<<TOIE1;	// Enable overflow interrupt
}

////////////////ADC0 channel input initialization//////////////
//
// Config. the ADC to take input from channel 0:
// * Reference: Aref
// * Channel: 0
// * ADC clock: 16MHz/128 = 125KHz
// * Mode: single conversion
// * Result justification: right adjusted
//
//////////////////////////////////////////////////////////////
void ADC0_Init(void)
{
	ADCSRA = (1<<ADEN)|(1<<ADSC)|(1<<ADIE)|\
			 (1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);	  
}

/////////////Timer/Counter1 overflow ISR///////////////////////
//
// All this does is update OCR1A to the PWM_Value variable 
// to adjust duty cycle. The larger the PWM_Value, the dimmer
// the LED becomes.
//
///////////////////////////////////////////////////////////////
ISR(TIMER1_OVF_vect)
{
	OCR1A = PWM_Value;	
}

/////////////ADC conversion complete ISR///////////////////////
//
// All this does is update PWM_Value according to the ADC
// conversion result. The ADC conversion result is subtracted
// from 1023 to obtain PWM_Value. So for ADC value 0 ~ 1023,
// PWM_Value will be 1023 ~ 0 and the brightness of the LED
// will increase.
//
///////////////////////////////////////////////////////////////
ISR(ADC_vect)
{
	PWM_Value = 1023 - ADC;	// Update PWM_Value
	PORTA = ~(0xFF<<(ADC/127));	// Output ADC conv. result 
								// as 0 ~ 8 LEDs on PORTA
	ADCSRA |= 1<<ADSC;	// Start a new conversion
}