PWM from scratch with Arduino UNO

Hello,

I am new on Adruino and I want to generate a PWM Signal on any digital pin of the Arduino UNO board.

To get full control and complete understanding of the PWM I don´t want to use a existingl PWM library.

In the following code I use Pin 9 as a PWM output, which should be toggled by compare register A interrupt.
But it doesn´t work, and I don´t understand why.

I set following for timer 2:

  • reset all registers to zero
  • choose fast PWM
  • disconnect PWM pins (not needed if interrupt is used)
  • compare register A set to a value for duty cycle (e.g. 200)
  • activate timer interrupt for CompA and OVF (compare and timer overflow)
  • prescaler 64

The timer overflow is needed to preload the timer 2 to get 1kHz frequency.

I get no signal on pin 9 with this setup.

Perhaps anyone can give me a hint, how to get it to work.

Thank you in advance!

#include <avr/io.h> 
#include <avr/interrupt.h> 

//******* Variable Declaration *******
int value;
boolean toggle = 0;  //storage variables


//-----------------------------------------------------------------------------------------

void setup()
{
    
  //set pins as outputs
  pinMode(9, OUTPUT); // PWM Interrupt Output
  pinMode(11, OUTPUT); // OC2A
  pinMode(3, OUTPUT);  // OC2B
  
  //setup all timers 
  noInterrupts();           // disable all interrupts

  //****** Register Reset ******
  TCNT2 = 0x00;
  TCCR2A = 0x00;   
  TCCR2B = 0x00;
  TIMSK2 = 0x00;
  TIFR2 = 0x00;
  
  //****** Select PWM Mode by WGM Bits *******
  TCCR2B |= (0 << WGM22);
  TCCR2A |= (1 << WGM21) | (1 << WGM20);    // fast pwm
  
  //****** Chose Signal Mode *******  
  TCCR2A |= (0 << COM2A1) | (0 << COM2A0);  // OC2A disconnected
  TCCR2A |= (0 << COM2B1) | (0 << COM2B0);  // OC2B disconnected
    
  //****** Set Compare Register ******* 
  OCR2A = 200;
  OCR2B = 100;
    
  //****** Activate Interrupt ******* 
  TIMSK2 |= (0 << OCIE2B) | (1 << OCIE2A) | (1 << TOIE2) ;   //activate Timer 2 Interrupts 

  //****** Set Prescaler and start PWM ******* 
  TCCR2B |= (1 << CS22) | (0 << CS21) | (0 << CS20);  //prescaler 64 
  
  interrupts();    // enable all interrupts

}

//-----------------------------------------------------------------------------------------

ISR(TIMER2_CompA_vect)
{
  if (toggle){
    digitalWrite(9,LOW);
    toggle = 0;
  }
  else{
    digitalWrite(9,HIGH);
    toggle = 1;
  }

  TIFR2 = 0x00;
}

//-----------------------------------------------------------------------------------------

ISR(TIMER2_OVF_vect)        //timer2 interrupt at timer overflow 
{        
  TCNT2 = 6;                //load timer
  TIFR2 = 0x00;             //Timer2 INT Flag Reg: Clear Timer Overflow Flag
}
 
//-----------------------------------------------------------------------------------------

void loop()
{
 
}

toggle should be volatile :wink:

Or, you could just read the state:

digitalWrite(pin, !digitalRead(pin))

or read and set the real register yourself

Thanks for the fast reply! :slight_smile:

I updated the code. But the main issue is the interrupt. It is not called, so there is no PWM signal on pin 9.

#include <avr/io.h> 
#include <avr/interrupt.h> 

//******* Variable Declaration *******
int value;


//-----------------------------------------------------------------------------------------

void setup()
{
  //Serial.begin(9600);
   
  //set pins as outputs
  pinMode(9, OUTPUT); // PWM Interrupt Output
  pinMode(11, OUTPUT); // OC2A
  pinMode(3, OUTPUT);  // OC2B
  
  //setup all timers 
  noInterrupts();           // disable all interrupts

  //****** Register Reset ******
  TCNT2 = 0x00;
  TCCR2A = 0x00;   
  TCCR2B = 0x00;
  TIMSK2 = 0x00;
  TIFR2 = 0x00;
  
  //****** Select PWM Mode by WGM Bits *******
  TCCR2B |= (0 << WGM22);
  TCCR2A |= (1 << WGM21) | (1 << WGM20);    // fast pwm
  
  //****** Chose Signal Mode *******  
  TCCR2A |= (0 << COM2A1) | (0 << COM2A0);  // OC2A disconnected
  TCCR2A |= (0 << COM2B1) | (0 << COM2B0);  // OC2B disconnected
    
  //****** Set Compare Register ******* 
  OCR2A = 200;
  OCR2B = 100;
    
  //****** Activate Interrupt ******* 
  TIMSK2 |= (0 << OCIE2B) | (1 << OCIE2A) | (1 << TOIE2) ;   //activate Timer 2 Interrupts 

  //****** Set Prescaler and start PWM ******* 
  TCCR2B |= (1 << CS22) | (0 << CS21) | (0 << CS20);  //prescaler 64 
  
 interrupts();    // enable all interrupts

 
}

//-----------------------------------------------------------------------------------------

ISR(TIMER2_CompA_vect)
{
  digitalWrite(9, !digitalRead(9));

  TIFR2 = 0x00;
}

//-----------------------------------------------------------------------------------------

ISR(TIMER2_OVF_vect)        //timer2 interrupt at timer overflow 
{        
  TCNT2 = 6;                //load timer
  TIFR2 = 0x00;             //Timer2 INT Flag Reg: Clear Timer Overflow Flag
}
 
//-----------------------------------------------------------------------------------------

void loop()
{
 
}

Sorry, I was blocked for a few minutes, because I answered too fast. ::slight_smile:
Now it´s updated, see above.

Syntax is case sensitive

//ISR(TIMER2_CompA_vect) 

ISR(TIMER2_COMPA_vect)

I can´t belive it ... I am searching since hours for the root cause and you did it within minutes!! :o

Now it is running well! Thank you so much!

Hopefully this code helps other beginners too. I think it is easy to understand.

Here the final code:

#include <avr/io.h> 
#include <avr/interrupt.h> 

//******* Variable Declaration *******
int value;


//-----------------------------------------------------------------------------------------

void setup()
{
     
  //set pins as outputs
  pinMode(9, OUTPUT); // PWM Interrupt Output
  pinMode(11, OUTPUT); // OC2A
  pinMode(3, OUTPUT);  // OC2B
  
  //setup all timers 
  noInterrupts();           // disable all interrupts

  //****** Register Reset ******
  TCNT2 = 0x00;
  TCCR2A = 0x00;   
  TCCR2B = 0x00;
  TIMSK2 = 0x00;
  TIFR2 = 0x00;
  
  //****** Select PWM Mode by WGM Bits *******
  TCCR2B |= (0 << WGM22);
  TCCR2A |= (1 << WGM21) | (1 << WGM20);    // fast pwm
  
  //****** Choose Signal Mode *******  
  TCCR2A |= (1 << COM2A1) | (0 << COM2A0);  // OC2A disconnected
  TCCR2A |= (0 << COM2B1) | (0 << COM2B0);  // OC2B disconnected
    
  //****** Set Compare Register ******* 
  OCR2A = 128;
  OCR2B = 200;
    
  //****** Activate Interrupt ******* 
  TIMSK2 |= (0 << OCIE2B) | (1 << OCIE2A) | (1 << TOIE2) ;   //activate Timer 2 Interrupts 

  //****** Set Prescaler and start PWM ******* 
  TCCR2B |= (1 << CS22) | (0 << CS21) | (0 << CS20);  //prescaler 64 
  
 interrupts();    // enable all interrupts


}

//-----------------------------------------------------------------------------------------

ISR(TIMER2_COMPA_vect)
{
  digitalWrite(9, !digitalRead(9));

  TIFR2 = 0x00;
}

//-----------------------------------------------------------------------------------------

ISR(TIMER2_OVF_vect)        //timer2 interrupt at timer overflow 
{        
  TCNT2 = 6;                //load timer to adjust frequency
  TIFR2 = 0x00;             //Timer2 INT Flag Reg: Clear Timer Overflow Flag
}
 
//-----------------------------------------------------------------------------------------

void loop()
{
 
}