Go Down

Topic: [SOLVED] MKR1000 PWM Timers (Read 3394 times) previous topic - next topic

tryo1

Feb 11, 2017, 05:09 pm Last Edit: Feb 12, 2017, 06:08 pm by tryo1 Reason: Solved! Case closed
Hi,

I've been trying to use PWM to control a fan from my MKR1000, and I was wondering if anyone knew which pins were connected to which timer. I ask because I want to control a computer fan which requires a 25kHz (or thereabouts - more 24-28kHz) PWM frequency.

I know how to change the TOP/prescaler values of a timer to get the right PWM, but only on a Mega2560 and an Uno. I also know there are 7 configurable timers on a SAMD21, but I can't seem to find any helpful diagram to detail which timer goes to which pin. On the MKR1000 pins, that is - I've already found the basic pinout diagrams for the SAMD21 in the datasheet

Could anyone point me in the right direction?

MartinL

#1
Feb 11, 2017, 07:03 pm Last Edit: Feb 11, 2017, 07:10 pm by MartinL
Hi tryo1,

The following code will give you a 50% duty cycle PWM signal, at 25kHz on pin D6 of your MKR1000:

Code: [Select]
// Output 25kHz PWM on digital pin D6 using timer TCC0 (10-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 TCC0 PWM channel 2 (digital pin D6), SAMD21 pin PA20
  PORT->Group[g_APinDescription[6].ulPort].PINCFG[g_APinDescription[6].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[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

  // Normal (single slope) PWM operation: timer countinuouslys count up to PER register value and then is reset to 0
  REG_TCC0_WAVE |= TCC_WAVE_WAVEGEN_NPWM;         // Setup single 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 (period) register,
  // this determines the frequency of the PWM operation:
  // 1919 = 25kHz
  REG_TCC0_PER = 1919;      // Set the frequency of the PWM on TCC0 to 25kHz
  while(TCC0->SYNCBUSY.bit.PER);

  // The CCBx register value determines the duty cycle
  REG_TCC0_CCB2 = 959;       // TCC0 CCB2 - 50% duty cycle on D6
  while(TCC0->SYNCBUSY.bit.CCB2);

  // Divide the 48MHz signal by 1 giving 48MHz (20.8ns) 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() { }

To change the duty cycle just load the buffered update register for timer TCC0 channel 2 with a number between 0 (0% duty cycle) and 1919 (100%):

Code: [Select]
REG_TCC0_CCB2 = 1439;       // TCC0 CCB2 - 75% duty cycle on D6
while(TCC0->SYNCBUSY.bit.CCB2);



tryo1

That's more complete a solution than I could have hoped for, MartinL - thank you! I'm waiting on a fet with a lower threshold voltage before I can try this out, I'll let you know how the bench test goes when I can!

As an aside, could you tell me more about the code? I'm looking at the number of times GCLK4 is chosen and enabled, and it seems a lot. I understand the syncing while statements, PER register counter etc (it all looks like what I've seen before), but I'm less certain about what the multiplexer is required for. Is it there just because there are more pins on the chip than pins on the arduino?

Why does the generic clock have to be set to a 50/50 duty cycle before the sketch progresses through port enabling and TCC0 connection?

And why is the 48MHz broken down by the divisor again before the TCC0 is finally enabled as an output? Are we setting up the PWM on one clock then moving it to a further one?

This is really interesting! I knew the SAMD21 was a more complex beast but never thought it would be this impressive.

MartinL

#3
Feb 11, 2017, 11:53 pm Last Edit: Feb 12, 2017, 12:04 am by MartinL
Hi tyro1,

The Arduino core code uses generic clocks 0 to 3. This leaves GCLKs 4 to 7 free. I've just arbitrarily chosen GCLK4. The initial code just selects 48MHz as the the generic clock's source and connects it to timer TCC0.

The SAMD21 pins can be switched between either GPIO or a selection of peripheral functions from A through to H. What pin is assigned to what peripheral function is described in the Table 7.1 Port Function Multiplexing in the SAMD21's datasheet.

The pins are switched from GPIO to a perhipheral, by setting pin's associated PMUXEN (Pin Mulitplexer Enable) bit in its PINCFGx (Pin Configuration) register.

Timers TCC0, TCC1 and TCC2 are assigned to either peripheral E or F. However, this is where things get a bit more confusing, as there are only 16 PMUX (Peripheral Multiplexer) registers assigned to 32 port pins. So each neighbouring odd and even pin share a PMUX register, for instance SAMD21 port A pins PA00 (even) and PA01 (odd) share PMUX0, PA2 and PA3 share PMUX1 and so on... In the example above port pins PA20 and PA21 share PMUX10. (g_APinDescription[6].ulPin = 20, 20 >> 1 = 10).

As it's possible to divide the generic clock down, setting the GCLK_GENCTRL_IDC bit just reestablishes the output duty cycle at 50%, but isn't really necessary in this instance as we're dividing by 1.

It's possible to divide the 48Mhz clock source by both the generic clock divisor and the timer's own prescaler.

tryo1

Thank you for flagging up the datasheet entry, I tried to read through and got a bit bogged down. I found the peripheral table and couldn't make head or tail of it, but your explanation's very clear and it makes a lot more sense now.

Thank you again!

fidla

Hi, the code from MartinL works great for pin 6 ... thanks a lot for this! Unfortunately when I try to use it on digital pins 5, 4 or 3 it does not seem to work. I changed the PINCFG and PMUX assignments including the odd/even flag, but no signal is coming out :-( For pin 2 however it does work. Can someone point me out what am I overlooking?

Go Up