How to achieve to PWM, maximum frequency and variable duty cycle of at least 8 bit

Hi everyone,

I build guitar pedals using Arduino Nanos and I drive vactrols, transistors and OTAs with PWM quite often. I know about analog write but I've seen people around here do all kinds of advanced tweaks to the timers to make the PWM signal suit their needs. I've read the specs like a thousand times and I can't get my head around them.

My goal is to achieve the fastest possible (well over the human hearing threshold of 20KHz) with at least 256 possible values and in two pins at once (I typically use 9 and 3) without messing with the millis() and delay() functions.

Is it possible?

1 Like

Hi @guillemdc

Here's an example that uses dual slope PWM output at 20kHz on digital pins 9 and 10 using the 16-bit timer1. Just change the OCR1x registers between 0 and 399 to get a 0% to 100% duty-cycle:

// Output 20kHz dual slope PWM at 8-bit resolution on digital pins: 9 & 10
void setup() { 
  
  pinMode(9, OUTPUT);                      // Initialise pins 9 and 10
  pinMode(10, OUTPUT);

  TCCR1A = _BV(COM1A1) | _BV(COM1B1);      // Enable the PWM outputs OC1A and OC1B on digital pins 9 and 10
  TCCR1B = _BV(WGM13) | _BV(CS10);         // Set phase and frequency correct PWM and prescaler of 1 on timer 1
  ICR1 = 399;                              // Set the timer 1 frequency to 20kHz: 16MHz / (2 * (399 + 1)) = 20kHz
}

void loop() {
  OCR1A = 100;                              // Set duty-cycle to 25%
  OCR1B = 100;
  delay(2000);                              // Wait for 2 seconds
  OCR1A = 300;                              // Set duty-cycle to 75%
  OCR1B = 300;
  delay(2000);                              // Wait for 2 seconds
}

https://www.gammon.com.au/timers

1 Like

DPin-9 and DPin-10 would be good choice (Fig-1) using 16-bit TC1. Do you want the signals having same phase or opposite phase?
pwm328x
Figure-1:

A scheme (Fig-2) to generate VVVF Signal using TC1.
pwmApp
Figure-2:

The following orderly sequence of TC1 initialization is important for proper operation of the PWM Module:
(1) Reset TCCR1A and TCCR1B to 0s
(2) Set COM1x Bits for Ch-A
(3) Set COM1x Bits for Ch-B
(4) Set the Wave form Mode
(5) Set the values for TOP and duty cycles.
(6) Start TC1 with required prescale/division factor.

If the above order of initialization is not followed, it is possible to come up with non-functioning PWM Module.

16 MHz / 256 = 62.5 kHz Fast PWM or 31.25 kHz Phase-Correct PWM. I'd use Timer2 (Pins 3 and 11) if you don't need 11 for any SPI devices.

Can DPin-11 (refer to UNO Board of Fig-1) be avoided for any SPI device?
spiblk
Figure-1:

Not if you are using SPI for output.

===> Not if you are using SPI for output.
DPin-11 (for UNO Board) is the MOSI (Master-out Slave-in) signal line. Both Master and Slave must use this signal line for data send/receive purpose.

You can leave MOSI disconnected if your SPI peripheral is input only (example input shift register).

First of all, thanks everybody for the quick and kind responses. I didn't expect that many and that fast! To add a little bit of context, the real life problem I have is that I've got a phaser pedal that uses OTAs driven by PWM and a tremolo in which a two vactrols and two transistors are driven by PWM. The PWM signal is the only thing passing from the digital realm to the analog one in the circuit and, somehow, when activated, a faint squeal can be heard in the background. It's dim, but it's there and must be removed. I think that increasing the PWM frequency might rid of it.

@MartinL Could I decrease that ICR1 value to, for instance, 255, so I get something around the 30KHz range by losing some accuracy in the values? Also, you're changing the duty cycle of OCR1A and OCR1B at the same time to the same value, but I reckon those are independent and I could change the duty cycle to anything. I have a stereo tremolo in which I'd like to treat each side slightly differently to create panning effects.

@anon57585045 I've just saved that in my markers. It looks great. Definitely will come back to it as soon as I find an hour or so today.

@GolamMostafa I don't mind the phase in this case. I have a flanger pedal in mind that would require two 50% duty cycle signals of opposite phase varying in frequency. Those would drive a bucket brigade delay chip, but that's a different story.

@johnwasser John, especial thanks to you since this year I've read like a thousand contributions of yours in this forum around this very topic (and I gotta say I've ctrl+c ctrl+v'd some of those).

I don't mind phase correct since I'm just driving a vactrol and a transistor and they just care about the average electrons passing through and opening the gate :). That 62.5 kHz Fast PWM in 3 and 11 looks great to me.

I've got a delay already working that uses a digital resistor I set via SPI. I use PWM to modulate the delay time, but no noise filters through with the "analogWrite" Arduino method since it connects to the digital part of the circuit and doesn't bleed into the audio, so that one is working and I'm not changing the code.

Now, would it be possible to have @johnwasser and @MartinL solutions at the same time and drive 4 independent leds/transistors/whatever independently and beyond the human hearing range? I thought this was impossible but I think I'm starting to get it and my mind's flying with possible uses of this.

Why not eliminate the problem at its source, and use a processor that has a built in DAC peripheral? A passive single pole low pass is a cheesy analog reconstruction filter. I'm assuming you have one?

Hi @guillemdc

If your PWM frequency isn't critical, (so long as it's above audible range) and you'd prefer to have 0 to 255 duty-cycle steps, the easiest way is to change the timer's prescaler and thereafter use the analogWrite() function as normal. This gives an output PWM frequency of 31.25kHz using phase correct PWM.

For example to output 31.25kHz with timer1 on digital pins 9 and 10, just add the following line to the setup() portion on your sketch:

TCCR1B &= ~_BV(CS11);	// Increase the timer1 PWM frequency to 31.25kHz

The same goes for timer2 on digtial pins 3 and 11, by adding these lines to setup():

TCCR2B &= ~_BV(CS22);	// Increase the timer2 PWM frequency to 31.25kHz
TCCR2B |= _BV(CS20);

Thereafter just use the analogWrite() function as normal, e.g.:

analogWrite(9, 128);    // Set the duty-cyle to 50% on digital pin D9

As already mentioned, 4 independent, 31.25kHz PWM outputs is problematic, if you're using SPI, since that already uses digital pin 11. You could use timer0 on digtial pins 5 and 6, but this would mean forgoing the Arduino timing functions such as delay(), micros() or millis() on timer0.

Sketch for 20 kHz signals at DPIn-9 (Ch-A) and DPin-10 (Ch-B inverting) using 16-Bit TC1 in Fast PWM-14. Duty cycle for Ch-A and Ch-B can b varied with Pot1 and Pot2 respectively of Fig-1. (LED1 and LED2 are just for testing purpose of the functioning of the PWM signals by lowering down the frequency to 1 or 5 Hz.)

#define OC1A 9
#define OC1B 10

void setup()
{
  Serial.begin(9600);
  pinMode(OC1A, OUTPUT); //Ch-A
  pinMode(OC1B, OUTPUT); //Ch-B

  TCCR1A = 0x00;   //reset
  TCCR1B = 0x00;   //TC1 reset and OFF
  //fOC1A = clckSys/(N*(1+ICR1)); Mode-14 FPWM; OCR1A controls duty cycle
  // 20 kHz = 16000000/(8*(1+ICR1)) ==> ICR1 = 99, N = 8

  TCCR1A |= (1 << COM1A1) | (0 << COM1A0);  //Ch-A non-inverting, Mode-14
  TCCR1A |= (1 << COM1B1) | (1 << COM1B0); //Ch-B inverting
  TCCR1A |= (1 << WGM11);
  TCCR1B |= (1 << WGM13) | (1 << WGM12); //Mode-14 Fast PWM
  ICR1 = 99;  //changes frequency as ICR changes
  OCR1A = 50;   //~= 50% duty cycle
  OCR1B = 50;
  TCCR1B |= (1 << CS11); //N = 8
}

void loop()
{
  OCR1A = map(analogRead(A0), 0, 1023, 90, 5); //duty cycle: 905 to 5%
  OCR1B = map(analogRead(A1), 0, 1023, 90, 5); //duty cycle: 905 to 5% 
}

pwmApp-1
Figure-1:

Will the above command not override all the previous settings of TC1 and be generating a 490 Hz PWM signal at DPin-9?
pwm328

Hi @GolamMostafa

Yes, but I'm not using any read-modify-write register operations (|= or &= ~) during initialisation, so there's no need to reset the registers to 0.

Though you're right, since technically the TCCR1B register should be be set after the ICR1, since TCCR1B kicks off the timer by setting the prescaler.

Before setup() is launched, the TCs might have been manipulated by the main()/init() function; so, it is better to get them reset first.

I learnt it from one of @cattledog's post having had a problem with a non-functioning PWM Module.

Hi @GolamMostafa

No, since the PWM frequency isn't set in the analogWrite() function itself, but rather initialised in the Arduino core code's wiring.c file prior to running the sketch.

This means that it's possible to simply use analogWrite() function at the higher frequency as normal thereafter.

I know that the execution of analogWrite(9, dutyCycle); command, the DPin-9 will be immediately emitting 490 Hz PWM signal.

Hi @GolamMostafa

But if you add the line or lines of code that I suggest, you'll find it will output PWM at a frequency of 31.25kHz.