8-bit AVR® A More Complete PWM Driver

Last modified by Microchip on 2023/11/09 09:02

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.

Will will expand the options available for lighting our LED with the code changes outlined below.  We will include the ability to not only increase the brightness but to dim it as well.  We will clean up our code a bit by declaring function prototypes and using enumerators to keep track of our 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.

void PWM_Increase_duty(void)
{
   uint16_t period = OCR1A;
   uint16_t duty =  OCR1B;
   
   if (duty < period)
    {
        duty++;
    }
   else
    {
        duty = 0;
    }
    OCR1B = duty;  
}

Lower the LED Brightness

Copy this entire function and paste just below it and rename it PWN_Decrease_duty.  Now modify the logic so that it tests whether duty is greater than 0 and decrements Duty is true.  It should be like the accompanying code.

void PWM_Decrease_duty(void)
{
   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.

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;
}

Declare the Functions

You may notice that MPLAB® X 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 our functions.

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);

Final Code

Here is the final code that contains the elements shown above.

#define F_CPU 16000000UL
#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.

Program the Device

You should observe the device increasing and decreasing brightness.


Learn More