Go Down

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

MartinL

#15
Feb 12, 2016, 10:58 am Last Edit: Feb 12, 2016, 02:08 pm by MartinL
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.

Code: [Select]
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:

Code: [Select]
// 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() { }



rmay_pci

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

pharaohamps

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?

MartinL

#18
Feb 17, 2016, 09:08 pm Last Edit: Feb 17, 2016, 09:29 pm by MartinL
Hi pharaohamps,

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

Code: [Select]
// 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:

Code: [Select]
// 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.

pharaohamps

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.

MartinL

#20
Feb 26, 2016, 10:15 am Last Edit: Feb 26, 2016, 10:17 am by MartinL
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.

gcl8a

#21
Feb 28, 2016, 05:36 am Last Edit: Feb 29, 2016, 01:33 pm by gcl8a
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:

Code: [Select]

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

- Another option is to note that the Arduino code (see https://github.com/arduino/ArduinoCore-samd/blob/master/cores/arduino/wiring_analog.c) uses a default value of 0xFF for the PER register. This can be changed manually as you have shown above, but then one has to set the CCBx register manually. Still, it saves a little of the setting things up. To do PWM on D6, for example:

Code: [Select]
 
  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.

MartinL

#22
Feb 28, 2016, 05:58 pm Last Edit: Feb 28, 2016, 05:58 pm by MartinL
Thanks gcl8a, a nice combination of analogWrite() and register manipulation.

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

glovisol

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





MartinL

#24
Mar 09, 2016, 06:04 pm Last Edit: Mar 09, 2016, 06:27 pm by MartinL
Hi glovisol,

Quote
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?

glovisol

#25
Mar 09, 2016, 06:56 pm Last Edit: Mar 09, 2016, 07:43 pm by glovisol
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: https://www.picotech.com/support/forum31.html

 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


MartinL

#26
Mar 09, 2016, 08:04 pm Last Edit: Mar 09, 2016, 08:05 pm by MartinL
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:

Code: [Select]
// 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:

Code: [Select]
GCLK->CLKCTRL.reg = 0x441A;
This effectively does the same thing, but is less readable.

glovisol

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

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

MartinL

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.

Go Up