AVR Timers PWM :
You might be wondering why we’re only lighting up three LEDs using the hardware PWM, and the sad reason is that the AVR’s dedicated PWM pins are limited to two per timer, for a total of six: four pins at an 8-bit PWM depth, and two at 16 bits. So the software lights up three LEDs in a row with the PWM values you’ve typed, shifting the new value in, the current values over, and the oldest value out.
As with the rest of the timer/counter-based code demos so far, the event loop is quite sparse. It gets a value from the serial port and then shifts the values up a chain, corresponding to the output compare registers of LED1, LED2, and LED3. Note how we don’t need to use any more variables to store the “previous” values
that we received over the serial line—they’re stored in the OCRnx registers just as if they were normal variables. Writing those three variables takes just a couple microseconds even at 1 MHz. The rest of the CPU time is spent waiting for and processing our serial input. Note how we couldn’t do this at all with the software-based PWM approach in pwm.c; if we waited for serial input, the LEDs would stop blinking. Even if we just checked if the serial port had new data, it would throw off our PWM timings. Here, all our code
has to do to keep the LEDs blinking at their appropriate duty cycle is write values to the appropriate OCRnx registers. The hardware takes care of the rest—counting, comparing, turning on and off pins. In this example, that leaves us free to use the CPU to talk to us over the serial port.
CODE:
/* PWM Demo with serial control over three LEDs */
// ------- Preamble -------- //
#include <avr/io.h> /* Defines pins, ports, etc */
#include <util/delay.h> /* Functions to waste time */
#include "pinDefines.h"
#include "USART.h"
static inline void initTimers(void) {
// Timer 1 A,B
TCCR1A |= (1 << WGM10); /* Fast PWM mode, 8-bit */
TCCR1B |= (1 << WGM12); /* Fast PWM mode, pt.2 */
TCCR1B |= (1 << CS11); /* PWM Freq = F_CPU/8/256 */
TCCR1A |= (1 << COM1A1); /* PWM output on OCR1A */
TCCR1A |= (1 << COM1B1); /* PWM output on OCR1B */
// Timer 2
TCCR2A |= (1 << WGM20); /* Fast PWM mode */
TCCR2A |= (1 << WGM21); /* Fast PWM mode, pt.2 */
TCCR2B |= (1 << CS21); /* PWM Freq = F_CPU/8/256 */
TCCR2A |= (1 << COM2A1); /* PWM output on OCR2A */
}
int main(void) {
uint8_t brightness;
// -------- Inits --------- //
initTimers();
initUSART();
printString("-- LED PWM Demo --\r\n");
/* enable output on LED pins, triggered by PWM hardware */
LED_DDR |= (1 << LED1);
LED_DDR |= (1 << LED2);
LED_DDR |= (1 << LED3);
// ------ Event loop ------ //
while (1) {
printString("\r\nEnter (0-255) for PWM duty cycle: ");
brightness = getNumber();
OCR2A = OCR1B;
OCR1B = OCR1A;
OCR1A = brightness;
} /* End event loop */
return (0); /* This line is never reached */
}
PWM on Any Pin Demo:
To recap, the PWM-on-any-pin code works by setting up a timer/counter in Normal mode—counting up from 0 to 255 continuously—and interrupts are set to trigger off the timer. First, the overflow interrupt triggers when the timer rolls over back to 0. In this ISR, we turn the pins on. An output-compare ISR then turns the pin back off once the counter reaches the values stored in the output-compare register. That way, a larger value in the OCR registers mean that the pin is on for more of the cycle.
CODE:
// Quick and dirty demo of how to get PWM on any pin with interrupts
// ------- Preamble -------- //
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "pinDefines.h"
#define DELAY 3
volatile uint8_t brightnessA;
volatile uint8_t brightnessB;
// -------- Functions --------- //
static inline void initTimer0(void) {
/* must be /64 or more for ISR timing */
TCCR0B |= (1 << CS01) | (1 << CS00);
/* both output compare interrupts */
TIMSK0 |= ((1 << OCIE0A) | (1 << OCIE1B));
TIMSK0 |= (1 << TOIE0); /* overflow interrupt enable */
sei();
}
ISR(TIMER0_OVF_vect) {
LED_PORT = 0xff;
OCR0A = brightnessA;
OCR0B = brightnessB;
}
ISR(TIMER0_COMPA_vect) {
LED_PORT &= 0b11110000; /* turn off low four LEDs */
}
ISR(TIMER0_COMPB_vect) {
LED_PORT &= 0b00001111; /* turn off high four LEDs */
}
int main(void) {
// -------- Inits --------- //
uint8_t i;
LED_DDR = 0xff;
initTimer0();
// ------ Event loop ------ //
while (1) {
for (i = 0; i < 255; i++) {
_delay_ms(DELAY);
brightnessA = i;
brightnessB = 255 - i;
}
for (i = 254; i > 0; i--) {
_delay_ms(DELAY);
brightnessA = i;
brightnessB = 255 - i;
}
} /* End event loop */
return (0); /* This line is never reached */
}
You might be wondering why we’re only lighting up three LEDs using the hardware PWM, and the sad reason is that the AVR’s dedicated PWM pins are limited to two per timer, for a total of six: four pins at an 8-bit PWM depth, and two at 16 bits. So the software lights up three LEDs in a row with the PWM values you’ve typed, shifting the new value in, the current values over, and the oldest value out.
As with the rest of the timer/counter-based code demos so far, the event loop is quite sparse. It gets a value from the serial port and then shifts the values up a chain, corresponding to the output compare registers of LED1, LED2, and LED3. Note how we don’t need to use any more variables to store the “previous” values
that we received over the serial line—they’re stored in the OCRnx registers just as if they were normal variables. Writing those three variables takes just a couple microseconds even at 1 MHz. The rest of the CPU time is spent waiting for and processing our serial input. Note how we couldn’t do this at all with the software-based PWM approach in pwm.c; if we waited for serial input, the LEDs would stop blinking. Even if we just checked if the serial port had new data, it would throw off our PWM timings. Here, all our code
has to do to keep the LEDs blinking at their appropriate duty cycle is write values to the appropriate OCRnx registers. The hardware takes care of the rest—counting, comparing, turning on and off pins. In this example, that leaves us free to use the CPU to talk to us over the serial port.
CODE:
/* PWM Demo with serial control over three LEDs */
// ------- Preamble -------- //
#include <avr/io.h> /* Defines pins, ports, etc */
#include <util/delay.h> /* Functions to waste time */
#include "pinDefines.h"
#include "USART.h"
static inline void initTimers(void) {
// Timer 1 A,B
TCCR1A |= (1 << WGM10); /* Fast PWM mode, 8-bit */
TCCR1B |= (1 << WGM12); /* Fast PWM mode, pt.2 */
TCCR1B |= (1 << CS11); /* PWM Freq = F_CPU/8/256 */
TCCR1A |= (1 << COM1A1); /* PWM output on OCR1A */
TCCR1A |= (1 << COM1B1); /* PWM output on OCR1B */
// Timer 2
TCCR2A |= (1 << WGM20); /* Fast PWM mode */
TCCR2A |= (1 << WGM21); /* Fast PWM mode, pt.2 */
TCCR2B |= (1 << CS21); /* PWM Freq = F_CPU/8/256 */
TCCR2A |= (1 << COM2A1); /* PWM output on OCR2A */
}
int main(void) {
uint8_t brightness;
// -------- Inits --------- //
initTimers();
initUSART();
printString("-- LED PWM Demo --\r\n");
/* enable output on LED pins, triggered by PWM hardware */
LED_DDR |= (1 << LED1);
LED_DDR |= (1 << LED2);
LED_DDR |= (1 << LED3);
// ------ Event loop ------ //
while (1) {
printString("\r\nEnter (0-255) for PWM duty cycle: ");
brightness = getNumber();
OCR2A = OCR1B;
OCR1B = OCR1A;
OCR1A = brightness;
} /* End event loop */
return (0); /* This line is never reached */
}
PWM on Any Pin Demo:
To recap, the PWM-on-any-pin code works by setting up a timer/counter in Normal mode—counting up from 0 to 255 continuously—and interrupts are set to trigger off the timer. First, the overflow interrupt triggers when the timer rolls over back to 0. In this ISR, we turn the pins on. An output-compare ISR then turns the pin back off once the counter reaches the values stored in the output-compare register. That way, a larger value in the OCR registers mean that the pin is on for more of the cycle.
CODE:
// Quick and dirty demo of how to get PWM on any pin with interrupts
// ------- Preamble -------- //
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "pinDefines.h"
#define DELAY 3
volatile uint8_t brightnessA;
volatile uint8_t brightnessB;
// -------- Functions --------- //
static inline void initTimer0(void) {
/* must be /64 or more for ISR timing */
TCCR0B |= (1 << CS01) | (1 << CS00);
/* both output compare interrupts */
TIMSK0 |= ((1 << OCIE0A) | (1 << OCIE1B));
TIMSK0 |= (1 << TOIE0); /* overflow interrupt enable */
sei();
}
ISR(TIMER0_OVF_vect) {
LED_PORT = 0xff;
OCR0A = brightnessA;
OCR0B = brightnessB;
}
ISR(TIMER0_COMPA_vect) {
LED_PORT &= 0b11110000; /* turn off low four LEDs */
}
ISR(TIMER0_COMPB_vect) {
LED_PORT &= 0b00001111; /* turn off high four LEDs */
}
int main(void) {
// -------- Inits --------- //
uint8_t i;
LED_DDR = 0xff;
initTimer0();
// ------ Event loop ------ //
while (1) {
for (i = 0; i < 255; i++) {
_delay_ms(DELAY);
brightnessA = i;
brightnessB = 255 - i;
}
for (i = 254; i > 0; i--) {
_delay_ms(DELAY);
brightnessA = i;
brightnessB = 255 - i;
}
} /* End event loop */
return (0); /* This line is never reached */
}
No comments:
Post a Comment