Implementation of PWM in AVR

Hello readers! Welcome to this blog on Pulse Width Modulation (PWM) and its implementation using AVR C. Let’s get started!

Very often in Analog Electronics, we are required to generate an arbitrary voltage between two particular voltages. We use Pulse Width Modulation (PWM) to achieve this. Before we delve into PWM, let’s try to understand what is meant by duty-cycle.

Duty-Cycle:

The duty cycle of a digital signal is defined as the proportion of ‘on’ time to its time period. For example, if the duty-cycle is 0.5(or 50%), then the ‘on’ time and ‘off’ time are equal. For a much clear understanding, refer to the figure on the right.

Now consider the maximum voltage of the digital signal to be 5V and the minimum signal to be 0V. What do you think will be the average voltage in the case of a 50% duty cycle? Yes! It will be 2.5V. This is exactly how an arbitrary voltage between two voltages is generated.

The crux of Pulse Width Modulation is to modulate (vary the properties of) a given digital signal of a particular duty-cycle into a digital signal of the desired duty-cycle. Now, let’s see how this is achieved in microcontrollers such as ATMEGA16.

Most of the microcontrollers have an inbuilt clock, using which a periodic digital signal of a particular frequency can be generated. They also have counters which increment periodically based on the signal from the base clock. To generate a PWM signal of the required duty cycle and frequency, a threshold value for the counter is calculated which when reached by the counter signifies the end of ‘on’ time. The cycle is started by first generating the peak voltage. Once a compare match (value of the counter is equal to the threshold) is reached, the output is shifted from high state to low state. The cycle is repeated by generating the peak voltage, once the second compare match (value of the counter is such that the cycle is completed) is reached. The counter is reset to 0 at the end of each PWM cycle.

Now, we possess a basic understanding of the working of PWM and Duty Cycle. So now we can start implementing a basic model on a microcontroller such as ATMEGA16. To implement the concept, AVR Microcontrollers provide inbuilt functionalities and registers to output PWM signal on special pins of the Microcontroller. Let’s take a close look at these registers.

PWM Implementation on a Microcontroller:

Let’s first take a look at how a microcontroller implements PWM.

There are three registers whose knowledge is mandatory for implementing basic PWM on ATMEGA16 :

  • TCCR0: Timer/Counter Control Register
  • TCNT0: Timer/Counter Register
  • OCR0: Output Compare Register

Also, a basic knowledge of  DDR(Data Direction Register) is required.

For special interrupt features, there are other two registers namely TIMSK(Timer/Counter Mask Register) and TIFR(Timer/Counter Interrupt Flag Register) which are used to enable and control interrupt flags respectively. We are not going to use them in this article but for further reading please refer to this section of the datasheet of Atmega16.

In ATMEGA16, there are two modes of PWM available:

  • Fast PWM Mode
  • Phase Correct  PWM Mode

We will be using Fast PWM Mode and for implementing the Phase Correct PWM Mode the reader can refer to the datasheet. A basic understanding of normal operation on Fast PWM Mode would sufficiently equip a beginner to successfully implement Phase Correct PWM when required.

In Fast PWM Mode operation the following workflow takes place:

The TCNT0 register is basically an 8-bit counter register that increments after each timer tick whose frequency is determined by TCCR0 register bits. Each time the timer ticks the TCNT0 register value increases by one. Once the TCNT0 register reaches a max value of MAX (0xFF) (maximum value a 8-bit register data can hold), the TCNT0 register becomes equal to BOTTOM (0x00).

During this process, the TCNT0 register is continuously compared with the value held by the OCR0 register. Exactly at the moment when TCNT0  and OCR0 become equal in value, the pin OC0 toggles itself. When configured in Non-Inverting mode the OC0 pin changes to zero. When the TCNT0 reaches MAX, the OC0 pin is again set (output 5V). In this way, the OC0 pin toggles between high and low with High time directly depending on the value stored in the OCR0 register.

Implementation of Fast PWM Mode on ATMEGA16:

Step 1: Set data direction of OC0 pin (PB3 pin) as output using DDRB register.

Step 2: Set the Timer clock of our timer for Specific PWM frequency by setting TCCR0 register using the following info:

The first row defines the name and macro for the code of each bit.

The first three bits from the left define the Clock Select bits (CS00: CS02). They are used for Prescaling the internal clock or using external clock sources which are then used to determine the frequency of each tick. Their configuration can be understood using the following table:

Using this table we can understand how to configure the bits properly.

For a clock frequency 𝑓, We can PWM frequency by dividing it by MAX+1 (256). For example A clkI/O of 16MHz and a prescaler of 8 will give me the timer frequency of 16/8 MHz = 2MHz. Therefore PWM frequency would now be 2000/256 kHz = 7.8125KHz.

Step 3: Specifying the mode of operation to Fast PWM mode:

Here we configure the Wave Generation Mode bits (WGM00 and WGM01) to make Atmega16 execute PWM in Fast PWM mode.

Here for our use, we have to set WGM bits in Fast PWM which means both WGM00 and WGM01 have to be set to 1.

Step 4: Specifying the operation to be done when a compare match takes place between TCNT0 and OCR0:

We specify to the microcontroller what operations to do when a compare match takes place or the TCNT0 register reaches MAX value. We do this by using Compare Output Mode bits (COM01 : COM00). As we are using Fast PWM mode we specifically configure these bits according to that mode.

For our application we will use non-inverting mode by setting  COM00 to 0 and  COM01 to 1.

Step 5: Setting Duty cycle of PWM using OCR0 register:

Now we have successfully set up our PWM and we just need to specify the Duty cycle of PWM by using the OCR0 register. We can use the following formula to get the duty cycle of PWM:

We can easily derive the following formula using basic algebra.

Code:

Here I present an AVR code with the implementation of a function that executes PWM on OC0 pin. A LED attached to the PB3 pin via a current limiting resistor will periodically turn on slowly and fade off slowly. The speed of fading depends on the value of the variable fade amount. The duty cycle is the value of the current PWM duty cycle which is continuously increased and then decreased periodically.

/*
* KRSSGBlog1.c
* This code implements a Breathing LED which turns on slowly and off slowly periodically.
* Created: 20-06-2021 22:47:36
*  Author: BlueCoffee093
*/


#define F_CPU 8000000UL //Internal 8Mhz RC Oscillator
#include <avr/io.h>
#include <util/delay.h> //Library to include Delay function


void InitPWM(void);
void setDutyCycle(int);
int dutyCycle = 0; //Variable to store value of duty cycle
int fadeAmount = 5; //Amount by which duty Cycle should increase


int main(void)
{
   InitPWM();
   
while(1)
   {
           setDutyCycle(dutyCycle);
//Set this duty cycle
           
if(dutyCycle <= 0 || dutyCycle >= 100)
           {
                   fadeAmount = -fadeAmount;
           }
           dutyCycle += fadeAmount;
           _delay_ms(
20);
   }
}
void InitPWM(void)
{
   DDRB |= (
1<<PB3); //Step 1
   TCCR0 |= (
1<<CS01); //Step 2
   TCCR0 |= (
1<<WGM01) | (1 << WGM00); //Step 3
   TCCR0 |= (
1<<COM01); //Step 4
}
void setDutyCycle(int duty)
{
   OCR0 =
255*(duty/100); //Map (0,100) to (0,255) , Step 5
}

Blog by:- Yash & Ajay

KRSSG, IIT Kharagpur

Page created using Blog & Page Builder by Reputon

Leave a comment

Please note, comments must be approved before they are published