PWM 20khz on pin 9 and 10 on Arduino Uno

I have the following code that I have gotten from somewhere on the net. It is set to get 20khz PWM on pin D9. This is fine and I know that the timer change also affects D10. The only issue is the writePWM function only takes the PWM value (0-799) and not pin 9 or 10. Can anyone help alter this code so I can get 20khz on both pin 9 and 10? I will need to send different pwm values to each pin. Here is the code I have:

void writePWM( uint16_t value ) //0-799
{
  if ( (value >= 0) && (value < 800) )
  {
    OCR1A = value;
  }
}

void InitPWM( void )  //get 20khz PWM on pin D9
{
  // Stop the timer while we muck with it

  TCCR1B = (0 << ICNC1) | (0 << ICES1) | (0 << WGM13) | (0 << WGM12) | (0 << CS12) | (0 << CS11) | (0 << CS10);
 
  // Set the timer to mode 14...
  //
  // Mode  WGM13  WGM12  WGM11  WGM10  Timer/Counter Mode of Operation  TOP   Update of OCR1x at TOV1  Flag Set on
  //              CTC1   PWM11  PWM10
  // ----  -----  -----  -----  -----  -------------------------------  ----  -----------------------  -----------
  // 14    1      1      1      0      Fast PWM                         ICR1  BOTTOM                   TOP
 
  // Set output on Channel A to...
  //
  // COM1A1  COM1A0  Description
  // ------  ------  -----------------------------------------------------------
  // 1       0       Clear OC1A/OC1B on Compare Match (Set output to low level).
 
  TCCR1A =
      (1 << COM1A1) | (0 << COM1A0) |   // COM1A1, COM1A0 = 1, 0
      (0 << COM1B1) | (0 << COM1B0) |
      (1 << WGM11) | (0 << WGM10);      // WGM11, WGM10 = 1, 0
 
  // Set TOP to...
  //
  // fclk_I/O = 16000000
  // N        = 1
  // TOP      = 799
  //
  // fOCnxPWM = fclk_I/O / (N * (1 + TOP))
  // fOCnxPWM = 16000000 / (1 * (1 + 799))
  // fOCnxPWM = 16000000 / 800
  // fOCnxPWM = 20000

  ICR1 = 500; //799 for 20khz 500 for 16kHz
 
  // Ensure the first slope is complete

  TCNT1 = 0;
 
  // Ensure Channel B is irrelevant
 
  OCR1B = 0;
 
  // Ensure Channel A starts at zero / off
 
  OCR1A = 0;
 
  // We don't need no stinkin interrupts
 
  TIMSK1 = (0 << ICIE1) | (0 << OCIE1B) | (0 << OCIE1A) | (0 << TOIE1);

  // Ensure the Channel A pin is configured for output
  DDRB |= (1 << DDB1);

  // Start the timer...
  //
  // CS12  CS11  CS10  Description
  // ----  ----  ----  ------------------------
  // 0     0     1     clkI/O/1 (No prescaling)

  TCCR1B =
      (0 << ICNC1) | (0 << ICES1) |
      (1 << WGM13) | (1 << WGM12) |              // WGM13, WGM12 = 1, 1
      (0 << CS12) | (0 << CS11) | (1 << CS10);
}

I just wish I understood what all this stuff means....

Enable PWM output on OC1B:
(1 << COM1B1) | (0 << COM1B0) |

Modify the writePWM() function to take a pin argument or write a second function for Pin 10. To set Pin 10, use OCR1B instead of OCR1A.

1 Like

So I just modify this part?

 TCCR1A =
      (1 << COM1A1) | (0 << COM1A0) |   // COM1A1, COM1A0 = 1, 0
      (0 << COM1B1) | (0 << COM1B0) |
      (1 << WGM11) | (0 << WGM10);      // WGM11, WGM10 = 1, 0

Do I change the (1<<COM1A1.. line to 0<<COM1A1 then the next line to (1<<COM1B1...? (in the new function)?

It is all in the data sheet for the processor.
Basically it is setting bits in the timer control register.

If you want to set the PWM frequency to a specific value then you will loose the ability to use the full range of duty cycle values.

If you need to keep te full range then there are only a range of specific PWM frequencies you can use by altering the timer pre scale register.

It is a trade off. How important is each, PWM frequency of full duty cycle adjustment, to your project? You can only have one.

Only if you want to turn off PWM on Pin 9 as well as turn on PWM on Pin 10. I don't think you want to do that.

I now have this:

TCCR1A =
      (1 << COM1A1) | (0 << COM1A0) |   // COM1A1, COM1A0 = 1, 0
      (1 << COM1B1) | (0 << COM1B0) |
      (1 << WGM11) | (0 << WGM10);      // WGM11, WGM10 = 1, 0

and have changed the name of the writePWM function to writePWM9 and copied to another named writePWM10 which have:

void writePWM9( uint16_t value ) //0-799
{
  if ( (value >= 0) && (value < 800) )
  {
    OCR1A = value;
  }
}

void writePWM10( uint16_t value ) //0-799
{
  if ( (value >= 0) && (value < 800) )
  {
    OCR1B = value;
  }
}

Is this correct?  I am not getting anything out of pin 10.

I do notice in the original it has:

 // Ensure the Channel A pin is configured for output
  DDRB |= (1 << DDB1);

Do I need to do anything for Channel B pin?

You need to set both Pin 9 and Pin 10 to OUTPUT.

   pinMode(9, OUTPUT);
   pinMode(10, OUTPUT);

I added:

 DDRB |= (1 << DDB2);

and it is now working. Thanks!

(multiple places in your code)

left shifting a zero does nothing. Since you are doing assignment, it works but is the same as

TCCR1A = (1 << COM1A1) | (1 << COM1B1) | (1 << WGM11) ;

you also have this

void writePWM( uint16_t value ) //0-799
{
  if ( (value >= 0) && (value < 800) )

but since value is unsigned it will always be greater than or equal to zero, save as

if ( value < 800 )

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.