8-bit AVR® Microcontrollers A More Complete PWM Driver
In this video
- Create functions: PWM_Increase_duty(), PWM_Decrease_duty(), PWM_BrightDim()
- Use enumerators to keep track of the direction of PWM duty cycle change
- Declare our function prototypes
We will expand the options available for lighting our LED with the code changes outlined below. We will include the ability not only to increase the brightness but also to dim it. We will clean up our code a bit by declaring function prototypes and using enumerators to track PWM duty-cycle changes.
Procedure
Increase the LED Brightness
Take the logic that brightens the LED which is currently inside an ISR and copy and then delete it. Create a new function called PWM_Increase_duty just below the ISR and paste the copied logic into it. It should look like the accompanying code.
{
uint16_t period = OCR1A;
uint16_t duty = OCR1B;
if (duty < period)
{
duty++;
}
else
{
duty = 0;
}
OCR1B = duty;
}
Decrease the LED Brightness
Copy the PWM_Increase_duty() function and paste it directly below the original function. Rename the new function PWM_Decrease_duty().
Modify the logic so that the function checks whether duty is greater than 0. If it is, decrement duty by 1. Otherwise, keep duty at 0.
The completed function should look like the following code:
{
uint16_t period = OCR1A;
uint16_t duty = OCR1B;
if (duty > 0)
{
duty--;
}
else
{
duty = 0;
}
OCR1B = duty;
}
Determine the Direction of the Duty Cycle
Now we create a new function called PWM_BrightDim with the following logic and place it into the TIMER0 ISR. The code allows us to assign the direction of the duty cycle. UP and DOWN will be declared as enums toward the top of the code, which will be shown in the final code version.
{ uint16_t period = OCR1A;
uint16_t duty = OCR1B;
static uint8_t direction;
switch (direction)
{
case UP:
if (++duty == (period - 1))
direction = DOWN;
break;
case DOWN:
if (--duty == 2)
direction = UP;
break;
}
OCR1B = duty;
}
Declare the Functions
You may notice that MPLAB® X IDE is showing errors in our code. We need to declare functions toward the beginning to make the errors go away. Place the following code below the #defines, which will declare our enums and functions.
void PWM_Decrease_duty(void);
void PWM_Decrease_duty(void);
void PWM_BrightDim(void);
void Timer_Frequency(uint8_t freq);
void PWM_Init(void);
void milliS_timer(uint8_t milliS);
Final Code
Here is the final code that contains the elements shown above.
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#define LED_ON PORTB |= (1<<PORTB5)
#define LED_OFF PORTB &= ~(1<<PORTB5)
#define LED_TOGGLE PINB |= (1<<PINB5)
enum {UP, DOWN};
void PWM_Decrease_duty(void);
void PWM_Decrease_duty(void);
void PWM_BrightDim(void);
void Timer_Frequency(uint8_t freq);
void PWM_Init(void);
void milliS_timer(uint8_t milliS);
ISR(TIMER1_COMPA_vect)
{
LED_ON;
}
ISR(TIMER1_COMPB_vect)
{
LED_OFF;
}
ISR(TIMER0_COMPA_vect)
{
PWM_BrightDim();
}
void PWM_Increase_duty(void)
{
uint16_t period = OCR1A;
uint16_t duty = OCR1B;
if (duty < period)
{
duty++;
}
else
{
duty = 0;
}
OCR1B = duty;
}
void PWM_Decrease_duty(void)
{
uint16_t period = OCR1A;
uint16_t duty = OCR1B;
if (duty > 0)
{
duty--;
}
else
{
duty = 0;
}
OCR1B = duty;
}
void PWM_BrightDim(void)
{ uint16_t period = OCR1A;
uint16_t duty = OCR1B;
static uint8_t direction;
switch (direction)
{
case UP:
if (++duty == (period - 1))
direction = DOWN;
break;
case DOWN:
if (--duty == 2)
direction = UP;
break;
}
OCR1B = duty;
}
void Timer_Frequency(uint8_t freq)
{
TCCR1B |= (1 << CS12) | (1 << WGM12); //Set clock source & set mode to CTC
TIMSK1 |= (1 << OCIE1A); //Enable the CTC interrupt
OCR1A = (F_CPU/(freq*2*256)-1);
}
void PWM_Init(void)
{
TCCR1B |= (1 << CS10) | (1 << WGM12); //No Prescaler & set mode to CTC
TIMSK1 |= (1 << OCIE1A) | (1 << OCIE1B); //Enable the CTC interrupt
OCR1A = 800;
OCR1B = 400;
}
void milliS_timer(uint8_t milliS)
{
TCCR0A |= (1 << WGM01); //set to CTC mode
TCCR0B |= (1 << CS02) | (1 << CS00); //set prescaler to 1024
OCR0A = milliS * 7.8125 - 1;
TIMSK0 |= (1 << OCIE0A); //enable the interrupt
}
int main(void) {
DDRB |= (1 << PB5); // set PB5 as output pin
DDRB &= ~(1<<DDB7); //set PB7 as an input pin
milliS_timer(2);
PWM_Init();
sei(); //Enable global interrupts
while (1) {
}
}
Program the Device
Press the Make and Program Device Main Project button at the top of the MPLAB X IDE.
![]()
You should observe the device increasing and decreasing in brightness.