How to get 20khz PWM on pin D9

Do you need PWM output on pin 10?

No, just pin 9. The VHN2SP30 only uses 1 pin for PWM, then 2 other pins for enabling each side. I already sent off for my pcboard, or I'd say any PWM pin would work. I guess I could make a new board if 20Khz PWM isn't possible on pin 9.

Call this from setup to initialize the timer...

void analogWriteSAH_Init( void )
{
  // 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 = 799;
  
  // 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);
}

Call this to change the output value...

void analogWriteSAH( uint16_t value )
{
  if ( (value >= 0) && (value < 800) )
  {
    OCR1A = value;
  }
}

The range is zero to 799.

SouthernAtHeart:
I've spent an hour reading through pages like "secretOfArduinoPWM", "PwmFrequency", "TimerPWNCheatsheet", About all I could get was that something called TCCR1B affects the timer1 which operates pin 9. This is WAY over my head. I've seen different examples, but I don't even know which of them to start with.

That's a good start. Each of the three timers has two PWM outputs (A and B). For Pin 9 you have to look at which timer controls it:

That spreadsheet shows that on an UNO the Pin 9 PWM comes from Timer 1 output A (T1A) and on the ATmega it's known as Port B Pin 1 (PB 1). If you switch to a Mega you need to change to Timer 2 output B (T2B) which on the ATmega is known as Port H Pin 6 (PH 6).

Knowing you want Timer/Counter 1 you read that section of the datasheet. It talks about the Timer Counter Registers: TCCR1A, TCCR1B, and TCCR1C (they had too many bit to fit in one byte so they made it into three registers).
TIMSK1: The Interrupt Mask Register. Since you aren't using interrupts this remains 0.
TIFR1: Interrupt Flag Register. Since you aren't using interrupts this isn't important.
Since it is a 16-bit counter the other registers are two bytes each:
TCNT1: The actual timer/counter count
OCR1A: The 'A' Output Compare Register that is associated with Pin 9
OCR1B: The 'B' Output Compare Register associated with a different pin.
ICR1: The Input Capture Register. In out case we are using it to set the TOP of the counting range so the counter doesn't go all the way to 65535.

Thanks for the help on this! I'll test it out.

Call this to change the output value...

This won't be changing the frequency, but the motor speed?

So then instead of using

AnalogWrite(motorSpeed); //motorSpeed is 0 to 255

I'll use this?

analogWriteSAH(motorSpeed); //motorSpeed is 0 to 799

Many thanks.

SouthernAtHeart:
Thanks for the help on this!

You are welcome.

I'll test it out.

Please do (thoroughly test it). I only had the time to perform a very basic test with an LED.

Call this to change the output value... This won't be changing the frequency, but the motor speed?

Exactly. The frequency is set in analogWriteSAH_Init to 20K Hz (assuming I didn't make a mistake).

So then instead of using

analogWrite( 9, motorSpeed ); //motorSpeed is 0 to 255

I'll use this?

analogWriteSAH( motorSpeed ); //motorSpeed is 0 to 799

Exactly.

Just to confirm, you have a VNH2SP30 (this is a snippet of the VNH3SP30 and VNH2SP30 Comparison from Pololu's website)...

VNH3SP30 VNH2SP30
... ... ...
Maximum PWM frequency 10 kHz 20 kHz
... ... ...

thanks. Yes, I got the VNH2SP30 for that very reason, that it goes up to 20Khz.
I don't have a scope, but I do have a Fluke meter that measures frequency. I've never used that function, but I'll have to test it out, it'll be good learning.

Hi guys!
Your answers are great :slight_smile: and very useful as I have the very same VNH2SP30 chip on my motomonster Hbridge (sparkfun https://www.sparkfun.com/products/10182)

but I would like to drive two motors! I have read in the code that "Channel B is irrelevant" and I'm lost...

Could you please tell me what to implement in order to have D9 and D10 ultrasonic's POWER

Many thanks!
Mat

Call from setup...

void analogWriteSAH_Init( void )
{
  // 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 and B to...
  //
  // COM1z1  COM1z0  Description
  // ------  ------  -----------------------------------------------------------
  // 1       0       Clear OC1A/OC1B on Compare Match (Set output to low level).
  
  TCCR1A = 
      (1 << COM1A1) | (0 << COM1A0) |   // COM1A1, COM1A0 = 1, 0
      (1 << 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 = 799;

  // Ensure the first slope is complete

  TCNT1 = 0;

  // Ensure Channel A and B start at zero / off

  OCR1A = 0;
  OCR1B = 0;

  // We don't need no stinkin interrupts

  TIMSK1 = (0 << ICIE1) | (0 << OCIE1B) | (0 << OCIE1A) | (0 << TOIE1);

  // Ensure the Channel A and B pins are configured for output
  DDRB |= (1 << DDB1);
  DDRB |= (1 << DDB2);

  // 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);
}
1 Like

Channel A output...

void analogWriteSAHA( uint16_t value )
{
  if ( (value >= 0) && (value < 800) )
  {
    OCR1A = value;
  }
}

Channel B output...

void analogWriteSAHB( uint16_t value )
{
  if ( (value >= 0) && (value < 800) )
  {
    OCR1B = value;
  }
}
1 Like

Thanks Coding Badly for your quick reply! and commented code 8)

Wonderful!
It seems that because the 2 pins are sharing the same timer (PWM T1), it's quite short to implement: excellent! I woudn't have dared to mess with the code: registry frighten me!

I'll have a try now with my Nano ATMega 328

To be compliant with the rest of my code, can I use this (map and constrain) without decreasing the overall performance?

void analogWriteSAHA( uint8_t value )
{
  //  My variable value varies from 0 to 255
  //  but awaited range in OCR1A is from 0 to 799 and nothing else!
    OCR1A = constrain(map(value, 0, 255, 0, 799), 0, 799);
}

Thks :slight_smile:

Here is my test: it compiles perfectly, upload ok, but no moves...

The direction leds (green/red) are less bright... than usual.
When a motor is connected, the leds don't light at all...

it must be kryptonite somewhere close... :grin:

After that, I modified ICR1 to 1599 to get 10kHz (and OCR1A max in the function)
but the behaviour is the same ;-(

... I'll have to buy an oscilloscope $$

This website has been reported as unsafe
Phishing threat:
This site might try to trick you into disclosing your login, password or other sensitive information by disguising itself as another website you may trust.

Instead of hosting images off-site please attach them to your post. Click Modify to change your post, click Addition Options, click Browse to select the file, then click Save.

Mat13:
The direction leds (green/red) are less bright... than usual.
When a motor is connected, the leds don't light at all...

Test with two LEDs (and series resistors) using the Fade example. If the LEDs get dimmer / brighter then at least you know the pulse width modulation is working.

Yes, good idea! XD

I didn't succeeded in fading the LED.
With the Analogwrite, it is Ok: LED is fading
With analogWriteSAHA, the LED stays off

Maybe my Arduino Nano V3.0 funduino is not working...

Thanks for any hint!
yours
Mat

int led = 9;           // the pin that the LED is attached to
int brightness = 0;    // how bright the LED is
int fadeAmount = 5;    // how many points to fade the LED by

void analogWriteSAH_Init( void )
{
  TCCR1B = (0 << ICNC1) | (0 << ICES1) | (0 << WGM13) | (0 << WGM12) | (0 << CS12) | (0 << CS11) | (0 << CS10);
  TCCR1A = 
    (1 << COM1A1) | (0 << COM1A0) |   // COM1A1, COM1A0 = 1, 0
  (1 << COM1B1) | (0 << COM1B0) |
    (1 << WGM11) | (0 << WGM10);      // WGM11, WGM10 = 1, 0
  
  ICR1 = 799;
  TCNT1 = 0;

  OCR1A = 0;
  OCR1B = 0;
  TIMSK1 = (0 << ICIE1) | (0 << OCIE1B) | (0 << OCIE1A) | (0 << TOIE1);

  DDRB |= (1 << DDB1);
  DDRB |= (1 << DDB2);

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

void analogWriteSAHA( uint16_t value )
{
  if ( (value >= 0) && (value < 800) )
  {
    OCR1A = value;
  }
}
// the setup routine runs once when you press reset:
void setup()  {
  analogWriteSAH_Init;
  // declare pin 9 to be an output:
  pinMode(led, OUTPUT);
}

// the loop routine runs over and over again forever:
void loop()  {
  // set the brightness of pin 9:
 analogWriteSAHA(brightness);  
// analogWrite(led, brightness);  
  // change the brightness for next time through the loop:
  brightness = brightness + fadeAmount;

  // reverse the direction of the fading at the ends of the fade:
  if (brightness == 0 || brightness == 795) {
    fadeAmount = -fadeAmount ;
  }    
  // wait for 30 milliseconds to see the dimming effect    
  delay(30);                            
}

You forgot something...

void setup()  {
  analogWriteSAH_Init();
...

Thanks but unfortunatly, it was already in the code... there must be something else somewhere else :~

maybe in the wiring.c or .h files? or a previous tweak i may have done in serial file...
I'll do a reinstall!
and meanwhile I'll ask a friend to do the same :wink:

// the setup routine runs once when you press reset:
void setup()  {
  analogWriteSAH_Init;
  // declare pin 9 to be an output:
  pinMode(led, OUTPUT);
}

Mat13:
Thanks but unfortunatly, it was already in the code... there must be something else somewhere else :~

Reread my previous post. More carefully this time. I promise you forgot something. Just not what you think.

Oh myGod! I forgot the brackets!
Thanks for having spotted that Coding Badly! 8)

Ultrasonic fading led is now working!
Tomorrow a video with the Hbridge hopefully!!