Arduino Mega PWM Generation

Hello

My question is how do we set PWM frequency to 800hz in Arduino mega. I am using Arduino Mega and I want to generate 800hz output from pin 9 & 8. But when measure frequency using oscilloscope only got around 480hz. I don’t have much experience with this. Could anyone guide me/any suggestions on how to achieve this frequency?

void setup()
{

 TCCR2B=(TCCR2B&248)|0x03;    //prescaler=32, 980.3Hz

}
void loop()
{
   analogWrite(9,100);
}

This Code generates a frequency of about 1Khz. Here is the screenshot.
How do I generate 800hz & What should I Change?

Thanks in Advance.
Aasai

Refer to the datasheet, namely the section on the Waveform Generation Modes (WGM) available for timer2. To get an arbitrary frequency, you must be able to adjust the TOP and prescaler to get the desired frequency, then set the output compare value to get the desired duty cycle. The only mode of the 8-bit asynchronous timer2 that allows you to set the TOP requires you to use OCR2A, which takes out one of the two PWM channels. You are much better off using one of the 4 16-bit timers (1,3,4,5), which, in addition to being much better timers for any purposes where an asynchronous timer is not required (which is basically never in arduino-land), have a WGM where you can specify the TOP via the ICRn register, leaving both PWM pins available.

In any event, you can’t get it from any 8-bit timer with a 16MHz system clock and prescaler of 32 anyway - this is basic math - 16,000,000/32/800 is 625. In a dual-ramp WGM from an 8-bit timer, you can only count to 512, even if you can set TOP. One could of course use a larger prescale, but then you lose resolution, and that still doesn’t address Timer2’s lack of an appropriate WGM…

And no matter what, if you’re making arbitrary frequency PWM, forget about analogWrite! analogWrite is only for 8-bit PWM, and with prescaling only available in powers of 2, you only have so many options for the frequency.

The right course of action is to use one of the 16-bit timers in WGM where you set TOP with the ICR register, iirc it’s WGM 14 on the mega’s 16-bit timers on the AVRs. Hell, then you wouldn’t even need to prescale it!

Just set it to WGM, the appropriate pins as output, put timer fast PWM with TOP set by the timer’s IRC register, prescaler of 1… 16000000/800=20000, so set ICRn to 19999 (0 is one cycle) - (it’s a 16-bit register), set the COMnx bits to 2.

Then, to set the duty cycle, you can just write something like

void setDutyCycle(unsigned int duty) { 
OCRnx = duty;
}

That argument would be relative to the TOP value, of course, so instead of calling analogWrite(pin,127) to get 50% duty cycle, assuming 800Hz desired output frequency (ie, TOP=19999), you’d call setDutyCycle(9999)…

In register names above per Atmel’s convention, n is the timer number, x is channel letter, ex, ICR1, OCR1A, etc.

On the atmega2560 used in the mega, you even get 3 output channels per timer instead of 2 like most AVRs!

The classic AVR timers are pretty straightforward. I was staring at the megaavr (eg, atmega4809 as used on nano every, or the megaavr 0-series and 1-series tinies) all night - they’re quite a bit more complicated - but damned cool… Overall, though, the classic avrs often give you more timers, more output pins, etc, particularly as you move up the product line… they’re also way more expensive though (with the exception of, like, the 328pb, which is cheaper than a 328p, despite having a second uart/spi/i2c interface, 4 extra I/O pins and 3 of those juicy 16-bit timers instead of just 1 like the 328p had… but I digress…

Because it’s more fun than what I should be doing (and good to make sure I’m not forgetting the classic timers with all the time I’ve been on the megaAVR parts. Totally untested off the cuff code but it may require some syntax correction and the like.

Used Timer5 on pins 44,45, and 46. Can adapt to timers 1, 3 or 4, and/or to other parts with similar 16-bit timers by adjusting the register names to point to the other timer. Most other parts don’t have 3 outputs per timer, though, only 2.

initPWM(frequency, rampmode) sets it up, including disabling any PWM output on the pins.

rampmode is:
0 (fast PWM, single slope mode)
1 (phase correct PWM, dual slope mode)
2 (phase and frequency correct PWM, dual slope mode)

setPWMDutyCycle8Bit(pin,dutycycle)

turn on PWM on a pin (if not on) and set it specified duty cycle, 0-255 like analogWrite().

disablePWMOutput(pin)

turn off PWM on pin and write it LOW.

Actually, I think digitalWrite() will also turn off the PWM. Maybe I didn’t even need to write that, heh

See datasheets for more information on what the differences are.

void setup() {
  // put your setup code here, to run once:
  initPWM(800,0);
}

void loop() {
  setPWMDutyCycle8Bit(44,128);
  setPWMDutyCycle8Bit(45,64);
  setPWMDutyCycle8Bit(46,192);

}

void initPWM(unsigned long freq,byte rampmode) {
  //calculate TOP
  unsigned long clockspercycle=(F_CPU/freq);
  if (rampmode) { //dual slope mode requested
    clockspercycle=clockspercycle>>1;
  }
  byte presc=1; //this is the value to be written to the CS bits, NOT the actual prescaler
  while (clockspercycle>65536) { //do we need to prescale? 
    presc++; //increase prescaling
    clockspercycle=clockspercycle>>(presc>3?2:3); //divide required top by 8 or 4
    //no test needed for whether we've exhausted available prescalers, because 20,000,000/1024 will fit in 16-bits, and that's the highest rated speed for classic AVRs
  }
  // reset registers
  TCCR5B=0; 
  TCCR5A=0; 
  byte wgm=(rampmode?(rampmode==1?10:8):14); //determine desired WGM
  ICR5=clockspercycle-1;
  //clear these for good measure
  OCR5A=0;
  OCR5B=0;
  OCR5C=0;
  TCCR5A=0x03&wgm; //set low bits of WGM 
  TCCR5B=((wgm<<3)&0x18)|presc; //set rest of WGM, prescaler and start timer
}
//8-bit resolution, like analogWrite()
void setPWMDutyCycle8Bit(byte pin,byte dutycycle) {
  unsigned int ocrval=map(dutycycle,0,255,0,ICR5);
  if (!(pin==44||pin==45||pin==46)) { //if we call on a pin that isn't on this timer, return immediately.
    return; 
  }
  switch (pin)
  {
    case 46:
      OCR5A=ocrval;
      TCCR5A|=0x80; //COMA=2 non-inverting pwm
      break;
    case 45:
      OCR5B=ocrval;
      TCCR5A|=0x20; //COMA=2 non-inverting pwm
      break;
    case 44:
      OCR5B=ocrval;
      TCCR5A|=0x08; //COMA=2 non-inverting pwm
      break;
  }
  pinMode(pin,OUTPUT);
}

void disablePWMOutput(byte pin) {
  if (!(pin==44||pin==45||pin==46)) { //if we call on a pin that isn't on this timer, return immediately.
    return; 
  }
  switch (pin)
  {
    case 46:
      TCCR5A&=0x3F; //COMA=0 off
      break;
    case 45:
      TCCR5A&=0xCF; //COMA=0 off
      break;
    case 44:
      TCCR5A&=0xF3; //COMA=0 off
      break;
  }
  pinMode(pin,INPUT); //This may not be the ideal behavior for your use case - possibly digitalWrite'ing it LOW would be better.
}

/* 
// uncomment and fill in MAX to get a high-res version where duty cycle expressed as 0~MAX
void setDutyCycleHiRes(byte pin,unsigned int dutycycle) {
  unsigned int ocrval=map(dutycycle,0,MAX,0,ICR5)
  if (!(pin==44||pin==45||pin==46)) { //if we call on a pin that isn't on this timer, return immediately.
    return; 
  }
  switch (pin)
  {
    case 46:
      OCR5A=ocrval;
      TCCR5A|=0x80; //COMA=2 non-inverting pwm
      break;
    case 45:
      OCR5B=ocrval;
      TCCR5B|=0x80; //COMA=2 non-inverting pwm
      break;
    case 44:
      OCR5C=ocrval;
      TCCR5C|=0x80; //COMA=2 non-inverting pwm
      break;
  }
  pinMode(pin,OUTPUT);
}

Edit 3/20: Fixed a few minor syntax errors, put a sketch around it to demo it…