0

Changing PWM Frequency on Feather M0
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Changing PWM Frequency on Feather M0

by Don_at_Noria on Thu Sep 01, 2016 4:04 pm

Setting up a simple PWM output (I happen to be using D5 or D6) and testing with a DMM capable of frequency and duty cycle measurements, I am seeing a default PWM frequency of about 732 Hz. I need to change this to about 30 kHz to correctly drive a BLDC motor outside the audible range.

I've been reading the SAM D21 data sheet and can see a number of (too many?) ways where the PWM frequency could be scaled/changed, but I realize I don't know how the timers are configured by default in the Arduino SAMD core and/or if they are further modified in the Adafruit board files. Any information on how they are configured would be most appreciated. Simple answers on how to set the PWM frequency I need while maintaining the analogWrite() function are welcome too, of course.

Thanks,
Don P.

Don_at_Noria
 
Posts: 21
Joined: Sat Aug 06, 2016 11:18 am

Re: Changing PWM Frequency on Feather M0

by adafruit_support_mike on Fri Sep 02, 2016 4:10 am

The gory details are in: Arduino15/packages/adafruit/hardware/samd/1.0.13/cores/arduino/wiring_analog.c

The standard PWM settings set the prescaler to 1/256 division. If you set it to about 1/8 it should put the frequency in the 20kHz-30kHz range.

adafruit_support_mike
 
Posts: 42178
Joined: Thu Feb 11, 2010 2:51 pm

Re: Changing PWM Frequency on Feather M0

by Don_at_Noria on Fri Sep 02, 2016 2:48 pm

Thanks for directing me to that. I'm not sure how long it would have taken me to find it by myself.

Changing the two instances of DIV256 in that file to DIV8 does change the PWM frequency to 23.42 kHz. At some point I will dig deeper into the code and clock configs to get it all the way to 30 kHz to be sure that nobody can hear it, but this will do for now.

Thanks,
Don P.

Don_at_Noria
 
Posts: 21
Joined: Sat Aug 06, 2016 11:18 am

Re: Changing PWM Frequency on Feather M0

by mailhouse on Fri Sep 02, 2016 8:47 pm

pick a different pin if you dont want to break analogWrite() since analogWrite() uses TCC0. See the tables in section 6 of the atsamd21 data sheet.

mailhouse
 
Posts: 104
Joined: Sat Jul 25, 2015 8:06 pm

Re: Changing PWM Frequency on Feather M0

by adafruit_support_mike on Sat Sep 03, 2016 1:28 am

If you drop the division to 1/4 it will kick the frequency up to about 46kHz.

Division ratios that aren't powers of two are harder.

adafruit_support_mike
 
Posts: 42178
Joined: Thu Feb 11, 2010 2:51 pm

Re: Changing PWM Frequency on Feather M0

by Don_at_Noria on Sat Sep 03, 2016 7:26 pm

The PWM input of the integrated motor driver chip is only spec'ed to 30 kHz, although I had it up to almost 32 kHz at one point with an ATmega328p (Uno) in an earlier test.

This work-around is enough for now. We are likely to go with a more advanced sinusoidal driver chip and/or we will create a custom clock > timer > PWM channel path using the Atmel registers to get exactly the frequency we need. The use of analogWrite() - and the Arduino IDE in general - is only for speed and ease of prototyping.

Thanks to everyone for the input.

Don P.

Don_at_Noria
 
Posts: 21
Joined: Sat Aug 06, 2016 11:18 am

Re: Changing PWM Frequency on Feather M0

by mailhouse on Mon Sep 05, 2016 4:26 pm

Don_at_Noria wrote:create a custom clock > timer > PWM channel path using the Atmel registers to get exactly the frequency we need.


Frequency = GCLK frequency / (2 * N * PER) where N = prescaler value (CTRLA register)

Resolution = log(PER + 1)/log(2)

REG_TCC0_CCB0 - digital output D2
REG_TCC0_CCB1 - digital output D5
REG_TCC0_CCB2 - digital output D6
REG_TCC0_CCB3 - digital output D7
REG_TCC1_CCB0 - digital output D4
REG_TCC1_CCB1 - digital output D3
REG_TCC2_CCB0 - digital output D11
REG_TCC2_CCB1 - digital output D13

So if I did my math right, and there's a good chance I didn't, this code should give you 30kHz pwm on pin D6 but it breaks AnalogWrite() to pins that output TCC0;

Code: Select all | TOGGLE FULL SIZE
// Output 30kHz PWM on timer TCC0 (9-bit resolution)
void setup()
{
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // Divide the 48MHz clock source by divisor 1: 48MHz/1=48MHz
                    GCLK_GENDIV_ID(4);            // Select Generic Clock (GCLK) 4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                     GCLK_GENCTRL_GENEN |         // Enable GCLK4
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                     GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Enable the port multiplexer for the digital pin D6
  PORT->Group[g_APinDescription[6].ulPort].PINCFG[g_APinDescription[6].ulPin].bit.PMUXEN = 1;
 
  // Connect the TCC0 timer to digital output D6 - port pins are paired odd PMUO and even PMUXE
  // F & E specify the timers: TCC0, TCC1 and TCC2
  PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg = PORT_PMUX_PMUXE_F;

  // Feed GCLK4 to TCC0 and TCC1
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TCC0 and TCC1
                     GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                     GCLK_CLKCTRL_ID_TCC0_TCC1;   // Feed GCLK4 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Dual slope PWM operation: timers countinuously count up to PER register value then down 0
  REG_TCC0_WAVE |= TCC_WAVE_POL(0xF) |         // Reverse the output polarity on all TCC0 outputs
                    TCC_WAVE_WAVEGEN_DSBOTH;    // Setup dual slope PWM on TCC0
  while (TCC0->SYNCBUSY.bit.WAVE);               // Wait for synchronization

  // Each timer counts up to a maximum or TOP value set by the PER register,
  // this determines the frequency of the PWM operation:
  REG_TCC0_PER = 800;         // Set the frequency of the PWM on TCC0 to 30kHz
  while (TCC0->SYNCBUSY.bit.PER);                // Wait for synchronization
 
  // Set the PWM signal to output 50% duty cycle
  REG_TCC0_CC2 = 400;         // TCC0 CC2 - on D6
  while (TCC0->SYNCBUSY.bit.CC2);                // Wait for synchronization
 
  // Divide the 48MHz signal by 1 giving 48MHz (20.83ns) TCC0 timer tick and enable the outputs
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |    // Divide GCLK4 by 1
                    TCC_CTRLA_ENABLE;             // Enable the TCC0 output
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization

}

void loop() {

}
Last edited by mailhouse on Mon Sep 05, 2016 10:31 pm, edited 1 time in total.

mailhouse
 
Posts: 104
Joined: Sat Jul 25, 2015 8:06 pm

Re: Changing PWM Frequency on Feather M0

by Don_at_Noria on Mon Sep 05, 2016 5:07 pm

Thank you for taking the time to write that all down. I have studied it briefly and will continue to do so at greater length.

Having studied it a bit, I do have a few questions. First, in the following few lines, it looks like you are initially setting a 50% duty cycle. In my project, however, I would like a 100% initial duty cycle since the fan control is actually active low and I would want the fans to be off immediately upon startup. How would this code differ for an initial 100% duty cycle?

Code: Select all | TOGGLE FULL SIZE
  REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                     GCLK_GENCTRL_GENEN |         // Enable GCLK4
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                     GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
Later, there is another line that sets a 50% duty cycle. Is this the line I would use in loop() or while(1) to vary the duty cycle, i.e. fan speed, as needed?

Code: Select all | TOGGLE FULL SIZE
  // Set the PWM signal to output 50% duty cycle
  REG_TCC0_CC2 = 400;         // TCC0 CC2 - on D6
  while (TCC0->SYNCBUSY.bit.CC2);                // Wait for synchronization
Thanks again,
Don P.

Don_at_Noria
 
Posts: 21
Joined: Sat Aug 06, 2016 11:18 am

Re: Changing PWM Frequency on Feather M0

by mailhouse on Mon Sep 05, 2016 6:36 pm

the first is for the generic clock that feeds TCC0, the other is for the PWM signal that TCC0 is outputting.

and yes those are the lines for the loop. the duty cycle is from 1 to PER so setting REG_TCC0_CC2 to 800 instead of 400 would be 100% duty cycle.

please let me know your results as i could be completely off base here

mailhouse
 
Posts: 104
Joined: Sat Jul 25, 2015 8:06 pm

Re: Changing PWM Frequency on Feather M0

by Don_at_Noria on Tue Sep 06, 2016 9:01 am

I will share my results, although it might be a little while before I can test it in depth.

Thanks,
Don P.

Don_at_Noria
 
Posts: 21
Joined: Sat Aug 06, 2016 11:18 am

Re: Changing PWM Frequency on Feather M0

by Don_at_Noria on Thu Sep 22, 2016 4:32 pm

Just realized that I totally forgot to reply.

The code provided by mailhouse worked exactly as expected. I was also able to change the period (frequency) and change the duty cycle within loop().

Thanks again for the help.

Don_at_Noria
 
Posts: 21
Joined: Sat Aug 06, 2016 11:18 am

Please be positive and constructive with your questions and comments.