Go Down

Topic: Changing Arduino Zero PWM Frequency (Read 39427 times) previous topic - next topic

joelcrouch

I have an application where I need to output a PWM frequency >250kHz.  Using Arduino's analogWrite function only gets me to 187kHz max.  I found the Due has this function, PWMC_ConfigureClocks(), to change the frequency.  Is there something similar for the Zero?

mantoui

#1
Sep 10, 2015, 01:47 am Last Edit: Nov 30, 2015, 08:32 pm by mantoui
I don't think ZERO has an API to set PWM frequency.  (The API for  Teensy ARM products has an analogWriteFrequency(pin,herz),  cold comfort :( )

If you want to roll your own, there is sample code in the thread
ZERO IRrremote
that does pwm at user-selected frequency.

Also in the core packages/arduino/hardware/samd/1.6.1/cores/arduino/Tone.cpp
there is code to set the frequency of TC5, selecting best prescaler and compare value.

MartinL

#2
Sep 10, 2015, 11:15 am Last Edit: Oct 27, 2015, 10:41 am by MartinL
Hi joelcrouch,

Welcome to the Arduino Zero forum.

Both the AVR and the ARM processors are capable of controlling both the phase and frequency using dual slope PWM. If you're interested in reading further, it's detailed in the SAMD21 datasheet, page 660, under the heading "Dual Slope PWM Generation". In dual slope PWM the timer counts up to a given value then counts back down to zero, and so on...

Dual slope PWM on the SAMD21 is provided by the timers TCC0, TCC1 and TCC2. These timers are clocked by one of the processor's generic clocks (GCLK). There are 8 GCLKs in total, 0 to 3 are used by Arduino, but you're free to use the others. It's possible to set up a given GCLK to feed a 48MHz (CPU clock frequency) signal to one of the timers. However, due to the high speed of your 250kHz PWM signal in relation to the 48MHz clock frequency, you'll end up with poor resolution. Here's the calculations:

The frequency for dual slope PWM is determined by:

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

The value in the PER register determines the maximum value the timer counts up to.

In your case N = 1 (as we'd like to clock the timer as fast as possible), therefore...

Frequency = 48MHz / (2 * 1 * 96) = 250kHz

So the PER should be set at 96 (decimal).

The resolution for dual slope PWM is given by:

Resolution = log(PER + 1)/log(2), therefore:

Resolution at 250kHz = log(96 + 1) / log(2) = 6.6 = 6 bits (rounding down)

To change the PWM pulse width (phase), just load the timer's CCBx register with a value between 0 and 96. 0 outputs 0V (0% duty cycle), 96 outputs 3.3V (100% duty cycle). Loading the CCBx register with 48 gives a 50% duty cycle.

The following code sets up GCLK 4 to feed timer TCC0 with 48MHz. The TCC0 is set up for dual slope PWM operation and connected to ouput D7. To control the PWM output just load the REG_TCC0_CCB3 register with a value between 0 and 96. Using my old multimeter I've sucessfully tested the code for 125kHz, however it's not capable of measuring 250kHz and I haven't got a scope, so I'm unable to test it at this higher frequency.

Code: [Select]
// Output 250kHz PWM on timer TCC0 (6-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 D7
  PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;
 
  // Connect the TCC0 timer to digital output D7 - 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_PMUXO_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 = 96;         // Set the frequency of the PWM on TCC0 to 250kHz
  while (TCC0->SYNCBUSY.bit.PER);                // Wait for synchronization
 
  // Set the PWM signal to output 50% duty cycle
  REG_TCC0_CC3 = 48;         // TCC0 CC3 - on D7
  while (TCC0->SYNCBUSY.bit.CC3);                // 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() { }

joelcrouch

Hi MartinL,
Your code snippet works!  I measure 250kHz on oscilloscope.

Thank you,
Joel

joelcrouch

MartinL,
Is there an easy way to assign PWM function to another pin other than 7?  I tried changing number in your two PORT->Group.... statements above, but it didn't work.

Thanks,
Joel

joelcrouch

MartinL,
Running your code breaks analogWrite() function.

MartinL

#6
Sep 10, 2015, 05:30 pm Last Edit: Sep 11, 2015, 12:07 am by MartinL
Yes, it'll break analogWrite(), because it's also trying to use timer TCC0.

The pin mapping for the TCC0, TCC1 and TCC2 are as follows:

REG_TCC0_CCB0 - digital output D2 (Zero Pro/M0 Pro/M0 - digital pin D4)
REG_TCC0_CCB1 - digital output D5
REG_TCC0_CCB2 - digital output D6
REG_TCC0_CCB3 - digital output D7
REG_TCC1_CCB0 - digital output D4 (Zero Pro/M0 Pro/M0 - digital pin D2)
REG_TCC1_CCB1 - digital output D3
REG_TCC2_CCB0 - digital output D11
REG_TCC2_CCB1 - digital output D13

Note: On Arduino.org's Zero Pro/M0 Pro/M0, D2 and D4 are reversed.

Note that first you'll also have to enable TCC1 and TCC2 in a similar manner to TCC0. You can connect TCC2 to GCLK4 using the lines:

Code: [Select]
// Feed GCLK4 to TCC2 (and TC3)
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TCC2 (and TC3)
                     GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                     GCLK_CLKCTRL_ID_TCC2_TC3;    // Feed GCLK4 to TCC2 (and TC3)
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

To connect the timers to their output pins, first enable the port multiplexer on each timer pin:

Code: [Select]
// Enable the port multiplexer for the 8 PWM channels: timer TCC0, TCC1 and TCC2 outputs
  const uint8_t CHANNELS = 8;
  const uint8_t pwmPins[] = { 2, 3, 4, 5, 6, 7, 11, 13 };
  for (uint8_t i = 0; i < CHANNELS; i++)
  {
     PORT->Group[g_APinDescription[pwmPins[i]].ulPort].PINCFG[g_APinDescription[pwmPins[i]].ulPin].bit.PMUXEN = 1;
  }

Then connect all the timer outputs (TCC0, TCC1 & TCC2) to their respective output pins:

Code: [Select]
// Connect the TCC timers to the port outputs - port pins are paired odd PMUO and even PMUXE
  // F & E specify the timers: TCC0, TCC1 and TCC2
  PORT->Group[g_APinDescription[2].ulPort].PMUX[g_APinDescription[2].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;
  PORT->Group[g_APinDescription[4].ulPort].PMUX[g_APinDescription[4].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;
  PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;
  PORT->Group[g_APinDescription[11].ulPort].PMUX[g_APinDescription[11].ulPin >> 1].reg = PORT_PMUX_PMUXO_E | PORT_PMUX_PMUXE_E;

The PMUX registers are arranged in pin pairs odd and even. So for example Arduino's digital pin 13 (D13) is actually the SAMD21 port A, pin 17, or PA17. An odd pin. (See Arduino Zero's schematic diagram). This is paired with its neighboring even pin PA16, (actually digital pin 11). To connect the TCC2 timer to the D13 we have to specify the even SAMD21 pin, in this case PA16 (D11), then connect the timer to D13 using the odd PORT_PMUX_PMUXO_E mask. PMUXO stands for: port multiplexer odd. The E and F suffixes specify that the timers are to be connected.

By the way, the >>1 shift left is actually divide by 2, because there is one PMUX register for each odd and even pair of pins, there's only 16 PMUX registers for 32 pins on a given port.

The g_APinDesciription() function simply converts the Arduino pin numbers into the SAMD21's port and pin representation.


mantoui

@MartinL, is it really necessary to configure GCLK4?  Coudln't one just use GCLK0 which is configured as 48MHz via startup.c?

inquiring minds

MartinL

Hi mantoui

Thanks for pointing this out.

Yes, in this instance you could make the code more efficient by using GCLK0, but if you wanted divide down the GCLK frequency further to obtain a lower frequency using the REG_GCLK_GENDIV register, then I believe you'd have to use one of the free GCLKs. For example, in my project I divide GCLK4's 48MHz by 3 to obtain 16MHz, so that the PWM timing works out the same as the AVR processors on my Arduino Micro and Mega.

Glowtape

#9
Nov 11, 2015, 11:59 pm Last Edit: Nov 11, 2015, 11:59 pm by Glowtape
Hi!

I'm doing some research into the feasibility of programming a quadcopter flight controller based on an Arduino Zero, among other things, all the PWM business to control the ESCs.

If I understood the code above correctly, I can program TCC0 to a specific frequency, tie it to D4-D7 to control four ESCs, and still control the duty cycle of each pin via CCB0-CCB3?

On top of that, given clock division combined with this PER and CCB stuff, I should be able to get very fine duty cycle control at lower PWM frequencies? I'm interested in getting four PWMs going at rates of either 490hz or arbitrary rates below that.

MartinL

#10
Nov 12, 2015, 10:33 am Last Edit: Nov 12, 2015, 11:15 am by MartinL
Yes, the PER/PERB and CCBx registers allow fine control over the PWM duty cycle and frequency. With TCC0, TCC1 and TCC2 it's possible to control up to 8 motors.

If you're using Electronic Speed Controllers (ESCs), setting the generic clock divisor to 3 to get 16MHz and then using the control A register prescaler of 8 is nice, because the duty cycle can be controlled by loading the CCBx registers with a number between 0-2000. This corresponds to a 0-2000us duty cycle required by most ESCs.

So for 490Hz, the PER register value is:

PER = 16MHz / (2 * 8 * 490) = 2040

where 48MHz / 3 (GCLK divisor) = 16MHz and 8 is the control A prescaler

For some reason initialising the PWM using the buffered PERB/WAVEB registers causes a delay of about 5 seconds before the output becomes active. To overcome this I initialise the PWM using the unbuffered PER/WAVE registers, but use the buffered CCBx registers to control the duty cycle. This way the PWM becomes active immediately. Using the buffered CCBx registers are necessary to prevent changes in the duty cycle from causing glitches on your motors.

Glowtape

Cool, thanks for your help. Even tho it's less intuitive to set up than the analogWrite stuff from the Arduino library, the higher precision should be worth it. And should incur less latency from what I read.

shiram

Do I need all that code just to change PWM frequency on the arduino zero?
on the Atmega it was one line:

  TCCR1B = TCCR1B & 0b11111000 | 0x01;

did anyone find a simpler way? it is really hard to adjust that complex code to my needs....

MartinL

#13
Dec 10, 2015, 01:11 pm Last Edit: Dec 10, 2015, 04:08 pm by MartinL
Hi shiram,

On the Atmega devices the TCCRxx registers are used to set up PWM, but for complete control of the PWM phase (duty cycle) and frequency you still need to modify their ICRx and OCRxx registers.

The SAMD microcontroller is internally more flexible than the older AVR ICs, it requires that you first connect a generic clock (GCLK) to the timer peripherals. After that the activation of the port pins and the switching of the port multiplexer is analogous to setting up the TCCRxx registers on Atmegas.

Quote
it is really hard to adjust that complex code to my needs....
I might be able to help. What are the PWM requirements for your project?

rmay_pci

Martini ... you appear to be an expert on the Zero and PWM so maybe there is a simpler method to accomplish what I am trying to do.  I had posted the comments below on another section of the forum and was redirected here, and had tried exactly the same thing (to change the PWM frrequency on the Zero (pin 9) that shiram has listed above based on something I found on another post:

Pins 9 and 10: controlled by timer 1 in phase-correct PWM mode (cycle length = 510)

Setting    Divisor    Frequency
0x01         1        31372.55
0x02         8        3921.16
0x03        64        490.20   <--DEFAULT
0x04       256        122.55
0x05      1024        30.64

TCCR1B = (TCCR1B & 0b11111000) | <setting>;

So I understood (wrongly, apparently) that changing TCCR1B as above would do the trick.  But this line of code doesn't compile as the compiler can't find a declaration of TCCR1B.  All I am trying to do is change the PWM frequency from the default of near 190 KHz (187 KHz is a number I've seen posted elsewhere) down to 490 Hz or 123 Hz (or similar ... somewhere in the low 100s). 

Is there a simple way to do this, or do I need to follow a much more extensive process (which I think you have outlined above but I am not too clear on all of those details).  I did find a library (PWM.h) what was supposed to do this for the Zero, but that also doesn't compile or recognize any of the library functions.  I would have thought changing a PWM frequency would be relatively easy for an Arduino, but maybe the Zero is a different animal. 187 KHz is just too high for my application.  Thanks for any help.

Go Up