Faster PWMs on Arduino Uno. 100khz+ on pins 3 9 10 & 11

I’ve already set the prescaler for 39Khz PWMs on pins 3 9 10 & 11. (20mhz crystal)
I’ve read that you can sacrifice some bit depth for faster PWMs, but I’ve not found a way to do it with out breaking the functionality of one of the PWM pins.

I am using the ATMEGA328 to change levels and filter values in a high gain audio circuit with high speed analog switches (CD4066.) I don’t want to use pins 5 & 6 because I’m worried about inter-modulation if different PWM frequencies are used. 61

The problem - aliasing when pin 10’s duty cycle is around 35 - 65%.

Audio bandwidth rolls off steeply at around 7Khz. It seems this would have worked, but I didn’t take the amount of gain in to consideration. After testing with an external oscillator at around 100Khz the aliasing disappears.

Is there a way to increase all four PWM frequencies and still have variable duty cycle? A bit depth of 6, or even 4 would be acceptable if I could get 100Khz or higher.

Simplified version of my code if it would be helpful

int a = 0; // variable a declared and will be used as the value to delcare analog input on pin 0

void setup() {
  //sets timer 1 & 2 prescaler for pwms on pins 3 9 10 11 ~39Khz with 20mhz crystal.
  TCCR1B = TCCR1B & 0b11111000 | 0x01;
  TCCR2B = TCCR2B & 0b11111000 | 0x01;

  // assign pin modes
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);			// pwm pin
  pinMode(7, OUTPUT);			
  pinMode(8, OUTPUT);
  pinMode(10, OUTPUT);			// pwm pin
  pinMode(9, OUTPUT);			// pwm pin
  pinMode(11, OUTPUT);			// pwm pin
  pinMode(12, OUTPUT);
}

void loop() {

  a = analogRead(modein);	// read analog input to set mode
  
  //sets mode 1 bright drive
  while (a >= 0 && a <= 2) {
    digitalWrite(2, LOW);  // switch between output 1 or 2.
    digitalWrite(7, HIGH);   // turns on feedback loop diodes.
    digitalWrite(8, HIGH);    // turns on output low pass filter.
    digitalWrite(12, LOW);  // turn on audio level boost.
    analogWrite(3, 117);   // bass level control            		~pwm
    analogWrite(10, 51);  // highpass filter frequency				~pwm
    analogWrite(9, 204); //  prebass level 							~pwm
    analogWrite(11, 107); // output level boost						~pwm
    a = analogRead(modein);

  }
  
  //  repeats ten more while statements depending on modein value. Left out for simplicity

I have already tried the pwm library here. https://forum.arduino.cc/index.php?topic=117425.0

I don’t see it documented anywhere, but it doesn’t seem to work when using all four pins.

I’ve tried several OCRxx register hacks, but I don’t really understand them. They all seemed to break functionality of at least one pin.

I’ve read this over and over, but don’t understand it and can’t seem to find any working examples based on it. PWM On The ATmega328 - QEEWiki

Any help would be greatly appreciated.

Thanks

How does 78125 Hz grab you? Close enough to 100000 Hz?

It's worth a try.
The filter range was actually larger than necessary. I reduced it in the analog circuit and it's helped some, but the problem is still there at 39KHz.

Any advice would be appreciated.

thank you

Getting to that higher frequency is just one more step from what you already have: change timer 1 and timer 2 to Fast PWM (instead of the default Phase Correct PWM) (timer 0 is already initialized to Fast). If you need help with that just ask.

Any of the ICR regsiter modes will allow faster PWM without sacrificing an OCR register - this is a
case where the datasheet section(s) need careful reading (2 or 3 times through), to get a good understanding
of the flexible timer/PWM units.

If your frequency range is upto 7kHz, and 39kHz is causing aliasing, you probably don't have a
very steep anti-aliasing filter in place, or there are some artifacts up at 30kHz from some other
part of the circuit?

Added the following lines of code. Perhaps this will be useful to others later. :slight_smile:

  TCCR1B |= (1 << WGM12);
  TCCR2A |= (1 << WGM21);

Kind of hacky as I set register bits differently in earlier code, but it works.
Definitely some more learning to do as I’m not confident I understand the syntax.

Thank you so much for the advice.

MarkT:
Any of the ICR regsiter modes will allow faster PWM without sacrificing an OCR register - this is a
case where the datasheet section(s) need careful reading (2 or 3 times through), to get a good understanding
of the flexible timer/PWM units.

If your frequency range is upto 7kHz, and 39kHz is causing aliasing, you probably don’t have a
very steep anti-aliasing filter in place, or there are some artifacts up at 30kHz from some other
part of the circuit?

There’s a 7khz 2 pole MFB lowpass filter on the output, and a potentiometer controlled RC filter prior to that which maxes out around 10KHz.
The problem is gain. Aliasing was not an issue when the signal level was low. The circuit has a total voltage gain of about 5000, and intentionally distorts, so there’s definitely some serious THD going on.

I guess my use of the word bandwidth made it sound like there’s no information above 7KHz. There is some, but the rolloff is at least -12db per octave.

subdecay:
Kind of hacky...

When initializing peripherals like the timers you will be better served setting all the associated registers instead of manipulating individual bits. In the case of timers, first disabling the timer (by setting Clock Select to No Clock Source / zero) is also a good choice to avoid output and interrupt quirks.

Thank you so much for the advice.

You are welcome.

and that would simply be done like this?

register name = 0;
register name = 8 bit number;

It seems I've found very few examples that do it this way, but seems far simpler... and reminds me of using poke commands on the commodore 64, which is where I first learned programming eons ago.

This is how I might configure timer 1…

  // Ensure timer 1 is off while we manipulate it
  TCCR1B = 0;

  // Ensure no glitch when we turn it back on
  TCNT1 = 0

  // Ensure no interrupts; we don't need them
  TIMSK1 = 0;

  // COM1A = 00 --> Normal port operation, OC1A disconnected.  The core changes this as needed.
  // COM1B = 00 --> Normal port operation, OC1B disconnected.  The core changes this as needed.
  // WGM1 = 0101 --> Fast PWM, 8-bit 0x00FF BOTTOM TOP
  TCCR1A = 
      (0 << COM1A1) | (0 << COM1A0) | 
      (0 << COM1B1) | (0 << COM1B0) |
      (0 << WGM11) | (1 << WGM10);

  // No capture input.
  // WGM1 = 0101 --> Fast PWM, 8-bit 0x00FF BOTTOM TOP
  // CS1 = 001 --> clk/1 (No prescaling)
  TCCR1B =
      (0 << ICNC1) | (0 << ICES1) |
      (0 << WGM13) | (1 << WGM12) |
      (0 << CS12) | (0 << CS11) | (1 << CS10);

The majority of that (register names, bit names, descriptions) is copied from the datasheet.

The verbosity makes the intent crystal clear to the future you (when you are debugging / maintaining) and other people using your code.