Changing Arduino Zero PWM Frequency

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.

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....

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.

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?

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) | ;

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.

Hi rmay_pci,

Pin 9 and 10 control the output of both phase and phase and frequency correct PWM on the Arduino Uno. At the risk of going off topic the following code for the Uno sets up two servos at 50Hz. The ICR1 register controls the frequency, for example: 20000 = 50Hz, 10000 = 100Hz, 2500 = 400Hz. The OCR1A and OCR1B registers control the duty cycle. If you're using servos just load them with a value between 1000 and 2000, with 1500 being the center point of the servo. Or, if you need a 50% duty cycle with a value that is half ICR1. However, using this method prevents you from using libraries that also use timer1, for instance the servo library.

void setup() {
  // Initialise timer 1 for phase and frequency correct PWM
  pinMode(9, OUTPUT);                         // Set digital pin 9 (D9) to an output
  pinMode(10, OUTPUT);                        // Set digital pin 10 (D10) to an output
  TCCR1A = _BV(COM1A1) | _BV(COM1B1);         // Enable the PWM outputs OC1A, and OC1B on digital pins 9, 10
  TCCR1B = _BV(WGM13) | _BV(CS11);            // Set phase and frequency correct PWM and prescaler of 8 on timer 1
  ICR1 = 20000;                               // Set the PWM frequency to 50Hz
  OCR1A = 1500;                               // Centre the servo on D9
  OCR1B = 1500;                               // Centre the servo on D10
}

void loop() {
  OCR1A = 1000;                               // Move the servo to min position on D9 
  OCR1B = 1000;                               // Move the servo to min position on D10
  delay(1000);                                // Wait for 1 second
  OCR1A = 2000;                               // Move the servo to max position on D9
  OCR1B = 2000;                               // Move the servo to max position on D10
  delay(1000);                                // Wait for 1 second
}

Back to the Zero. It's internally more flexible than the older AVR processors, so it does require more extensive set-up, but much of it's just routing the processor's generic clock to the appropriate timer and hooking up the correct pins for output. Once that's out of the way, it's pretty much the same as the AVR.

Here's similar code for the Zero, but sets up 4 servos at 50Hz on digital pins D2, D5, D6 and D7 using the TCC0 timer. The REG_TCC0_PER register is equivalent of ICR1 and the REG_TCC0_CCBx the OCR1x registers:

// Output 50Hz PWM on timer TCC0 (14-bit resolution)
void setup() 
{ 
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(3) |          // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
                    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 4 PWM channels: timer TCC0 outputs
  const uint8_t CHANNELS = 4;
  const uint8_t pwmPins[] = { 2, 5, 6, 7 };
  for (uint8_t i = 0; i < CHANNELS; i++)
  {
     PORT->Group[g_APinDescription[pwmPins[i]].ulPort].PINCFG[g_APinDescription[pwmPins[i]].ulPin].bit.PMUXEN = 1;
  }
  // Connect the TCC0 timer 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[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | 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_DSBOTTOM;    // 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:
  // 20000 = 50Hz, 10000 = 100Hz, 2500  = 400Hz
  REG_TCC0_PER = 20000;      // Set the frequency of the PWM on TCC0 to 50Hz
  while(TCC0->SYNCBUSY.bit.PER);

  // The CCBx register value corresponds to the pulsewidth in microseconds (us)
  REG_TCC0_CCB0 = 1500;       // TCC0 CCB0 - center the servo on D2
  while(TCC0->SYNCBUSY.bit.CCB0);
  REG_TCC0_CCB1 = 1500;       // TCC0 CCB1 - center the servo on D5
  while(TCC0->SYNCBUSY.bit.CCB1);
  REG_TCC0_CCB2 = 1500;       // TCC0 CCB2 - center the servo on D6
  while(TCC0->SYNCBUSY.bit.CCB2);
  REG_TCC0_CCB3 = 1500;       // TCC0 CCB3 - center the servo on D7
  while(TCC0->SYNCBUSY.bit.CCB3);

  // Divide the 16MHz signal by 8 giving 2MHz (0.5us) TCC0 timer tick and enable the outputs
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV8 |    // Divide GCLK4 by 8
                    TCC_CTRLA_ENABLE;             // Enable the TCC0 output
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop() { }

Martini,
Thanks very much for the detailed response. I will try this code with the Zero over the weekend to see if I can get it to work.

What we are doing is running a PID heater control algorithm using the duty cycle of the PWM output (which drives a MOSFET, which drives the heater current). The heater power is directly proportional to the PWM duty cycle, but the system has a horribly slow response (it is more like an oven than a contact heater).

Tuning using a normal PID approach has not worked, so we are trying to get fancier with the algorithm and stumbled with the PWM frequency on the Zero being too fast. I tried a Due which seems to have a default PWM frequency of 1 KHz and everything works very well, but we prefer the Zero for size reasons (and because we have already made a custom "shield" with the same footprint). So if I can get the Zero's PWM frequency down I think we will be in business.

Thanks again for the detailed explanation ... people like you who understand all these details are a very valuable resource for those of us who don't but need to get projects working.
Randy

OK, any advice on how to change the frequency on only pin 13? I've got an application where I need its output to be in the audio band, so somewhere between 100Hz and 10kHz would work nicely. I think I can follow the steps from the example above, but how do I change the PWM ratio? Does analogWrite() still work?

Hi pharaohamps,

The following code outputs a 100Hz PWM signal from timer TCC0, on digital pin 13:

// Output 100Hz PWM on timer TCC0 digital pin D13
void setup() 
{ 
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(3) |          // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
                    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 digital pin 13 (D13): timer TCC0 output
  PORT->Group[g_APinDescription[13].ulPort].PINCFG[g_APinDescription[13].ulPin].bit.PMUXEN = 1;
  
  // Connect the TCC0 timer to the port output - port pins are paired odd PMUO and even PMUXE
  // F & E specify the timers: TCC0, TCC1 and TCC2
  PORT->Group[g_APinDescription[11].ulPort].PMUX[g_APinDescription[11].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_DSBOTTOM;    // 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:
  // 20000 = 50Hz, 10000 = 100Hz, 2500  = 400Hz
  REG_TCC0_PER = 10000;      // Set the frequency of the PWM on TCC0 to 100Hz
  while(TCC0->SYNCBUSY.bit.PER);

  // The CCBx register value corresponds to the pulsewidth in microseconds (us)
  REG_TCC0_CCB3 = 1500;       // TCC0 CCB3 - center the servo on D13
  while(TCC0->SYNCBUSY.bit.CCB3);

  // Divide the 16MHz signal by 8 giving 2MHz (0.5us) TCC0 timer tick and enable the outputs
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV8 |    // Divide GCLK4 by 8
                    TCC_CTRLA_ENABLE;             // Enable the TCC0 output
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop() { }

You can change the PWM ratio or duty-cycle by loading the REG_TCC0_CCB3 register with a value between 0 and whatever value is in the REG_TCC0_PER register. For example, at 100Hz the REG_TCC0_PER register contains 10000, therefore loading the REG_TCC0_CCB3 with 5000 will give you a 50% duty-cycle. The REG_TCC0_CCB3 register can be changed with the following code:

// The CCBx register value corresponds to the pulsewidth in microseconds (us)
  REG_TCC0_CCB3 = 5000;       // TCC0 CCB3 - 50% duty cycle on D13
  while(TCC0->SYNCBUSY.bit.CCB3);

Manipulating TCCx timers at the register level will most likely conflict with analogWrite(). It's really a trade-off, analogWrite() provides both useability and portability, while register manipulation ultimately gives you more flexibility.

The example above gives a 13-bit resolution at 100Hz, (that was good enough for my project), however as the SAMD21's TCC0 and TCC1 are 24-bit timers, it's possible to obtain an even higher resolution, by altering the generic clock divisor and the timer's prescaler.

Thanks for the help! Am I correct in thinking that analogWrite() will still work on pins that I haven't changed manually? I'm fine with the standard 8-bit resolution from analogWrite() even on pin 13, so writing a function to change my mapping from 0-255 to 0-1000 is trivial.

I had a quick look at the analogWrite() code. I think analogWrite() will work provided a given output pin doesn't use the same timer that you're controlling using register manipulation. Should timer conflict be an issue, the SAMD21 is sufficiently flexible on some pins to switch to a different timer, for example digital pin 13 can be switched to output from timer TCC2 instead.

The peripheral multiplexing table can be found on pages 20-22 of the SAMD21 datasheet.

MartinL: Thanks for all the help on this. It's nice to know where to start looking to figure out the registers.

I think I have found a couple of "simpler" solutions, where the actions are really the same, but you can use some of the Arduino machinery to ease some of the coding.

  • The easiest is to just use a pre-scaler on the TCCx of choice, which does not break analogWrite() from my tests:
void setup() {
...
 analogWrite(6, 0); //uses the Arduino code, which sets up GLCK0 for TCC0

 REG_TCC0_CTRLA &= ~TCC_CTRLA_ENABLE;   // Disable the TCC0 output
 while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization

 REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV64 |    // Divide GCLK0 by 64
                   TCC_CTRLA_ENABLE;                    // Enable the TCC0 output
 while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

This will change the default PWM frequency to about 3kHz for all of the pins that use TCC0 for PWM. analogWrite() works as normal, ranging from 0..255. With other choices of pre-scalers, you can get a decent set of PWM frequencies.

Question: Will this break anything else? Since I'm only pre-scaling TCC0, I don't see it affecting GLCK0, per se. If it doesn't, this would be a good solution for several posters who don't necessary care about the exact frequency, but just need something slower (my motor driver can only do 20kHz, max; 3kHz is fine for testing).

  analogWrite(6, 0); //again, use the Arduino machinery
  // The CCBx register value corresponds to the positive clock cycles
  REG_TCC0_PER = 4096;       //D6 is on TCC0; set the period 
  while(TCC0->SYNCBUSY.bit.PER);

  //25% duty cycle
  // The CCBx register value corresponds to the positive clock cycles
  REG_TCC0_CCB2 = 1024;       //D6 is on TCC0 CCB2 
  while(TCC0->SYNCBUSY.bit.CCB2);
  delay(1000);

  //75% duty cycle
  // The CCBx register value corresponds to the positive clock cycles
  REG_TCC0_CCB2 = 3072;       //D6 is on TCC0 CCB2 
  while(TCC0->SYNCBUSY.bit.CCB2);

Using analogWrite() will reset PER to 0xFF, which will clobber any custom value for PER. Still, it only affects the pins on the same TCCx, so it might be easier.

Thanks gcl8a, a nice combination of analogWrite() and register manipulation.

Question: Will this break anything else? Since I'm only pre-scaling TCC0, I don't see it affecting GLCK0, per se.

Pre-scaling the TCC0 timer won't affect GCLK0, but the GCLK0 settings shouldn't be altered as they're used by the Arduino core.

Dear MartinL,

Compliments for your work! This info is sorely needed as ther is little on site for Arduino M0/M0 PRO.

I downloaded both your 50Hz & 100Hz sketches, but they do not compile & stop here:

GCLK_CLKCTRL_ID_TCC0_TCC1;

the message being: Arduino:1.7.8 (Windows 7), Scheda:"Arduino M0"

sketch_M0_PWM_100Hz_1.ino: In function 'void setup()':

sketch_M0_PWM_100Hz_1.ino:24:22: error: 'GCLK_CLKCTRL_ID_TCC0_TCC1' was not declared in this scope

This being the only complaint...any suggestion? I am using IDE 1.7.8.

glovisol

Hi glovisol,

I downloaded both your 50Hz & 100Hz sketches, but they do not compile & stop here:

I just rechecked the code on my Arduino Zero using Arduino.cc's IDE, version 1.6.7 and it compiles OK.

I think the issue might be to do with the fact that you're using Arduino.org's IDE, version 1.7.8.

The definition of GCLK_CLKCTRL_ID_TCC0_TCC1 is actually in an Atmel file called "gclk.h". It's referenced by the Arduino Zero's core, so you shouldn't have to add any includes to your sketch.

On my machine it's currently stored under the directory: C:\Users\Computer\AppData\Local\packages\arduino\tools\CMSIS\4.0.0-atmel\Device\ATMEL\samd21\include\component\gclk.h.

It might be that Arduino M0 Pro/M0 core doesn't reference this file? As I'm not using Arduino.org's IDE its difficult for me to find out where the problem lies. Have you selected the correct board type in the IDE?

Dear MartinL,

Thanks for your prompt reply. Before resorting to Arduino.org's 1.7.8 IDE, I tried for ONE WEEK to have the Arduino M0 (the one without the debug USB connector) work with the 1.6.7 IDE by Arduino.cc, but I was almost driven crazy by continous instability of the COMM port, which would never stay put, so really nothing would work. I painstakingly followed the procedure several times, which in the end makes you fix "board type" and "native Port", but had no success. I use W7 Pro.

Out of desperation I downloaded IDE 1.7.8 and got two boards working faultlessly in one hour. I wrote a quick sketch that would read sensors @ 10 Bit on A0-A5 and write values @ 10 Bit (analogWrite(10)) on as many output pins, but the 187.4 Khz PWM is killing me, as I have to drive Optos. This is a Galvanic Isolator application for precision data logging. If you go to the Picotech forum you will see a lot of my work: USB PC Data Loggers - Pico Technology

I need to bring the PWM freq.down to the 100 Hz region and your work was just getting me started. Well, I have downloaded the Atmel datasheet, the over 1000 pages of it, and will start studying tomorrow...unless you offer me a turnaround.

Kind regards & keep up the good work!

glovisol

Unfortunately the Arduino Zero and M0 use different bootloaders. To use the M0 with the Arduino.cc IDE, I believe you need to burn the bootloader using a programmer such as the Atmel ICE on its SWD port.

If the definition GCLK_CLKCTRL_ID_TCC0_TCC1 is an issue, you could try replacing this:

// 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

with this:

GCLK->CLKCTRL.reg = 0x441A;

This effectively does the same thing, but is less readable.

Dear MartinL,

Thanks a lot!! It works like magic. It compiled at once and the entire sketch loaded immediately. There are obvious differences between the two bootloaders & in the Port Function Multiplexing in the two IDES, as well as, I suspect, in pin enabling. In fact the pins that should have changed frequency are different and those changed show PWM @ 11.76 Khz. It is late in Italy now and I shall investigate tomorrow (afraid of mistakes when tired!). I shall also download the entire sketch tomorrow.

Thank you for your help and fast but extremely effective information!

Kind regards,

glovisol

Hi MartinL,

sketch worked all night, but this morning I attempted to change the 48 Mhz clock divisor and I lost the board. The board is not recognized any more by two different PC's( loaded with IDE 1.7.8) and it is most likely that something in the bootloader went wrong. I know for sure, because the second Arduino M0 board, which I keep as reference, works well on both PC's and uploads normally. I shall now organize a bootloader (so I shall be able to work with IDE 1.6.7 as well) and then report back.

I enclosed sketch using your script.

Cheers,

glovisol

sketch_Galv_Isol_3_DEMO.ino (8.41 KB)

On the Arduino Zero, if you double tap the reset button it will force the SAMD21 to enter the bootloader, (rather than your sketch). I don't know if this is also the case with the Arduino M0?

I ran the set-up portion of your sketch (without the SerialUSB communication) and it works fine. In your code you've used the timer prescaler of 64. This takes the 16MHz GCLK4 signal down to 250kHz. As we're using dual slope PWM, (counter counts up then down and so on), we can calculate the output frequency as follows:

250kHz / (2 * 20000) = 6.25Hz

where:
250kHz is the frequency of timer TCC0
20000 is the value in the PER register
2 is because we're using dual slope PWM

My (very) basic multimeter reads a frequency somewhere in the region of about 6Hz.

Hi MartinL

From my tests right before the crash I found the following, that might be interesting:

With all the unmodified set-ups of your sketch, on this version of Arduino M0 & 1.7.8 IDE, yesterday evening I got 17.5 KHz.

When I started moving constants, nothing happened, save for Prescaler = 64, that brought me down to 1.25 KHz.

I use Picotech PC scope 2000 series, with Picoscope software which reads frequency, Duty Cycle, and many other parameters, so I am pretty sure of freq. readings, apart from seeing the waveform on the display.

Also I noticed that reading on pin 2 kept at 175.4 Khz, while the 17.5 Khz came on pin 4, thus these pins are used differently on the two IDES & Boards.

Same applies to the controls:

Timer TCC must be another one here.
The PER register command does not operate, so another must be used.
The only one that works here is PRESCALER, where DIV8 and DIV64 make the difference.

I am reading Section 6 of the SAMD21 to get a better ida of the works.

Today I have ordered an original Genuino Zero, coming from UK, then it will be possible to tabulate the differences between Boards, IDEs. etc. It will be an interesting exercise.

Unfortunately the "double click" reset trick does not work in my board, which stays recognized no longer by the USB.

Kind regards,

glovisol