# Get 25 kHz PWM on a 16 MHz Arduino

I am trying to output a 25 kHz software PWM on at least four pins simultaneously with variable duty cycles on each pin individualy.

I've got all the actual PWM outputs via internal timers 2x8 1x16 with variable duty cycles and am looking for a way to output a 25 kHz PWM on the four pins (any pins) via an external timer if necessary. How can I do that?

• The three available timers on a 328 give you six PWM outputs via their A and B compare outputs, with the only limitation being that the two outputs of the same timer have to have the same period (but can have different duty cycles). So as far as I can see, you don't need to do anything special. – hobbs Oct 11 '15 at 16:58

At 16 MHz, your instruction cycle is 62.5 ns, and your PWM period is 40 \$\mu\$s. This is only 640 instruction cycles per PWM period.

To have any hope of doing this, the first thing you need to do is forget using anything Arduino other than the hardware. Download the datasheet for the AVR in question and start getting familiar with it. Your best bet is to use 8 bit timers to reduce the overhead of loading them.

My approach would be to use every 8 bit timer available, and divide the outputs up among the timers. Load the shorest pulse width output in the 8 bit tiemrs. Keep a 16 bit timer available to use for the PWM period. Start all the timers at once. On the first 8-bit timer interrupts, toggle the output, and reload the timers with the time between the next shorest pulse width. Rinse and repeat.

Alternatively you could run one timer, and write a little scheduler that puts the outputs in order from shortest to longest pulse width, constantly poll the timer, and toggle the bits as needed.

Both of these options have a ton of software overhead, and the pulse widths will all be semi-deterministic. Neither would be good for any application where the micro has to do anything else in a timely fashion, such as communication or drive a LCD.

• I've been thinking of making a board using 2 or 4, PCA9553 then it's all getting the i2c set up to update them adafruit.com/products/2590 is the the arduino I'm using currently, but I've yet to use i2c and I haven't found reference to anyone using it with arduino yet – Trevor Nichols Oct 11 '15 at 2:16

Look into using one of the AVR chips that have more PWM outputs. For example, AT90PWM81 which includes six hardware PWM outputs.

• I do like the outputs available on that chip I'll definitely have to read up on the spi interface, I have not yet seen data regarding duty cycle changes but I'm just getting started reading into it – Trevor Nichols Oct 11 '15 at 2:26
• I was tinking that chip might even run arduino code. – Jasen Oct 11 '15 at 2:28
• I would like to have the option of reading sensors, temp and rpm mainly, and use collected data to change the duty cycle for the pwm outputs and had planned to use the serial connection to send and receive data on a computer to at least to display data, and possibly manual controlled of duty cycle via an application on the on the computer – Trevor Nichols Oct 11 '15 at 3:18
• AT90PWM216 has a UART, that might make for easier PC interfacing. – Jasen Oct 11 '15 at 3:41
void setup()
{
// Set the main system clock to 8 MHz.
noInterrupts();
CLKPR = _BV(CLKPCE);  // enable change of the clock prescaler
CLKPR = _BV(CLKPS0);  // divide frequency by 2
interrupts();

// Configure Timer 0 for phase correct PWM @ 25 kHz.
TCCR0A = 0;           // undo the configuration done by...
TCCR0B = 0;           // ...the Arduino core library
TCNT0  = 0;           // reset timer
TCCR0A = _BV(COM0B1)  // non-inverted PWM on ch. B
| _BV(WGM00);  // mode 5: ph. correct PWM, TOP = OCR0A
TCCR0B = _BV(WGM02)   // ditto
| _BV(CS00);   // prescaler = 1
OCR0A  = 160;         // TOP = 160

// Same for Timer 1.
TCCR1A = 0;
TCCR1B = 0;
TCNT1  = 0;
TCCR1A = _BV(COM1A1)  // non-inverted PWM on ch. A
| _BV(COM1B1)  // same on ch. B
| _BV(WGM11);  // mode 10: ph. correct PWM, TOP = ICR1
TCCR1B = _BV(WGM13)   // ditto
| _BV(CS10);   // prescaler = 1
ICR1   = 160;

// Same for Timer 2.
TCCR2A = 0;
TCCR2B = 0;
TCNT2  = 0;
TCCR2A = _BV(COM2B1)  // non-inverted PWM on ch. B
| _BV(WGM20);  // mode 5: ph. correct PWM, TOP = OCR2A
TCCR2B = _BV(WGM22)   // ditto
| _BV(CS20);   // prescaler = 1
OCR2A  = 160;
}

void loop()
{
analogWrite( 3,   1);  // duty cycle = 1/160
analogWrite( 5,  53);  // ~ 1/3
analogWrite( 9, 107);  // ~ 2/3
analogWrite(10, 159);  // 159/160
}