Arduino Uno PWM – Strange Results

arduinopwm

I've been experimenting with the PWM Waveform Generation Modes on the ATMega328P. I've been getting some strange results and I can't figure out if its a problem with how I'm writing the firmware or how I'm interpreting the datasheet.

Here's the first piece of code that I wrote to emulate the analogWrite() function:

// Waveform Generation Mode 0
// Table 15-4 of the datasheet

void setup()
{
  DDRB = (1<<PB1); // set pin 9 as output

  TCCR1A |= (1<<COM1A1);
  OCR1A = 125;
}

void loop()
{
}

The above code produces an average voltage output of around 2.5V (49% duty cycle) out of pin 9. The strange thing (for me) is that according to the datasheet, TIMER1 is a 16bit timer, so it should overflow at 65536 ticks. From what I understand setting OCR1A between 0 and 65535 will change the duty cycle of the pulse. So, having set the OCR1A at 125, shouldn't I be getting an output of around 0.01 V instead of 2.5V? The results seem to imply that the clock is overflowing at 255.

For my second foray into PWM land, I wanted to try and create a 2.5V signal using the ATMega's fast PWM mode. Here's what I got:

// Waveform Generation Mode 14
// Table 15-4 of the datasheet

void setup()
{
  DDRB = (1<<PB1);

  TCCR1A |= (1<<COM1A1) | (1<<WGM11);
  TCCR1B |= (1<<WGM13) | (1<<WGM12) | (1<<CS10);

  ICR1 = 19999;
  OCR1A = 10000;
}

void loop()
{
}

I set ICR1 (the overflow value) arbitrarily to 20000 ticks then set OCR1A (the compare value) to about half that. I set Channel A to non-inverting mode, but (I think) it wouldn't have made a difference if I set it to inverting mode. When I flashed this onto the Arduino I was getting a steady voltage average of 5V (100% duty cycle) out of pin 9, and I can't for the life of me figure out why.

I would appreciate any insight you can offer.

Best Answer

Answered by joeymorin on AVRfreaks:

Note that on the Uno, the Arduino init code that runs before your setup() configures a lot of stuff, including all three timers on the 328P.

From wiring.c:

   sbi(TCCR0A, WGM01); 
   sbi(TCCR0A, WGM00); 
   sbi(TCCR0B, CS01); 
   sbi(TCCR0B, CS00); 
   sbi(TIMSK0, TOIE0); 

   sbi(TCCR1B, CS11); 
   sbi(TCCR1B, CS10); 
   sbi(TCCR1A, WGM10); 

   sbi(TCCR2B, CS22); 
   sbi(TCCR2A, WGM20);

This starts all three timers with a prescaler of 64.

TIMER0 is placed into mode 3 (fast PWM) with the overflow interrupt enabled to support the timing functions (millis(), micros(), and delay()).

TIMER1 is placed into mode 1 (fixed 8-bit phase-correct PWM).

TIMER2 is placed into mode 1 (phase-correct PWM).

void setup() 
{ 
  DDRB = (1<<PB1); // set pin 9 as output 

  TCCR1A |= (1<<COM1A1); 
  OCR1A = 125; 
} 

void loop() 
{ 
} 

Since WGM10 in TCCR1A is already set, setting COM1A1 will enable the PWM output in non-inverting mode, just as analogWrite() would.

TIMER1 is a 16bit timer, so it should overflow at 65536 ticks. From what I understand setting OCR1A between 0 and 65535 will change the duty cycle of the pulse. So, having set the OCR1A at 125, shouldn't I be getting an output of around 0.01 V instead of 2.5V? The results seem to imply that the clock is overflowing at 255.

In mode 1 it behaves like an 8-bit timer.

void setup() 
{ 
  DDRB = (1<<PB1); 

  TCCR1A |= (1<<COM1A1) | (1<<WGM11); 
  TCCR1B |= (1<<WGM13) | (1<<WGM12) | (1<<CS10); 

  ICR1 = 19999; 
  OCR1A = 10000; 
} 

void loop() 
{ 
}

Since WGM10 in TCCR1A is already set, setting WGM11, WGM13, and WGM12 will select mode 15, not mode 14. Mode 15 is fast PWM with TOP = OCR1A (not ICR1). Since you are also using setting OC1A output for PWM with COM1A1, this will result in an OC1A remaining high.

As mentioned already, if you want to configure timers in the Arduino environment, you should do it from scratch with = instead of |=.

theusch wrote:

And, for all I know, more might be run after setup()

From Arduino's main.cpp: Code:

#include <Arduino.h> 

int main(void) 
{ 
   init(); 

#if defined(USBCON) 
   USB.attach(); 
#endif 

   setup(); 

   for (;;) { 
      loop(); 
      if (serialEventRun) serialEventRun(); 
   } 

   return 0; 
}

JJ