Arduino Forum

Using Arduino => Microcontrollers => Topic started by: jazzlw on Jan 09, 2019, 06:29 am

Title: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: jazzlw on Jan 09, 2019, 06:29 am
Hi,

Can anyone clarify what the default PWM frequency and resolution on the Adafruit Metro M4 is when using the arduino IDE and analogWrite?  I've seen some places that it's 8 bit and 1.8 Khz, is that correct?

Furthermore, can anyone clarify how this resolution and frequency can be adjusted?

Lastly, if you can answer the above, so much thanks, and can you point me to where you got the info, so I can hopefully answer future questions for myself?

Thanks!
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Jan 09, 2019, 10:50 am
Hi jazzlw,

Yes, for analogWrite() the Adafruit M4 boards configure the timers for 8-bits, clock them at 120MHz and use a 256 prescaler:

120MHz / 256 / 2^8 = 1.83kHz

...where as Arduino Zero/MKR boards configure the timers for 16-bits, clock them at 48MHz and use no prescaler:

48MHz / 2^16 = 732Hz

The SAMD51J19A used on the Metro M4 has a huge amout of timers, there's five fully featured TCCx timers (TCC0 to TCC4), plus six standard TC timers (TC0 to TC5) and that's excluding the system tick timer and the RTC timer that are available as well. Two of the TCCx timers are 24-bit, the rest are 16-bit. All the TC timers are 16-bit, but can be chained together in pairs to create a 32-bit timer.

The timers need to be driven by a clock source via the generic clock system. On the Metro M4 boards Adafruit have set up the SAMD51's Digital Phase and Frequency Locked Loops to generate 48MHz, 100MHz and 120MHz clock sources. It's possible to configure the generic clock system to route any of these clock sources to the timer.

The timers themselves operate in a very similar manner to the SAMD21's, but unfortunately the generic clock set-up is slightly different.

In addition, it's necessary to also configure the microcontroller's IO pins, to select the timer output. This is achieved by selecting the pin's peripheral multiplexer and switching the multiplexer to the correct timer output. Which timer channel is connected to what pin is defined in the I/O Multiplexing and Considerations chapter in the SAMD51 datasheet.

Here's an example that outputs a 50Hz, 50% duty-cycle PWM signal with 16-bit resolution on D7:

Code: [Select]
// Adafruit Metro M4 Only: Set-up digital pin D7 to output 50Hz, single slope PWM with a 50% duty cycle
void setup()
{
  // Set up the generic clock (GCLK7) to clock timer TCC0
  GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(1) |       // Divide the 48MHz clock source by divisor 1: 48MHz/1 = 48MHz
                         GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                         GCLK_GENCTRL_GENEN |        // Enable GCLK7
                         GCLK_GENCTRL_SRC_DFLL;      // Select 48MHz DFLL clock source
                         //GCLK_GENCTRL_SRC_DPLL1;     // Select 100MHz DPLL clock source
                         //GCLK_GENCTRL_SRC_DPLL0;     // Select 120MHz DPLL clock source
  while (GCLK->SYNCBUSY.bit.GENCTRL7);               // Wait for synchronization 

  GCLK->PCHCTRL[25].reg = GCLK_PCHCTRL_CHEN |        // Enable the TCC0 peripheral channel
                          GCLK_PCHCTRL_GEN_GCLK7;    // Connect generic clock 7 to TCC0

  // Enable the peripheral multiplexer on pin D7
  PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;
 
  // Set the D7 (PORT_PB12) peripheral multiplexer to peripheral (even port number) E(6): TCC0, Channel 0
  PORT->Group[g_APinDescription[7].ulPort].PMUX[g_APinDescription[7].ulPin >> 1].reg |= PORT_PMUX_PMUXE(6);
 
  TCC0->CTRLA.reg = TC_CTRLA_PRESCALER_DIV8 |        // Set prescaler to 8, 48MHz/8 = 6MHz
                    TC_CTRLA_PRESCSYNC_PRESC;        // Set the reset/reload to trigger on prescaler clock                 

  TCC0->WAVE.reg = TC_WAVE_WAVEGEN_NPWM;             // Set-up TCC0 timer for Normal (single slope) PWM mode (NPWM)
  while (TCC0->SYNCBUSY.bit.WAVE)                    // Wait for synchronization

  TCC0->PER.reg = 119999;                            // Set-up the PER (period) register 50Hz PWM
  while (TCC0->SYNCBUSY.bit.PER);                    // Wait for synchronization
 
  TCC0->CC[0].reg = 59999;                           // Set-up the CC (counter compare), channel 0 register for 50% duty-cycle
  while (TCC0->SYNCBUSY.bit.CC0);                    // Wait for synchronization

  TCC0->CTRLA.bit.ENABLE = 1;                        // Enable timer TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization
}

void loop() {}
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: westfw on Jan 09, 2019, 12:06 pm
the adafruit zerotimer library is somewhat helpful for playing with the TC timers...
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: jazzlw on Jan 09, 2019, 10:26 pm
Thanks so much MartinL for the super detailed answer! It's very helpful, and points me in the right direction to learn more as well.

With the arduino UNO, several of the timers are used for things like the servo library already, and if you use those you can't also use the servo library, etc.  What's the best way to check which timers for the M4 will be used for what things (like servos)?  Should I just find the source to the libraries I want to use (which presumably have to be M4 / SAMD51 adapted), and then dig through the source to find which timers are used where, or is there some better way?

Thanks again!
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Jan 10, 2019, 09:57 am
On Arduino's SAMD21 boards at least, the servo library uses timer TC4. The main other user of timers is the analogWrite() function. Which timer it uses, depends upon which pin has been selected.

Unlike the AVR boards that use timer 0 for the delay(), millis() and micros() functions, the ARM based boards employ the systick (system tick) timer, this frees up the other TCC/TC timers to use as you wish.

Another point to note is that Adafruit don't enable TC timers TC0 to TC2 by default. To use these timers it's first necessary to activate them in Main Clock (MCLK) system:

Code: [Select]
MCLK->APBAMASK.reg |= MCLK_APBAMASK_TC0;           // Activate timer TC0
The PWM frequency can be calculated by the formula:

PWM frequency = GCLK frequency / (Prescaler * (PER + 1))

For the above example:

PWM frequency = 48MHz / (8 * (119999 + 1)) = 50Hz

The PWM resolution:

PWM resolution = log (PER + 1) / log(2)

Again for the above example:

PWM resolution = log (119999 + 1) / log(2) = 16.87 bits
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Casey10110 on Jan 17, 2019, 09:47 pm
Hi Martin,

Your code is super helpful to me. I noticed you posted this in another thread:

Code: [Select]
void setup()
{
  MCLK->APBAMASK.reg |= MCLK_APBAMASK_TC0;           // Activate timer TC0
 
  // Set up the generic clock (GCLK7) used to clock timers
  GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(3) |       // Divide the 48MHz clock source by divisor 3: 48MHz/3 = 16MHz
                         GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                         GCLK_GENCTRL_GENEN |        // Enable GCLK7
                         GCLK_GENCTRL_SRC_DFLL;      // Generate from 48MHz DFLL clock source
  while (GCLK->SYNCBUSY.bit.GENCTRL7);               // Wait for synchronization

  GCLK->PCHCTRL[9].reg = GCLK_PCHCTRL_CHEN |         // Enable perhipheral channel
                         GCLK_PCHCTRL_GEN_GCLK7;     // Connect generic clock 7 to TC0

  // Enable the peripheral multiplexer on pin A1
  PORT->Group[g_APinDescription[A1].ulPort].PINCFG[g_APinDescription[A1].ulPin].bit.PMUXEN = 1;
 
  // Set A1 the peripheral multiplexer to peripheral E(4): TC0, Channel 1
  PORT->Group[g_APinDescription[A1].ulPort].PMUX[g_APinDescription[A1].ulPin >> 1].reg |= PORT_PMUX_PMUXO(4);
 
  TC0->COUNT16.CTRLA.reg = TC_CTRLA_PRESCALER_DIV16 |        // Set prescaler to 16, 16MHz/16 = 1MHz
                           TC_CTRLA_PRESCSYNC_PRESC |        // Set the reset/reload to trigger on prescaler clock
                           TC_CTRLA_MODE_COUNT16;            // Set the counter to 16-bit mode

  TC0->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_MPWM;      // Set-up TC0 timer for Match PWM mode (MPWM)

  TC0->COUNT16.CC[0].reg = 1999;                    // Use CC0 register as TOP value, set for 50Hz PWM
  while (TC0->COUNT16.SYNCBUSY.bit.CC0);             // Wait for synchronization

  TC0->COUNT16.CC[1].reg = 999;                     // Set the duty cycle to 50% (CC1 half of CC0)
  while (TC0->COUNT16.SYNCBUSY.bit.CC1);             // Wait for synchronization

  TC0->COUNT16.CTRLA.bit.ENABLE = 1;                 // Enable timer TC0
  while (TC0->COUNT16.SYNCBUSY.bit.ENABLE);          // Wait for synchronization
}

void loop() {}


Works awesome on the Feather M4 Express. I was trying to get it to work on other pins (like A2), but this doesn't work:

Code: [Select]
  // Enable the peripheral multiplexer on pin A2
  PORT->Group[g_APinDescription[A2].ulPort].PINCFG[g_APinDescription[A2].ulPin].bit.PMUXEN = 1;
 
  // Set A2 the peripheral multiplexer to peripheral E(4): TC0, Channel 1
  PORT->Group[g_APinDescription[A2].ulPort].PMUX[g_APinDescription[A2].ulPin >> 1].reg |= PORT_PMUX_PMUXO(4);
 


What am I doing wrong?

UPDATE: Turns out the tone() function works fine on this board as well, so that should suffice for my purposes. Thank you for your awesome contribution, though!
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Jan 18, 2019, 10:28 am
Hi Casey10110,

The A2 pin on the Adafruit Feather M4 is only connected to timer TC4, (I/O Multiplexing and Considerations table in the SAMD51 datasheet). As you mention, the tone() function also happens to use timer TC4 as well.

By the way, if you need to change the duty-cycle or frequency during operation, then it's possible to use the SAMD51's buffered CCBUF and PERBUF registers. This updates the corresponding CC and PER registers at the beginning of the timer cycle, thereby preventing glitches from appearing on your PWM output. (Changes to the CC and PER registers occur immediately at the output, regardless of the current position in the timer cycle).

For example, to change the duty-cycle between 25% and 75% every second:

Code: [Select]
void loop()
{
  TCC0->CCBUF[0].reg = 29999;    // Set-up the CCBUF (Counter Compare Buffered), channel 0 register for 25% duty-cycle         
  delay(1000);                   // Wait for 1 second
  TCC0->CCBUF[0].reg = 89999;    // Set-up the CCBUF (Counter Compare Buffered), channel 0 register for 75% duty-cycle
  delay(1000);                   // Wait for 1 second
}
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Feb 16, 2019, 09:04 pm
Hi MartinL,

Do you have more timer examples for the SAMD51 (M4) both TCCx and TCx.  I am trying to control 3 stepper motors and each having its own variable speed control.  Do I need to use individual timers ie: TCC0, TCC1, TCC2, etc. or can I just use one timer and just change channels.

I have done some playing using your examples.  TCC0 (PCHCTRL[25]) modified pin output to D9 (PA20) E(6) this timer works fine.  I tried to create another timer TCC4 (PCHCTRL[38]) pin output D5 (PB14) E(5) does not work.  I also tried the TC timers I was able to create TC5 (PCHCTRL[30]) pin output to D6 (PB15) O(4) this too works fine but any others for example TC4 (PCHCTRL[30]) pin output to D7 (PB12) E(4) does not work.  Looking at your examples and the data sheets I thought I finally figured out setting up timers.... not even close?  I have attached the code - I must be missing something, I hope you can help.

Best Regards,
Jim
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Feb 18, 2019, 12:37 am
Hi Jim,

I had a look at your code, it just requires a couple of tiny modifications.

Firstly unlike the AVR Arduinos, on the ARM based boards it isn't necessary to use the pinMode() function set the peripheral or timer outputs. These lines can be deleted.

On TC4, you've set the output for channel 0, but in match PWM (MPWM) mode the counter compare (CC) channel is on channel 1.

TCC4 is only a 16-bit timer, therefore the PER/CC registers must be a value between 0 and 65535. Only TCC timers TCC0 and TCC1 are 24-bits.

Kind regards,
Martin

Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Feb 18, 2019, 06:34 pm
Hi MartinL,

Thank you for your reply.

TC4 MPWM/CC I am not sure how to change channels.

TCC4 you were correct as soon as I changed the registers to 16 bit this timer works fine.

Thanks,
Jim
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Feb 18, 2019, 07:57 pm
Hi Jim,

A TC4 channel 1 pin can be found on the Metro M4 Express' digital pin 4, aka port pin PB13.

All that needs to be changed is the digital pin number for the pin configuration (PINCFG) and multiplexer (PMUX) registers from 7 to 4, and as we're now using an odd port pin number (PB13), the PMUX bitfield definition to odd:

Code: [Select]
PORT->Group[g_APinDescription[4].ulPort].PINCFG[g_APinDescription[4].ulPin].bit.PMUXEN = 1;
 
PORT->Group[g_APinDescription[4].ulPort].PMUX[g_APinDescription[4].ulPin >> 1].reg |= PORT_PMUX_PMUXO(4);

Here's a full example of the TC4 timer set-up to output 50Hz with a 50% duty-cycle on digtial pin D4:

Code: [Select]
//Set-up timer TC4 to output 50Hz, 50% duty-cycle on digital pin D4
void setup() {
  // Set up the generic clock (GCLK6) used to clock timers
  GCLK->GENCTRL[6].reg = GCLK_GENCTRL_DIV(3) |       // Divide the 48MHz clock source by divisor 3: 48MHz/3 = 16MHz
                         GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                         GCLK_GENCTRL_GENEN |        // Enable GCLK6
                         GCLK_GENCTRL_SRC_DFLL;      // Generate from 48MHz DFLL clock source
                         //GCLK_GENCTRL_SRC_DPLL1;     // Generate from 100MHz DPLL clock source
                         //GCLK_GENCTRL_SRC_DPLL0;     // Generate from 120MHz DPLL clock source
  while (GCLK->SYNCBUSY.bit.GENCTRL6);               // Wait for synchronization

  GCLK->PCHCTRL[30].reg = GCLK_PCHCTRL_CHEN |        // Enable perhipheral channel
                         GCLK_PCHCTRL_GEN_GCLK6;     // Connect generic clock 6 to TC4, PCHCTRL[30]

  // Enable the peripheral multiplexer on pin D4
  PORT->Group[g_APinDescription[4].ulPort].PINCFG[g_APinDescription[4].ulPin].bit.PMUXEN = 1;
 
  // Set D4 (Port PB13) the peripheral multiplexer to peripheral (even) E(4): TC4, Channel 1 (peripheral A=0, B=1, C=2, D=3, E=4, etc)
  PORT->Group[g_APinDescription[4].ulPort].PMUX[g_APinDescription[4].ulPin >> 1].reg |= PORT_PMUX_PMUXO(4);
 
  TC4->COUNT16.CTRLA.reg = TC_CTRLA_PRESCALER_DIV16 | // Set prescaler to 16, 16MHz/16 = 1MHz
                           TC_CTRLA_PRESCSYNC_PRESC | // Set the reset/reload to trigger on prescaler clock
                           TC_CTRLA_MODE_COUNT16;     // Set the counter to 16-bit mode

  TC4->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_MPWM;       // Set-up TC4 timer for Match PWM mode (MPWM)
  
  TC4->COUNT16.CC[0].reg = 19999;                    // Use CC0 register as TOP value, set for 50Hz PWM
  while (TC4->COUNT16.SYNCBUSY.bit.CC0);             // Wait for synchronization

  TC4->COUNT16.CC[1].reg = 9999;                     // Set the duty cycle to 50% (CC1 half of CC0)
  while (TC4->COUNT16.SYNCBUSY.bit.CC1);             // Wait for synchronization

  TC4->COUNT16.CTRLA.bit.ENABLE = 1;                 // Enable timer TC4
  while (TC4->COUNT16.SYNCBUSY.bit.ENABLE);          // Wait for synchronization
}

void loop() {}
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Feb 18, 2019, 11:38 pm
Hi MartinL,

Ok thanks.  I have a stupid question - so - why didn't PB12 (TCC3_CH0, TC4_CH0) work?  Just trying to wrap my head around this and not sure what am I missing.

Thanks,
Jim

Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Feb 19, 2019, 12:54 pm
Hi Jim,

On the SAMD21 and SAMD51 microcontrollers the TC timers aren't as fully featured as their TCC counterparts. For this reason it's generally better to favour the TCC timers for PWM output and the TC timers for internal timing.

The TC timers can generate PWM output in either normal PWM (NPWM) or match PWM (MPWM) modes. Normal PWM mode allows independent output generation on both of its channels: CC0 and CC1, but offers no control of the waveforms' frequency. Match PWM on the other hand also uses both channels, but sacrifices CC0 to provide frequency control, (it determines the waveform's period), while CC1 determines the duty-cycle. The resulting waveform being output on CC1. This  is the reason why it's necessary to use the CC1 channel output in match PWM mode.

The PB12 port pin (D7) can output frequency controlled PWM output, but requires the pin mutliplexer to be switched to a different timer output. PB12 can also be connected either to TCC3/WO[0] (channel 0) on switch F(5) or TCC0/WO[0] on switch G(6), as shown in the I/O Multiplexing and Considerations table in the SAMD51 datasheet.
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Feb 19, 2019, 05:27 pm
Hi MartinL,

Thanks for your explanation, things are starting to make some sense.

So you recommend TCCx timers thanks.  So they will have the same issue one TCCx timer output takes up two pins (odd / even).
 
I have few more questions I hope you can help me with;

1) If the output pins are paired (odd/even) - one pin is PWM'd could the other pin be used reliably as GPIO or should the other pin be left unused?
2) Timers that are on the same channel (pin) PA09 (TC0, TCC0, TCC1) for PA09 I pick one timer the others can not be used on PA09.  Could I re-use these timers on a different channel (pin) each with independent frequency and control.
3) The timer output with the WO pairing (0-1, 2-3, etc) should always be configured to output on the odd channel/pin?
4) Within one timer TCC0 which has multiple channels - can each channel pair (odd/even) be a different frequency/dutycycle and controlled independent of the other channels?  Or should I use separate timers TCC2, TCC3, TCC4 for example.

I have attached some TCCx code that TCC0 works but TCC1, TCC3, TCC4 do not work?  Things for me just aren't clicking yet.

Thanks,
Jim
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Feb 19, 2019, 06:42 pm
Hi Jim,

The TCCx timers each have a number of channels (table 6.9 of the SAMD51 datasheet):

In the SAMD51 datasheet these channels are labelled: WO[0], WO[1], WO[2] and WO[3], WO[4] and WO[5], these correspond to channels 0, 1, 2, 3, 4 and 5. However, on timers TCC0 and TCC1 some of the channels are repeated (WO[6]/WO[7]) and can provide a complementary (inverted) output. The SAMD51 also offers dead-time insertion on timers TCC0 and TCC1.

For example on TCC1, WO[0] is repeated on WO[4], WO[1] on WO[5], WO[2] on WO[6] and finally WO[3] on WO[7]. Setting the duty-cycle with the counter compare register on channel 1 (CC[1]), outputs the PWM waveform on both WO[1] and WO[5].

Each timer has single period (PER) register, but a separate counter compare (CC(x)) register for each channel. This means that each timer can be configured to operate at one frequency (PER), but with different duty-cycles (CC(x)) on each channel.

The TCCx timers also offer buffered counter compare (CCBUF(x)) and period (PERB) registers. Changes to the buffered registers only occur at the start of the timer cycle, where as changes to CC(x) and the PER register appear immediately at the PWM output. 

Any of the SAMD51's pins can be either GPIO or mutliplexed to any peripheral, irrespective of port pin pairs. What pin is connected to what peripheral is provided by the SAMD51 datasheet I/O Multiplexing and Considerations table.

Each port pin has it's own pin configuration (PINCONFIG) registers, but shares the port multiplexer (PMUX) register with its neighbouring pin. The concept of port pin pairing is simply because the chip designers decided to create a single PMUX register to set the multiplexer peripheral switch (A, B, C, D, etc...) for two pins rather than one.

For instance PMUX[0] sets the port pins PA00 and PA01, PMUX[1] sets PA02 and PA03 and so on. The port pairs can be set to different peripherals (ADC input, TC timer, etc...) and either of them may be set to GPIO.

Kind regards,
Martin
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Feb 19, 2019, 07:02 pm
Hi Jim,

The reason why some of your timers aren't working, is because on the SAMD51 some of the TCCx timers share the same generic clock:

PHCTRL[25] = TCC0/TCC1
PHCTRL[29] = TCC2/TCC3
PHCTRL[38] = TCC4

For example if you've set up generic clock 7 (GCLK7) for TCC0, then TCC1 is also automatically connected to GCLK7 as well. Same goes for TCC2 and TCC3.

On TCC4, (that doesn't share a generic clock), it's not working because PB15 is on channel 1, you just need to change the counter compare register from CC[0] to CC[1].
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Feb 19, 2019, 09:02 pm
Hi MartinL,

Thanks for the info. things are getting a little clearer.  So just to clarify;

For example;

PA16 - TC2/WO[0], TCC1/WO[0], TCC0/WO[4]
PA17 - TC2/WO[1], TCC1/WO[1], TCC0/WO[5]

I would change in the TCC1 timer counter compare code;

From PA16 - channel 0, Even(5): Output PWM on PA16;
TCC1->CC[0].reg = 99;
while (TCC1->SYNCBUSY.bit.CC0);

To PA17 - channel 1, Odd(5); Output PWM on PA17;
TCC1->CC[1].reg = 99;
while(TCC1->SYNCBUSY.bit.CC1);

So to change the channel CC[WO#].

Thanks,
Jim

Would this now output PWM on pin PA17.

Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Feb 20, 2019, 03:07 pm
Hi Jim,

That's right.

If you're using a WO[4] to WO[7] on TCC1, this will also map to CC[0] to CC[3] respectively.
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Casey10110 on Feb 26, 2019, 05:42 pm
Super helpful stuff Martin. Thank you for everything, this thread rocks and seems to get better and better.
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Mar 05, 2019, 01:29 am
Hi MartinL,

Thanks for all your help.  I need your help with an example of TCC timer using;

1) an interrupt with handler.
2) best way to stop and start the TCC timer.

Thanks,
Jim
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Mar 05, 2019, 09:43 am
Hi Jim,

To use the TCCx interrupt handler it's first necessary to connect the peripherial to the ARM core through its NVIC (Nested Vector Interrupt Controller), then enable the required timer interrupt.

The SAMD51 offers a number of interrupt possibilities, but the main ones are OVF (overflow) that calls the interrupt handler at the end of each time cycle and MCx (Match Compare) that calls the interrupt handler each time the COUNT (Counter) register matches the correspoinding CCx (Counter Compare) value.

For example, to connect the TCC0 timer to the ARM core and enable overflow interrupts you'd add the following to the setup() portion of your sketch:

Code: [Select]
NVIC_SetPriority(TCC0_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC0 to 0 (highest)
NVIC_EnableIRQ(TCC0_IRQn);         // Connect TCC0 to Nested Vector Interrupt Controller (NVIC)
TCC0->INTENSET.reg |= /*TCC_INTENSET_MC1 | TCC_INTENSET_MC0 |*/ TCC_INTENSET_OVF;  // Enable overflow interrupts

Inside the handler function itself, the corresponding interrupt flag can be tested to discover which interrupt has occured and your own application code added. It's also necessary to clear the interrupt flag by writing a 1 to it. (The name of the interrupt handler itself is reserved and should not be changed).

For instance, here's the interrupt handler for TCC0:

Code: [Select]
void TCC0_Handler()                                       // Interrupt handler for TCC0
{
  if (TCC0->INTENSET.bit.OVF && TCC0->INTFLAG.bit.OVF)    // Test if the OVF (Overflow) interrupt has occured
  {
    // ...
    // Add your application code here...
    // ... 
    TCC0->INTFLAG.bit.OVF = 1;                            // Clear the OVF interrupt flag
  }
 
  if (TCC0->INTENSET.bit.MC0 && TCC0->INTFLAG.bit.MC0)    // Test if the MC0 (Match Compare Channel 0) interrupt has occured
  {
    // ...
    // Add your application code here...
    // ... 
    TCC0->INTFLAG.bit.MC0 = 1;                            // Clear the MC0 interrupt flag
  }

  if (TCC0->INTENSET.bit.MC1 && TCC0->INTFLAG.bit.MC1)    // Test if the MC1 (Match Compare Channel 1) interrupt has occured
  {
    // ...
    // Add your application code here...
    // ... 
    TCC0->INTFLAG.bit.MC1 = 1;                            // Clear the MC1 interrupt flag
  }
  // Add other channels as required...
}

To enable the TCC0 timer:

Code: [Select]
TCC0->CTRLA.bit.ENABLE = 1;           // Enable timer TCC0
while (TCC0->SYNCBUSY.bit.ENABLE);    // Wait for synchronization

To disable the TCC0 timer:

Code: [Select]
TCC0->CTRLA.bit.ENABLE = 0;           // Disable timer TCC0
while (TCC0->SYNCBUSY.bit.ENABLE);    // Wait for synchronization

To stop the TCC0 timer:

Code: [Select]
TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_STOP;   // Stop timer TCC0
while (TCC0->SYNCBUSY.bit.CTRLB);             // Wait for synchronization

To restart the TCC0 timer:

Code: [Select]
TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER;   // Start timer TCC0
while (TCC0->SYNCBUSY.bit.CTRLB);                  // Wait for synchronization

To read the value of the TCC0 COUNT register:

Code: [Select]
TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_READSYNC;    // Initiate read synchronization of the COUNT register
while (TCC0->SYNCBUSY.bit.CTRLB);                  // Wait for CTRLB register write synchronization
while (TCC0->SYNCBUSY.bit.COUNT);                  // Wait for COUNT register read synchronization
Serial.println(TCC0->COUNT.reg);                   // Read the COUNT register

Kind regards,
Martin
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Mar 30, 2019, 02:13 am
Hi MartinL,

Sorry for the delay in my reply, just got back from holidays.  Thank you again for all your help.

Best Regards,
Jim
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: sajattack on Apr 03, 2019, 06:05 am
Hi MartinL,

I'm trying to do on-the-fly duty cycle adjustments using TC2 and PA16/D13, could you give me a hand?
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Apr 03, 2019, 09:38 am
Hi sajattack,

Quote
I'm trying to do on-the-fly duty cycle adjustments using TC2 and PA16/D13, could you give me a hand?
Do you intend to use have control over both the output's frquency and duty-cycle?

If that's the case it will be necessary to output on TC2's channel 1 (TC2/WO[1]), such as port PA17 on D12, using the timer's Match PWM (MPWM) mode.

Normal PWM (NPWM) mode on the TCx timers offers two channel operation on channels 0 an 1, but sacrifices frequency control.
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Apr 03, 2019, 09:54 am
Here's an example of using the TC2 timer to output PWM at 50Hz at 25% and 75% alternating duty-cycles every second on D12/PA17 (TC2/WO[1]):

Code: [Select]
// Output 50Hz PWM on Metro M4 pin D12/PA17 using the TC2 timer
void setup()
{
  MCLK->APBBMASK.reg |= MCLK_APBBMASK_TC2;           // Activate timer TC2

  // Set up the generic clock (GCLK7) used to clock timers
  GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(3) |       // Divide the 48MHz clock source by divisor 3: 48MHz/3 = 16MHz
                         GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                         GCLK_GENCTRL_GENEN |        // Enable GCLK7
                         GCLK_GENCTRL_SRC_DFLL;      // Generate from 48MHz DFLL clock source
  while (GCLK->SYNCBUSY.bit.GENCTRL7);               // Wait for synchronization

  GCLK->PCHCTRL[26].reg = GCLK_PCHCTRL_CHEN |         // Enable perhipheral channel
                          GCLK_PCHCTRL_GEN_GCLK7;     // Connect generic clock 7 to TC2 at 16MHz

  // Enable the peripheral multiplexer on digital pin 12
  PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].bit.PMUXEN = 1;
  // Set the peripheral multiplexer for D12 to peripheral E(4): TC2, Channel 0
  PORT->Group[g_APinDescription[12].ulPort].PMUX[g_APinDescription[12].ulPin >> 1].reg |= PORT_PMUX_PMUXO(4);
 
  TC2->COUNT16.CTRLA.reg = TC_CTRLA_PRESCALER_DIV16 |        // Set prescaler to 64, 16MHz/16 = 1MHz
                           TC_CTRLA_PRESCSYNC_PRESC |        // Set the reset/reload to trigger on prescaler clock
                           TC_CTRLA_MODE_COUNT16;            // Set the counter to 16-bit mode
  TC2->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_MPWM;      // Set-up TC2 timer for Match PWM mode (MPWM)
  TC2->COUNT16.CC[0].reg = 19999;                    // Use CC0 register as TOP value, set for 50Hz PWM 
  while (TC2->COUNT16.SYNCBUSY.bit.CC0);             // Wait for synchronization
  TC2->COUNT16.CC[1].reg = 9999;                     // Set the duty cycle to 50% (CC1 half of CC0)
  while (TC2->COUNT16.SYNCBUSY.bit.CC1);             // Wait for synchronization
  TC2->COUNT16.CTRLA.bit.ENABLE = 1;                 // Enable timer TC2
  while (TC2->COUNT16.SYNCBUSY.bit.ENABLE);          // Wait for synchronization
}

void loop()
{
  // Using the TC2's buffered CCBUF registers for "on the fly" operation
  TC2->COUNT16.CCBUF[1].reg = 4999;                  // Set the duty cycle to 25% (CC1 half of CC0)
  delay(1000);                                       // Wait 1 second
  TC2->COUNT16.CCBUF[1].reg = 14999;                 // Set the duty cycle to 75% (CC1 half of CC0)
  delay(1000);                                       // Wait 1 second
}
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: sajattack on Apr 03, 2019, 06:50 pm
Oh, so I must've read the datasheet wrong. I thought WO[0] was the output. Thanks.
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Apr 10, 2019, 10:57 pm
Hi MartinL,

I am trying to use a digital pot to adjust the frequency and duty cycle of the timer TCC1_CH2.  I am getting errors compiling and not sure what I am missing.  I have attached the errors and code.

Arduino: 1.8.9 (Mac OS X), Board: "Adafruit Grand Central M4 (SAMD51), Enabled"

/Users/JimD/Documents/Arduino/TCC1_Timer_Test/TCC1_Timer_Test.ino: In function 'void loop()':
TCC1_Timer_Test:22:15: error: no match for 'operator[]' (operand types are 'volatile TCC_PERBUF_Type' and 'int')
   TCC1->PERBUF[2].reg = PotValue-1;                     // Set the frequency of the PWM on TCC1 Channel 2
               ^
TCC1_Timer_Test:23:31: error: 'volatile struct TCC_SYNCBUSY_Type::<anonymous>' has no member named 'PERBUF2'
     while (TCC1->SYNCBUSY.bit.PERBUF2);
                               ^
TCC1_Timer_Test:25:31: error: 'volatile struct TCC_SYNCBUSY_Type::<anonymous>' has no member named 'CCBUF2'
     while (TCC1->SYNCBUSY.bit.CCBUF2);
                               ^
exit status 1
no match for 'operator[]' (operand types are 'volatile TCC_PERBUF_Type' and 'int')

This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.

Sorry I fixed the file it has a typo: TCC4 to TCC1 on the SYNCBUSY's.

Best Regards,
Jim

Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Apr 11, 2019, 10:06 am
Hi Jim,

The TCC timers only have a single PER and PERBUF register per timer, therefore the square array brackets [] are not required:

Code: [Select]
TCC1->PERBUF.reg = PotValue-1;                     // Set the frequency of the PWM on TCC1
However, this means that any change to the PER or PERBUF registers will affect all the TCC1 timer channels.

Curiously the SAMD51 datasheet states that the CCBUFx and PERBUF registers are read and write synchronized, but (unlike the SAMD21) offers no corresponding CCBUFx or PERBUF synchronization bits in the SYNCBUSY register. The bits don't appear in the SAMD51's CMSIS register definitions either.

In the first edition of the SAMD51 datasheet the TCC timers' CCx registers let alone the buffered registers weren't documented, so perhaps they haven't got round to documenting them fully?

In practical terms and in the absence of SYNCBUSY synchronization bits, I just forgo the synchronization check and delete the while() loops. Synchronization happens anyway irrespective of whether you check for it or not. It only really becomes an issue when accessing the peripheral's register two times in quick succession, before synchronization has had a chance to complete. This only normally takes a handful of peripheral generic and APB clock cycles:

5×PGCLK + 2×PAPB < D < 6×PGCLK + 3×PAPB
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Apr 11, 2019, 06:51 pm
Hi MartinL,

Thanks, removing the brackets [ ] and the "while()" loops fixed the compile errors.


Thanks Again,
Jim
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: czitro on Apr 16, 2019, 05:38 pm
Hello there MartinL,

I tried to use your code for the timer and it compiles and uploads to the board but nothing actually happens then I can't get the servo connected to pin 7.


Did I miss something I should also write in the code additionally?
Otherwise I don't know what I'm doing wrong :/

Code: [Select]
// Adafruit Metro M4 Only: Set-up digital pin D7 to output 50Hz, single slope PWM with a 50% duty cycle
void setup()
{
 // Set up the generic clock (GCLK7) to clock timer TCC0
 GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(1) |       // Divide the 48MHz clock source by divisor 1: 48MHz/1 = 48MHz
                        GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                        GCLK_GENCTRL_GENEN |        // Enable GCLK7
                        GCLK_GENCTRL_SRC_DFLL;      // Select 48MHz DFLL clock source
                        //GCLK_GENCTRL_SRC_DPLL1;     // Select 100MHz DPLL clock source
                        //GCLK_GENCTRL_SRC_DPLL0;     // Select 120MHz DPLL clock source
 while (GCLK->SYNCBUSY.bit.GENCTRL7);               // Wait for synchronization  

 GCLK->PCHCTRL[25].reg = GCLK_PCHCTRL_CHEN |        // Enable the TCC0 peripheral channel
                         GCLK_PCHCTRL_GEN_GCLK7;    // Connect generic clock 7 to TCC0

 // Enable the peripheral multiplexer on pin D7
 PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;
 
 // Set the D7 (PORT_PB12) peripheral multiplexer to peripheral (even port number) E(6): TCC0, Channel 0
 PORT->Group[g_APinDescription[7].ulPort].PMUX[g_APinDescription[7].ulPin >> 1].reg |= PORT_PMUX_PMUXE(6);
 
 TCC0->CTRLA.reg = TC_CTRLA_PRESCALER_DIV8 |        // Set prescaler to 8, 48MHz/8 = 6MHz
                   TC_CTRLA_PRESCSYNC_PRESC;        // Set the reset/reload to trigger on prescaler clock                

 TCC0->WAVE.reg = TC_WAVE_WAVEGEN_NPWM;             // Set-up TCC0 timer for Normal (single slope) PWM mode (NPWM)
 while (TCC0->SYNCBUSY.bit.WAVE)                    // Wait for synchronization

 TCC0->PER.reg = 119999;                            // Set-up the PER (period) register 50Hz PWM
 while (TCC0->SYNCBUSY.bit.PER);                    // Wait for synchronization
 
 TCC0->CC[0].reg = 59999;                           // Set-up the CC (counter compare), channel 0 register for 50% duty-cycle
 while (TCC0->SYNCBUSY.bit.CC0);                    // Wait for synchronization

 TCC0->CTRLA.bit.ENABLE = 1;                        // Enable timer TCC0
 while (TCC0->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization
}

void loop() {}
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Apr 16, 2019, 08:07 pm
Hi czitro,

The PWM signal at on D7 is at 50Hz, but at 50% duty-cycle the 10ms pulse width is too wide to drive a servo. Normally, servos usually operate with a pulse width between 1 and 2ms repeated at 50Hz (20ms).
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Apr 16, 2019, 08:52 pm
Hi czitro,

The following code generates a 50Hz servo output on D7, just load the CCBUF0 regsiter with the required pulse width in microseconds: 1000 to move the servo to it's minimum position, 1500 to centre the servo, 2000 to move the servo to it's maximum position, (or anything in between):

Code: [Select]
// Adafruit Metro M4 Only: Set-up digital pin D7 to 50Hz servo output
void setup()
{
  // Set up the generic clock (GCLK7) to clock timer TCC0
  GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(3) |       // Divide the 48MHz clock source by divisor 3: 48MHz/3 = 16MHz
                         GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                         GCLK_GENCTRL_GENEN |        // Enable GCLK7
                         GCLK_GENCTRL_SRC_DFLL;      // Select 48MHz DFLL clock source
                         //GCLK_GENCTRL_SRC_DPLL0;     // Select 100MHz DPLL clock source
                         //GCLK_GENCTRL_SRC_DPLL0;     // Select 120MHz DPLL clock source
  while (GCLK->SYNCBUSY.bit.GENCTRL7);               // Wait for synchronization 

  GCLK->PCHCTRL[25].reg = GCLK_PCHCTRL_CHEN |        // Enable the TCC0 perhipheral channel
                          GCLK_PCHCTRL_GEN_GCLK7;    // Connect generic clock 7 to TCC0

  // Enable the peripheral multiplexer on pin D7
  PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;
 
  // Set the D7 (PORT_PB12) peripheral multiplexer to peripheral (even port number) E(6): TCC0, Channel 0
  PORT->Group[g_APinDescription[7].ulPort].PMUX[g_APinDescription[7].ulPin >> 1].reg |= PORT_PMUX_PMUXE(6);
 
  TCC0->CTRLA.reg = TC_CTRLA_PRESCALER_DIV16 |        // Set prescaler to 16, 16MHz/16 = 1MHz
                    TC_CTRLA_PRESCSYNC_PRESC;        // Set the reset/reload to trigger on prescaler clock                 

  TCC0->WAVE.reg = TC_WAVE_WAVEGEN_NPWM;             // Set-up TCC0 timer for Normal (single slope) PWM mode (NPWM)
  while (TCC0->SYNCBUSY.bit.WAVE)                    // Wait for synchronization
 
  TCC0->PER.reg = 19999;                             // Set-up the PER (period) register 50Hz PWM
  while (TCC0->SYNCBUSY.bit.PER);                    // Wait for synchronization
 
  TCC0->CC[0].reg = 1500;                            // Centre the servo
  while (TCC0->SYNCBUSY.bit.CC0);                    // Wait for synchronization

  TCC0->CTRLA.bit.ENABLE = 1;                        // Enable timer TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization
}

void loop()

  TCC0->CCBUF[0].reg = 1000;                         // Set servo to minimum
  delay(1000);                                       // Wait for 1 second
  TCC0->CCBUF[0].reg = 2000;                         // Set servo to maximum
  delay(1000);                                       // Wait for 1 second
}
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: signal64 on Jul 30, 2019, 06:06 am
For the IRQ example, is there anyway to have it trigger on falling edge?

I'm using the PWM to clock an external device that reads data on the high pulse and wanted to use the IRQ to switch data on the Low. 

Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Jul 30, 2019, 09:05 am
Hi signal64,

Quote
For the IRQ example, is there anyway to have it trigger on falling edge?
By default in Normal PWM mode the output signal goes high at the start of the timer cycle and low when the timer's COUNT (counter) matches the value in its CCx (counter compare) register.

If you enable the MCx (match compare) interrupt for a given channel, then the interrupt service routine (TCCx_Handler() function) will be called on the waveform's fall edge.
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: signal64 on Aug 03, 2019, 10:14 pm
I'm struggling with getting the match compare interrupt to work.
Overflow works fine.

The IRQ setup for TCC1 and CC[1] should be this right?:
Code: [Select]
NVIC_SetPriority(TCC1_0_IRQn, 0);   
NVIC_EnableIRQ(TCC1_0_IRQn);       
TCC1->INTENSET.reg |= TCC_INTENSET_MC1;

And the Handler:
Code: [Select]
void TCC1_0_Handler() {
   if (TCC1->INTENSET.bit.OVF && TCC1->INTFLAG.bit.OVF)
  {
     // my code

      TCC1->INTFLAG.bit.OVF = 1;
   }
   
   if (TCC1->INTENSET.bit.MC1 && TCC1->INTFLAG.bit.MC1) 
  {
     // my code
       
     TCC1->INTFLAG.bit.MC1 = 1;                           
  }
}

Also a bit confused on the change to use TCC1_0_IRQn and TCC1_0_Handler().
Without the _0_ these are undefined.
Also would think the _0_ should be _1_ but that doesn't work.
Not sure what the _0_ is in reference to.

I added debug lines to see if the handler and if conditions are getting hit.
With the OVF IRQ enable it works.
It's not calling the handler when I use the MC1 (or MC0) IRQ enable.
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Aug 04, 2019, 07:41 pm
Hi signal64,

My apologies, I was thinking of the SAMD21 rather than the SAMD51, in my previous post.

On the SAMD21 each TCCx timer has only one associated handler function TCCx_Handler(). The SAMD51 TCCx timers by contrast each have a number of interrupt handler functions TCCx_y_Handler(), where x is the timer number and y is the handler number.

The TCC1_0_Handler() function is called for all timer interrupt flags including overflow (OVF), except for the Match Compare (MCx) interrupts. The Match Compare (MCx) interrupts have their own interrupt service routine. MC0 calls TCC1_1_Handler(), MC1 calls TCC1_2_Handler() and so on... If you're using MC1 for example, you'll need to change your code for TCC1_2_Handler().

The key to which interrupt flag calls which handler function is in section 10.2.2 (NVIC) Interrupt Line Mapping table in the SAMD51 datasheet. The peripheral handler functions themselves are defined in the microcontroller's CMSIS (Cortex Microcontroller Software Interface Standard) "samd51j19a.h" file.

The CMSIS files can be found under (on my windows machine at least) at: C:\User\Computer\AppData\Local\Arduino15\packages\arduino\tools\CMSIS-Atmel\1.2.0\CMSIS\Atmel\samd51\include\... These directories also contain all the register definitions for the SAMD51 microcontroller. 

The fact that each MCx interrupt has its own dedicated handler function means that it isn't necessary to test interrupt flags, in order to test which interrupt called the routine. This provides the SAMD51 with a small speed optimisation over the SAMD21 microcontroller. (Although the interrupt flag still needs to be manually cleared by writing a one to it within the handler).
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: cyborg5 on Sep 11, 2019, 11:54 pm
I'm the author of IRLib2 which is used to receive, decode, and send infrared signals. I need to be able to set up PWM output in a range anywhere from about 34 kHz up to 58 kHz. At one point I had support for Adafruit Metro M4 working but somewhere along the way the variant.cpp values changed and now it's no longer working. I barely understand how any of this stuff works so it's kind of hard to figure out what's wrong. I've modeled the code after digitalWrite in wiring_analog.c.there's something missing. I think my old code always used clock generator zero and assumed that it was pre-initialized to a usable value. I think that's what's missing in my code is that that's no longer true. I would like the code to be flexible enough that I can use any PWM capable pin. Also I want to avoid conflicts with other software so I was hoping I could find a clock that I didn't have to mess with for fear it would conflict with some other library. On the M0 boards I use clock zero with no reconfiguration. I just attach it to the proper TCC as is. I would like to be able to do the same thing on M4. Could you please take a look at this code and see where I missing. The pin number is defined as IR_SEND_PWM_PIN and for testing purposes I'm using pin 7 but I would like to be free to use any PWM capable pin. One of the problems with my code right now is that it only works for PWM TCC and not TC timers. I will worry about that later. I'm currently using a Metro M4 Grand Central pin 7 which is PD21 and uses PIN_ATTR_PWM_F and TCC1_CH1.

Code: [Select]
void initializeSAMD51PWM(uint16_t khz) {
  PinDescription pinDesc = g_APinDescription[IR_SEND_PWM_PIN];
  uint32_t attr = pinDesc.ulPinAttribute;
  //If PWM unsupported then do nothing and exit
  if( !(attr & (PIN_ATTR_PWM_E|PIN_ATTR_PWM_F|PIN_ATTR_PWM_G))){
    return;
  }
  uint32_t tcNum = GetTCNumber(pinDesc.ulPWMChannel);
  uint8_t tcChannel = GetTCChannelNumber(pinDesc.ulPWMChannel);

  if(attr & PIN_ATTR_PWM_E)
    pinPeripheral(IR_SEND_PWM_PIN, PIO_TIMER);
  else if(attr & PIN_ATTR_PWM_F)
    pinPeripheral(IR_SEND_PWM_PIN, PIO_TIMER_ALT);
  else if(attr & PIN_ATTR_PWM_G)
    pinPeripheral(IR_SEND_PWM_PIN, PIO_TCC_PDEC);

  GCLK->PCHCTRL[GCLK_CLKCTRL_IDs[tcNum]].reg =
 GCLK_PCHCTRL_GEN_GCLK0_Val | (1 << GCLK_PCHCTRL_CHEN_Pos); //use clock generator 0
  
  // Normal (single slope) PWM operation: timers countinuously count up to PER
  // register value and then is reset to 0
  //Configure TCC
  IR_TCCx = (Tcc*) GetTC(pinDesc.ulPWMChannel);
  //Reset
  IR_TCCx->CTRLA.bit.SWRST = 1;
  while (IR_TCCx->SYNCBUSY.bit.SWRST);
  // Disable TCCx
  IR_TCCx->CTRLA.bit.ENABLE = 0;
  while (IR_TCCx->SYNCBUSY.bit.ENABLE);
  // Sent pre-scaler to 1
  IR_TCCx->CTRLA.reg = TCC_CTRLA_PRESCALER_DIV11 | TCC_CTRLA_PRESCSYNC_GCLK;
  //Set TCCx as normal PWM
  IR_TCCx->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;
  while (IR_TCCx->SYNCBUSY.bit.WAVE);
  while (IR_TCCx->SYNCBUSY.bit.CC0 || IR_TCCx->SYNCBUSY.bit.CC1);

  // Each timer counts up to a maximum or TOP value set by the PER register,
  // this determines the frequency of the PWM operation.
  uint32_t cc = 120000000UL/(khz*1000) - 1;
  // The CCx register value corresponds to the pulsewidth in microseconds (us)
  // Set the duty cycle of the PWM on TCCx to 33%
  IR_TCCx->CC[tcChannel].reg = (uint32_t) cc/3;      
  while (IR_TCCx->SYNCBUSY.bit.CC0 || IR_TCCx->SYNCBUSY.bit.CC1);

  IR_TCCx->PER.reg = cc;      // Set the frequency of the PWM on IR_TCCx
  while(IR_TCCx->SYNCBUSY.bit.PER);

  IR_TCCx->CTRLA.bit.ENABLE = 0;            //initially off will turn on later
  while (IR_TCCx->SYNCBUSY.bit.ENABLE);
}


Any idea what's going wrong here?
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Sep 12, 2019, 10:45 am
Hi cyborg5,

I've had a look at your code, perhaps I've missed something, but everything seems OK provided that you've enabled the timer after intialisation.

Are you able to generate PWM output on D7 (PD21) on the Metro M4 Grand Central using the analogWrite() function? This would at least confirm that the analogWrite() portion of your code is functioning correctly.
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Sep 13, 2019, 01:19 am
Hi MartinL,

I have been trying to start and stop, enable and disable TCC1 timer using the commands from you but nothing seams to work for me. The output D10 always continues to pulse.  I can not seam to stop or disable the timer output.  I am doing the following;

void setup() {
TCC1StartTimer();

}

void loop() {

 delay(500);
TCC1->CTRLBSET.reg = TCC_CTRLBSET_CMD_STOP;  // stop TCC1
while (TCC1->SYNCBUSY.bit.CTRLB);
//TCC1->CTRLA.bit.ENABLE = 0;  // disable TCC1
//while (TCC1->SYNCBUSY.bit.ENABLE);

}

Thanks,
Jim
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Sep 13, 2019, 09:17 am
Hi Jim,

Here's some example code that sets up TCC1/WO[2] on D10 (PA18) at 50Hz PWM, 50% duty-cycle. It then starts and stops the timer at 1 second intervals using the STOP and RETRIGGER commands:

Code: [Select]
// Adafruit Metro M4 Only: Set-up digital pin D10 to output 50Hz, single slope PWM with a 50% duty cycle
// Timer stop and started at 1 second intervals
void setup()
{
  // Set up the generic clock (GCLK7) to clock timer TCC1
  GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(1) |       // Divide the 48MHz clock source by divisor 1: 48MHz/1 = 48MHz
                         GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                         GCLK_GENCTRL_GENEN |        // Enable GCLK7
                         GCLK_GENCTRL_SRC_DFLL;      // Select 48MHz DFLL clock source
                         //GCLK_GENCTRL_SRC_DPLL0;     // Select 100MHz DPLL clock source
                         //GCLK_GENCTRL_SRC_DPLL0;     // Select 120MHz DPLL clock source
  while (GCLK->SYNCBUSY.bit.GENCTRL7);               // Wait for synchronization 

  GCLK->PCHCTRL[TCC1_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |        // Enable the TCC1 perhipheral channel
                                    GCLK_PCHCTRL_GEN_GCLK7;    // Connect generic clock 7 to TCC1

  // Enable the peripheral multiplexer on pin D10
  PORT->Group[g_APinDescription[10].ulPort].PINCFG[g_APinDescription[10].ulPin].bit.PMUXEN = 1;
 
  // Set the D10 (PORT_PA18) peripheral multiplexer to peripheral (even port number) F(5): TCC1, Channel 0
  PORT->Group[g_APinDescription[10].ulPort].PMUX[g_APinDescription[10].ulPin >> 1].reg |= PORT_PMUX_PMUXE(5);
 
  TCC1->CTRLA.reg = TC_CTRLA_PRESCALER_DIV8 |        // Set prescaler to 8, 48MHz/8 = 6MHz
                    TC_CTRLA_PRESCSYNC_PRESC;        // Set the reset/reload to trigger on prescaler clock                 

  TCC1->WAVE.reg = TC_WAVE_WAVEGEN_NPWM;             // Set-up TCC1 timer for Normal (single slope) PWM mode (NPWM)
  while (TCC1->SYNCBUSY.bit.WAVE)                    // Wait for synchronization

  TCC1->PER.reg = 119999;                            // Set-up the PER (period) register 50Hz PWM
  while (TCC1->SYNCBUSY.bit.PER);                    // Wait for synchronization
 
  TCC1->CC[2].reg = 59999;                           // Set-up the CC (counter compare), channel 2 register for 50% duty-cycle
  while (TCC1->SYNCBUSY.bit.CC0);                    // Wait for synchronization

  TCC1->CTRLA.bit.ENABLE = 1;                        // Enable timer TCC1
  while (TCC1->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization
}

void loop()
{         
  TCC1->CTRLBSET.reg = TCC_CTRLBSET_CMD_STOP;        // Stop TCC1
  while (TCC1->SYNCBUSY.bit.CTRLB);                  // Wait for synchronization
  delay(1000);                                       // Wait for 1 second
  TCC1->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER;   // Retrigger (start) TCC1
  while (TCC1->SYNCBUSY.bit.CTRLB);                  // Wait for synchronization
  delay(1000);                                       // Wait for 1 second
}
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: cyborg5 on Sep 13, 2019, 06:14 pm
It turns out that the code I posted earlier does work. I had a combination of software and hardware problems and when I solved the software problem it didn't work because I still had a hardware issue. I still have to make it work with TC timers instead of TCC in some cases for some pins but I think I can get that on my own. If I can't, I'll be back. Thanks for taking a look at this.
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Sep 13, 2019, 07:14 pm
Hi MartinL,
Thank you for your reply.  That works great... I was doing something stupid.... my bad. 

I have a question for you I am basically trying to create a controlled one shot pulse low for a variable pulse time low (typically 1500us variable down to 600us.  Basic steps are below.  Any thoughts on the best way to do this.

Step 1: Output high
Step 2: Trigger one shot
Step 3: Output goes low for 1500us then output goes high
Step 4: Wait for trigger


Thanks,
Jim
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Sep 13, 2019, 11:19 pm
Hi Jim,

Essentially the SAMD51's one shot mode works in exactly the same way as standard PWM, in as much as you set up the period (PER) and counter compare (CCx) registers in the same way. The only difference is that the output pulse is generated by a software trigger and the timer automatically stops at the end of the cycle (overflow), rather than being retriggered every cycle.

To set up TCC timer in one shot mode:

Code: [Select]
TCC1->CTRLBSET.reg = TCC_CTRLBSET_ONESHOT;       // Enable one shot
while (TCC1->SYNCBUSY.bit.CTRLB);                // Wait for synchronization
TCC1->DRVCTRL.reg |= TCC_DRVCTRL_NRE2;           // Continue to drive the output on TCC1/WO[2] when timer has stopped (rather than becoming tri-state)

Note that NRE stands for Non Recoverable State Output Enable.

To trigger a one shot pulse:

Code: [Select]
TCC1->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER;         // Retrigger the timer's One/Multi-Shot pulses
while (TCC1->SYNCBUSY.bit.CTRLB);                        // Wait for synchronization

To invert the output for an active low pulse:

Code: [Select]
TCC1->DRVCTRL.reg |= TCC_DRVCTRL_INVEN2;        // Invert the output to generate an active low pulse on TCC1/WO[2]
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Sep 14, 2019, 01:26 am
Hi MartinL,

Thanks for your help.  One or maybe three questions.

1) Do I add those lines inside the timer setup or external?  I know the RETRIGGER is external but not sure on the others.

2) If they are to be added inside the timer does the order of placement matter?

3) I am using PER reg to set time low but what should I set the CC[2] reg too?


Thanks,
Jim
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Sep 14, 2019, 02:49 am
Hi MartinL,

I did some playing it is working great but I am seeing some issues with the one shot;

1) the issue is timer output initially starts out low.
2) If I retrigger in setup() to run just once the output starts out low and then goes high.  I never get the high, low, high transistion.
3) If I retrigger in loop() I get a very small positive going pulse at the beginning then all is well after that.

I have attached my code.  Is there a way to set the output high to start I tried a digitalWrite but that did not work.  Your input would be greatly appreciated.

Thanks,
Jim
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Sep 14, 2019, 09:50 am
Hi Jim,

Here's some example code that sets up the TCC1/WO[2] timer on digital pin D10 for one shot operation. The output signal by default is high and upon triggering generates a 1500us low pulse. The process repeats every 4 milliseconds:

Code: [Select]
// Adafruit Metro M4 Only: Set-up digital pin D10 to output a one shot pulse with 1500us pulse width
// at an interval of 4 milliseconds
void setup()
{
  // Set up the generic clock (GCLK7) to clock timer TCC1
  GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(1) |       // Divide the 48MHz clock source by divisor 1: 48MHz/1 = 48MHz
                         GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                         GCLK_GENCTRL_GENEN |        // Enable GCLK7
                         GCLK_GENCTRL_SRC_DFLL;      // Select 48MHz DFLL clock source
                         //GCLK_GENCTRL_SRC_DPLL1;     // Select 100MHz DPLL clock source
                         //GCLK_GENCTRL_SRC_DPLL0;     // Select 120MHz DPLL clock source
  while (GCLK->SYNCBUSY.bit.GENCTRL7);               // Wait for synchronization 

  GCLK->PCHCTRL[TCC1_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |        // Enable the TCC1 perhipheral channel
                                    GCLK_PCHCTRL_GEN_GCLK7;    // Connect generic clock 7 to TCC1

  // Enable the peripheral multiplexer on pin D10
  PORT->Group[g_APinDescription[10].ulPort].PINCFG[g_APinDescription[10].ulPin].bit.PMUXEN = 1;
 
  // Set the D10 (PORT_PA18) peripheral multiplexer to peripheral (even port number) F(5): TCC1, Channel 2
  PORT->Group[g_APinDescription[10].ulPort].PMUX[g_APinDescription[10].ulPin >> 1].reg |= PORT_PMUX_PMUXE(5);
 
  TCC1->CTRLA.reg = TC_CTRLA_PRESCALER_DIV8 |        // Set prescaler to 8, 48MHz/8 = 6MHz
                    TC_CTRLA_PRESCSYNC_PRESC;        // Set the reset/reload to trigger on prescaler clock                 

  TCC1->CTRLBSET.reg = TCC_CTRLBSET_ONESHOT;         // Enable one shot
  while (TCC1->SYNCBUSY.bit.CTRLB);                  // Wait for synchronization
 
  TCC1->DRVCTRL.reg |= TCC_DRVCTRL_NRE2;             // Continue to drive the output on TCC1/WO[2] when timer has stopped (rather than becoming tri-state) 
  TCC1->DRVCTRL.reg |= TCC_DRVCTRL_INVEN2;           // Invert the output to generate an active low pulse on TCC1/WO[2]
 
  TCC1->WAVE.reg = TC_WAVE_WAVEGEN_NPWM;             // Set-up TCC1 timer for Normal (single slope) PWM mode (NPWM)
  while (TCC1->SYNCBUSY.bit.WAVE)                    // Wait for synchronization

  TCC1->PER.reg = 8999;                              // Set-up the PER (period) register for 1500us pulse period
  while (TCC1->SYNCBUSY.bit.PER);                    // Wait for synchronization
 
  TCC1->CC[2].reg = 8999;                            // Set-up the CC (counter compare), channel 2 register for 1500us pulse width
  while (TCC1->SYNCBUSY.bit.CC2);                    // Wait for synchronization

  TCC1->CTRLA.bit.ENABLE = 1;                        // Enable timer TCC1
  while (TCC1->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization
}

void loop()
{         
  TCC1->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER;   // Retrigger a one shot pulse
  while (TCC1->SYNCBUSY.bit.CTRLB);                  // Wait for synchronization
  delay(4);                                          // Wait for 4 milliseconds
}

If you need to reduce the pulse width to 600us then it's necessary to change the CC[2]/CCBUF[2] register to 3599.

Here's the output, 1500us (1.5ms) active low one shot pulse triggered every 4 milliseconds:

(https://forum.arduino.cc/index.php?action=dlattach;topic=589655.0;attach=324397)
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Sep 14, 2019, 06:53 pm
Hi MartinL,

Thanks for the code and your reply.  I still see the same issue with the timer output initially starting low.  The first one shot trigger is a very short positive pulse then goes low for 1.5ms and is then fine after seeing the high-low-high transition.  Is there a way to have the timer start with the output defaulting state high so I can use the first shot.  I have attached a photo of what is being captured.

Best Regards,
Jim
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Sep 14, 2019, 09:02 pm
Hi MartinL,

I am trying to setup an interrupt when the low pulse goes high and I am getting some errors compiling.  I have attached the code.  I must be doing something stupid.

Thanks,
Jim


Arduino: 1.8.9 (Mac OS X), Board: "Adafruit Metro M4 (SAMD51), Enabled, 120 MHz (standard), Small (-Os) (standard), 50 MHz (standard), Arduino, Off"

/Users/JimD/Documents/Arduino/TCC1_Timer_Test_Jim/TCC1_Timer_Test_Jim.ino: In function 'void setup()':
TCC1_Timer_Test_Jim:16:20: error: 'TCC1_IRQn' was not declared in this scope
   NVIC_SetPriority(TCC1_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC1 to 0 (highest)
                    ^
/Users/JimD/Documents/Arduino/TCC1_Timer_Test_Jim/TCC1_Timer_Test_Jim.ino: In function 'void TCC1_Timer()':
TCC1_Timer_Test_Jim:77:20: error: 'TCC1_IRQn' was not declared in this scope
   NVIC_SetPriority(TCC1_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC1 to 0 (highest)
                    ^
exit status 1
'TCC1_IRQn' was not declared in this scope

This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Sep 14, 2019, 09:19 pm
Hi Jim,

Regarding the timer glitch, it appears as though enabling the timer starts a oneshot trigger, as the output goes inactive high 1.5ms later.

The workaround is to leave the counter compare CC[2] register at its default of 0, enable the timer and then set CC[2] to 8999 afterwards:

Code: [Select]
// Remove setting the CC[2] register here...
//TCC1->CC[2].reg = 8999;                            // Set-up the CC (counter compare), channel 2 register for 1500us pulse width
//while (TCC1->SYNCBUSY.bit.CC2);                    // Wait for synchronization
 
TCC1->CTRLA.bit.ENABLE = 1;                        // Enable timer TCC1
while (TCC1->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization
 
TCC1->CC[2].reg = 8999;                            // Set-up the CC (counter compare), channel 2 register for 1500us pulse width
while (TCC1->SYNCBUSY.bit.CC2);                    // Wait for synchronization
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Sep 14, 2019, 09:32 pm
In the case of the interrupts on the SAMD51:

TCC1 overflow (OVF) interrupt:

Code: [Select]
NVIC_SetPriority(TCC1_0_IRQn, 0);      // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC1 to 0 (highest)
NVIC_EnableIRQ(TCC1_0_IRQn);           // Connect the TCC1 timer to the Nested Vector Interrupt Controller (NVIC)

TCC1 match compare channel 2 (MC2) interrupt:

Code: [Select]
NVIC_SetPriority(TCC1_3_IRQn, 0);      // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC1 to 0 (highest)
NVIC_EnableIRQ(TCC1_3_IRQn);           // Connect the TCC1 timer to the Nested Vector Interrupt Controller (NVIC)
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Sep 15, 2019, 07:14 pm
Hi MartinL,

Thanks for your help this works great if I leave CC.reg at 0.  If I try to set it back to 8999 then the pulse is still there on the first one shot.  Not sure?

I have added in an OVF interrupt this also works great.  The interrupt triggers on the falling edge of the one shot.  One question is it possible to have the interrupt trigger on the rising edge of the one shot pulse.


Thanks,
Jim
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Sep 15, 2019, 11:13 pm
Hi Jim,

Quote
If I try to set it back to 8999 then the pulse is still there on the first one shot.  Not sure?
The glitch pulse is most likely being generated, because the one shot pulse is being triggered immediately after enabling the timer and setting the CC[2] register. If you add a delay of say 4 milliseconds at the end of the setup():

Code: [Select]
delay(4);
The glitch pulse will extend out to 4ms:

(https://forum.arduino.cc/index.php?action=dlattach;topic=589655.0;attach=324590)

Quote
One question is it possible to have the interrupt trigger on the rising edge of the one shot pulse.
Yes, if you use the Match Compare on channel 2 (MC2) interrupt, together with TCC1_3_Irq, this will call the TCC1_3_Handler() interrupt service routine function on the rising edge of the one shot pulse.
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Sep 15, 2019, 11:38 pm
Hi Jim,

Unlike the SAMD21, the SAMD51 uses separate TCC1 timer Handler functions for its overflow (OVF) and each of its match compare channels (MCx). This means that the SAMD51 doesn't need to test the interrupt flags in Handler function itself, (in order to determine which interrupt has been called), thereby providing a small efficiency saving.

Which Nested Vector Interrupt Controller (NVIC) interrupt (e.g. TCC1_0_Irq) is connected to what TCC1 interrupt flag, is detailed in section 10.2 NVIC Interrupt Line Mapping in the SAMD51 datasheet.
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Sep 16, 2019, 01:43 am
Hi MartinL,

I tried the 4ms delay - it delays things but does not get rid of the initial pulse.  I tried setting up match for a interrupt on the rising edge but it did not change, I did however get another pulse between each one shot.  I must be doing something wrong.  I have attached a capture and my code.

On a side note, how do you like your Rigol scope.


Thanks,
Jim
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Sep 16, 2019, 04:52 pm
Hi MartinL,

Everything is working fine I forgot to invert my pulse.... my bad.  Thanks for all your help.

Jim
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Sep 16, 2019, 06:25 pm
Hi Jim,

Glad to hear that everything's working fine.

The Rigol scope for the price is excellent. I bought the basic, 4 channel, DS1054Z, it works up to about 50MHz or so, which is good enough for 99% of what I want to do.

Shame that most the extra features that were available as a trial, have long since expired. These extra features such as increased bandwidth and serial decoding are quite expensive, with each one costing almost half the price of the scope itself. So I continue to use the scope without all the fancy enhancements, it's still really very good though.
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: cyborg5 on Sep 16, 2019, 10:23 pm
Martin,

First of all thanks for all you are doing to help people in this thread including me. As a reminder I'm working on infrared library IRLib2. I need to generate PWM with a 33% duty cycle and a range of frequencies from about 36-58 kHz. Because I'm a glutton for punishment, I don't just want to get it working on one or two pins because I know as soon as I do someone is going to say "Why don't you support pin number (whatever)?" So I'm trying to write code that will work on any PWM pin. I got it working on pins that use TCC but it's not working on TC timers. I'm using as a model the code in wiring_analog.c for the function digitalWrite(). I don't particularly like the fact that fact that code uses COUNT8 but I can't get it to work anyway let alone switch to COUNT16.

I'm calculating the values using 120000000/(khz*1000) which for a sample value 30 kHz gives me 4000 which is obviously too big for an 8-bit counter. So I put a 1/16 divisor in thinking that would get me down into the 8-bit range. When I run the code, it seems to lock up. The serial monitor becomes unresponsive and I have to reboot the board to upload again. Here is my code. If you can get the eight bit version working that would be good enough. This doesn't need to be super accurate. But if you can show me how to do 16-bit that would be great.

Code: [Select]
  GCLK->PCHCTRL[GCLK_CLKCTRL_IDs[tcNum]].reg =
GCLK_PCHCTRL_GEN_GCLK0_Val | (1 << GCLK_PCHCTRL_CHEN_Pos); //use clock generator 0
  if (tcNum >= TCC_INST_NUM) {
// -- Configure TC
Tc* IR_PWM_TCx = (Tc*) GetTC(pinDesc.ulPWMChannel);
//reset
    IR_PWM_TCx->COUNT8.CTRLA.bit.SWRST = 1;
    while (IR_PWM_TCx->COUNT8.SYNCBUSY.bit.SWRST);
    // Disable TCx
    IR_PWM_TCx->COUNT8.CTRLA.bit.ENABLE = 0;
    while (IR_PWM_TCx->COUNT8.SYNCBUSY.bit.ENABLE);
    // Set Timer counter Mode to 8 bits, normal PWM, prescaler 1/16
IR_PWM_TCx->COUNT8.CTRLA.reg = TC_CTRLA_MODE_COUNT8 | TC_CTRLA_PRESCALER_DIV16;
    IR_PWM_TCx->COUNT8.WAVE.reg = TC_WAVE_WAVEGEN_NPWM;
    while (IR_PWM_TCx->COUNT8.SYNCBUSY.bit.CC0);
    // Set the initial value
    // Each timer counts up to a maximum or TOP value set by the PER register,
    // this determines the frequency of the PWM operation.
    uint32_t cc = 120000000UL/16/(khz*1000) - 1;
    // The CCx register value corresponds to the pulsewidth in microseconds (us)
    // Set the duty cycle of the PWM on TCx to 33%
    IR_PWM_TCx->COUNT8.CC[tcChannel].reg = (uint8_t) cc/3;
    while (IR_PWM_TCx->COUNT8.SYNCBUSY.bit.CC0);
    IR_PWM_TCx->COUNT8.PER.reg = cc;
    while (IR_PWM_TCx->COUNT8.SYNCBUSY.bit.PER);
    IR_PWM_TCx->COUNT8.CTRLA.bit.ENABLE = 0;      //temporarily disable, will enable later
    while (IR_PWM_TCx->COUNT8.SYNCBUSY.bit.ENABLE);
  } else {
// Use TCC timers. This code works so I won't post it.
}
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Sep 16, 2019, 11:54 pm
Hi MartinL,

Thanks for the feedback on the scope.  I had a look awhile back, they seem to be very good value - price performance.  Much less than Techtronics scopes.

I have another question.  I have setup a second oneshot timer TCC4 which works fine... thanks.  I am trying to trigger the TCC4 one shot inside the TCC1 interrupt but I can not get it working.  I have tried wrapping the retrigger commands in noInterrupt() and interrupt() but no luck.  Is there a way to trigger the 2nd one shot after the 1st one shots pulse goes high.

Thanks,
Jim
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Sep 17, 2019, 10:11 am
Hi cyborg5,

The TC timers have less features than the TCC timers with the main issue being the lack of frequency control in both 16-bit and 32-bit modes. To the control timer's frquency requires the use of either of 8-bit mode or 16-bit/32-bit in Match PWM (MPWM) mode. However, MPWM mode sacrifices the timer's CC0 channel to achieve this.

Looking at your code, it could be a synchronization problem, if you're using CC1 register but synchronizing on the CC0?

The other thing is setting the prescaler and counter synchronization in the CTRLA register. This determines whether the timer should wrap around/reset on the next generic clock or prescaler clock. If you're using the prescaler (DIV16) then it's better to select prescaler clock synchronization (TC_CTRLA_PRESCSYNC_PRESC):

Code: [Select]
IR_PWM_TCx->COUNT8.CTRLA.reg = TC_CTRLA_MODE_COUNT8 |
                               TC_CTRLA_PRESCALER_DIV16 |
                               TC_CTRLA_PRESCSYNC_PRESC;
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Sep 17, 2019, 12:43 pm
Hi Jim,

I didn't have much luck trying to retrigger TCC4 from within TCC1's interrupt service routine either.

Perhaps a more efficient way is to use the SAMD51's event system. The event system is a 32 channel peripheral-to-peripheral highway that allows them to communicate without CPU intervention.

It's possible to get TCC1 to generate a match compare event on the signal's rising edge on channel 2 and set up TCC4 to retrigger each time it receives this event.

Here I've set up TCC1 and TCC4 in one shot mode and to generate a 1.5ms pulse. TCC1 retriggers TCC4 on its rising edge, causing TCC4 to generate a similar pulse on its channel:

Code: [Select]
// Adafruit Metro M4 Only: Set-up digital pins D10 (TCC1) to output a one shot pulse with 1500us pulse width
// at an interval of 4 milliseconds
// Use the event system to retrigger the same 1500us output pulse on D5 (TCC4) on the rising edge of D10 (TCC1)
void setup()
{
  MCLK->APBBMASK.reg |= MCLK_APBBMASK_EVSYS;         // Switch on the event system peripheral
 
  // Set up the generic clock (GCLK7) to clock timer TCC1
  GCLK->GENCTRL[7].reg = GCLK_GENCTRL_DIV(1) |       // Divide the 48MHz clock source by divisor 1: 48MHz/1 = 48MHz
                         GCLK_GENCTRL_IDC |          // Set the duty cycle to 50/50 HIGH/LOW
                         GCLK_GENCTRL_GENEN |        // Enable GCLK7
                         GCLK_GENCTRL_SRC_DFLL;      // Select 48MHz DFLL clock source
                         //GCLK_GENCTRL_SRC_DPLL1;     // Select 100MHz DPLL clock source
                         //GCLK_GENCTRL_SRC_DPLL0;     // Select 120MHz DPLL clock source
  while (GCLK->SYNCBUSY.bit.GENCTRL7);               // Wait for synchronization 
 
  // TCC1 /////////////////////////////////////////////////////////////////////////////////////////////////
 
  GCLK->PCHCTRL[TCC1_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |        // Enable the TCC1 perhipheral channel
                                    GCLK_PCHCTRL_GEN_GCLK7;    // Connect generic clock 7 to TCC1
 
  // Enable the peripheral multiplexer on pin D10
  PORT->Group[g_APinDescription[10].ulPort].PINCFG[g_APinDescription[10].ulPin].bit.PMUXEN = 1;
 
  // Set the D10 (PORT_PA18) peripheral multiplexer to peripheral (even port number) F(5): TCC1, Channel 2
  PORT->Group[g_APinDescription[10].ulPort].PMUX[g_APinDescription[10].ulPin >> 1].reg |= PORT_PMUX_PMUXE(5);
 
  TCC1->CTRLA.reg = TCC_CTRLA_PRESCALER_DIV8 |       // Set prescaler to 8, 48MHz/8 = 6MHz
                    TCC_CTRLA_PRESCSYNC_PRESC;       // Set the reset/reload to trigger on prescaler clock                 

  TCC1->CTRLBSET.reg = TCC_CTRLBSET_ONESHOT;         // Enable one shot
  while (TCC1->SYNCBUSY.bit.CTRLB);                  // Wait for synchronization
 
  TCC1->DRVCTRL.reg |= TCC_DRVCTRL_NRE2;             // Continue to drive the output on TCC1/WO[2] when timer has stopped (rather than becoming tri-state) 
  TCC1->DRVCTRL.reg |= TCC_DRVCTRL_INVEN2;           // Invert the output to generate an active low pulse on TCC1/WO[2]
 
  TCC1->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;            // Set-up TCC1 timer for Normal (single slope) PWM mode (NPWM)
  while (TCC1->SYNCBUSY.bit.WAVE)                    // Wait for synchronization

  TCC1->PER.reg = 8999;                              // Set-up the PER (period) register for 1500us pulse period
  while (TCC1->SYNCBUSY.bit.PER);                    // Wait for synchronization

  // TCC4 /////////////////////////////////////////////////////////////////////////////////////////////////

  GCLK->PCHCTRL[TCC4_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |        // Enable the TCC1 perhipheral channel
                                    GCLK_PCHCTRL_GEN_GCLK7;    // Connect generic clock 7 to TCC4
 
  // Enable the peripheral multiplexer on pin D10
  PORT->Group[g_APinDescription[5].ulPort].PINCFG[g_APinDescription[5].ulPin].bit.PMUXEN = 1;
 
  // Set the D5 (PORT_PB14) peripheral multiplexer to peripheral (even port number) F(5): TCC4, Channel 0
  PORT->Group[g_APinDescription[5].ulPort].PMUX[g_APinDescription[5].ulPin >> 1].reg |= PORT_PMUX_PMUXE(5);
 
  TCC4->CTRLA.reg = TCC_CTRLA_PRESCALER_DIV8 |       // Set prescaler to 8, 48MHz/8 = 6MHz
                    TCC_CTRLA_PRESCSYNC_PRESC;       // Set the reset/reload to trigger on prescaler clock                 

  TCC4->CTRLBSET.reg = TCC_CTRLBSET_ONESHOT;         // Enable one shot
  while (TCC4->SYNCBUSY.bit.CTRLB);                  // Wait for synchronization
 
  TCC4->DRVCTRL.reg |= TCC_DRVCTRL_NRE0;             // Continue to drive the output on TCC4/WO[0] when timer has stopped (rather than becoming tri-state) 
  TCC4->DRVCTRL.reg |= TCC_DRVCTRL_INVEN0;           // Invert the output to generate an active low pulse on TCC4/WO[0]
 
  TCC4->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;            // Set-up TCC4 timer for Normal (single slope) PWM mode (NPWM)
  while (TCC4->SYNCBUSY.bit.WAVE)                    // Wait for synchronization

  TCC4->PER.reg = 8999;                              // Set-up the PER (period) register for 1500us pulse period
  while (TCC4->SYNCBUSY.bit.PER);                    // Wait for synchronization

  // Event System /////////////////////////////////////////////////////////////////////////////////////////////

  // Select the event system user on channel 0 (USER number = channel number + 1)
  EVSYS->USER[EVSYS_ID_USER_TCC4_EV_0].reg = EVSYS_USER_CHANNEL(1);                 // Set the event user (receiver) as timer TCC4 event action 0

  // Select the event system generator on channel 0
  EVSYS->Channel[0].CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT |              // No event edge detection
                                  EVSYS_CHANNEL_PATH_ASYNCHRONOUS |                 // Set event path as asynchronous
                                  EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_TCC1_MCX_2);     // Set event generator (sender) as TCC1 Match Capture channel 2
 
  TCC1->EVCTRL.reg |= TCC_EVCTRL_MCEO2;              // Output event enable on TCC1 Match Capture channel 2 (MC2)

  TCC4->EVCTRL.reg |= TCC_EVCTRL_TCEI0 |             // Input event enable 0 on TCC4
                      TCC_EVCTRL_EVACT0_RETRIGGER;   // Retrigger timer TCC4 upon receiving the event

  // Start TCC1 and TCC4 //////////////////////////////////////////////////////////////////////////////////////////
 
  TCC1->CTRLA.bit.ENABLE = 1;                        // Enable timer TCC1
  while (TCC1->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization
 
  TCC1->CC[2].reg = 8999;                            // Set-up the CC (counter compare), channel 2 register for 1500us pulse width
  while (TCC1->SYNCBUSY.bit.CC2);                    // Wait for synchronization
 
  TCC4->CTRLA.bit.ENABLE = 1;                        // Enable timer TCC4
  while (TCC4->SYNCBUSY.bit.ENABLE);                 // Wait for synchronization
 
  TCC4->CC[0].reg = 8999;                            // Set-up the CC (counter compare), channel 0 register for 1500us pulse width
  while (TCC4->SYNCBUSY.bit.CC0);                    // Wait for synchronization
}

void loop()
{         
  TCC1->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER;   // Retrigger a one shot pulse
  while (TCC1->SYNCBUSY.bit.CTRLB);                  // Wait for synchronization
  delay(8);                                          // Wait for 8 milliseconds
}

Here's the output, pulses spaced at 8ms intervals:

(https://forum.arduino.cc/index.php?action=dlattach;topic=589655.0;attach=324760)
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Sep 18, 2019, 01:43 am
Hi MartinL,

That is pure brilliance!  I did not know that you can combine timers like you did.  It works perfectly.  Thank you so much for all your help and willingness to share your expertise with timers.


Thanks Again,
Jim
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Sep 18, 2019, 04:49 pm
Hi MartinL,

I need to know when the 2nd oneshot pulse goes high telling the me that the oneshot routine is done.  Is there another way other than using an MC interrupt like we discussed before.


Thanks,
Jim
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Sep 18, 2019, 10:59 pm
Hi Jim

It depends on whether it's a peripheral or the the CPU that needs to know when the one shot routine is done?

If it's simply triggering another peripheral, for example another timer, then it's possible to use the event system.

If however it's your program (CPU) then it will be necessary to call an interrupt. 

Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: Jimbee on Sep 19, 2019, 02:45 am
Hi MartinL,

I need to let the main code know that it is done its task.  I have set it up with an interrupt and it works great.  Thanks again for your help.

Jim
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: cyborg5 on Sep 19, 2019, 07:18 pm
Martin,

Still working on trying to get my infrared library working on M4 with TC timers. Could not figure out what was going wrong so I basically just cut and pasted the code from wiring_analog.c into my code verbatim and thought I would worry about messing with the frequency later. I just needed to get something running. Much to my surprise it didn't work. Here's the culprit… At the end of the initialization procedure there is 2 lines that turn on the TC.

Code: [Select]
TCx->COUNT8.CTRLA.bit.ENABLE = 1;
while (TCx->COUNT8.SYNCBUSY.bit.ENABLE);

I initialize that by turning it off with

Code: [Select]
TCx->COUNT8.CTRLA.bit.ENABLE = 0;
while (TCx->COUNT8.SYNCBUSY.bit.ENABLE);

I then call a separate functions to turn it off and on without completely reinitializing

Code: [Select]
void IRStartPWM51(void) {
  TCx->COUNT8.CTRLA.bit.ENABLE = 1;
  while (TCx->COUNT8.SYNCBUSY.bit.ENABLE);
}

void IRStopPWM51(void) {
  TCx->COUNT8.CTRLA.bit.ENABLE = 0;
  while (TCx->COUNT8.SYNCBUSY.bit.ENABLE);
}

I call those two functions as necessary. The problem is anytime I do the disable it locks up the system. Or at least the serial monitor won't print debug statements. As originally written the initialization routine leaves it disabled and that crashes immediately. If I change it to enable and then try to later disable and reenable a crashes on the disable. On the TCC version I can disable and reenable at will using this…

Code: [Select]
    TCCx->CTRLA.bit.ENABLE = 0;            //or use 1 to enable
    while (IR_TCCx->SYNCBUSY.bit.ENABLE);

I suppose I could rewrite everything so that anytime I want to turn it on I do a complete initialization and when I want to turn it I just do a digitalWrite(pin,LOW); but that's going to make all the other logic in my program a little bit difficult. Can you think of any way to easily disable and reenable PWM on TC pins? By the way this is on an Adafruit Grand Central M4 pin 9 defined as follows…
Code: [Select]
  { PORTB, 2, PIO_DIGITAL, PIN_ATTR_PWM_E, No_ADC_Channel, TC6_CH0, TC6_CH0, EXTERNAL_INT_3 },


Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: MartinL on Sep 20, 2019, 11:12 am
Hi cyborg5,

I set up a test with timer TC0 in 8-bit mode, on port PA04, that's pin A3 on the Metro M4 that I'm using, pin A13 on the Metro M4 Grand Central. PWM output is at 457Hz on this pin.

The test enables and disables the TC0 timer every second and each time sends the contents of the TC0's COUNT register 10 times to the console via its native USB port.

I find it's possible to enable and disable the TC0 at will, without crashing the sketch.

Here's the test code:

Code: [Select]
// Enable/Disable test on TC0, pin A3 on Metro M4, pin A13 on Metro M4 Grand Central
void setup()
{
  Serial.begin(115200);                                        // Open USB serial comms
 
  MCLK->APBAMASK.reg |= MCLK_APBAMASK_TC0;                     // Activate timer TC0
 
  GCLK->PCHCTRL[TC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |         // Enable perhipheral channel
                                   GCLK_PCHCTRL_GEN_GCLK0;     // Connect generic clock 0 to TC0

  // Enable the peripheral multiplexer on pin A3
  PORT->Group[g_APinDescription[A3].ulPort].PINCFG[g_APinDescription[A3].ulPin].bit.PMUXEN = 1;
 
  // Set the A3 peripheral multiplexer to peripheral E(4): TC0, Channel 0
  PORT->Group[g_APinDescription[A3].ulPort].PMUX[g_APinDescription[A3].ulPin >> 1].reg |= PORT_PMUX_PMUXE(4);
 
  TC0->COUNT8.CTRLA.reg = TC_CTRLA_PRESCALER_DIV1024 |     // Set prescaler to 1024, 120MHz/1024 = 117.19kHz
                           TC_CTRLA_PRESCSYNC_PRESC  |     // Set the reset/reload to trigger on prescaler clock
                           TC_CTRLA_MODE_COUNT8;           // Set the counter to 8-bit mode

  TC0->COUNT8.WAVE.reg = TC_WAVE_WAVEGEN_NPWM;      // Set-up TC0 timer for Normal PWM mode (NPWM)

  TC0->COUNT8.PER.reg = 0xFF;                       // Use PER register as TOP value, set for 457.76Hz PWM
  while (TC0->COUNT8.SYNCBUSY.bit.PER);             // Wait for synchronization

  TC0->COUNT8.CC[0].reg = 127;                      // Set the duty cycle to 50% (CC1 half of PER)
  while (TC0->COUNT8.SYNCBUSY.bit.CC0);             // Wait for synchronization

  TC0->COUNT8.CTRLA.bit.ENABLE = 1;                 // Enable timer TC0
  while (TC0->COUNT8.SYNCBUSY.bit.ENABLE);          // Wait for synchronization
}

void loop()
{
  TC0->COUNT8.CTRLA.bit.ENABLE = 1;                       // Enable timer TC0
  while (TC0->COUNT8.SYNCBUSY.bit.ENABLE);                // Wait for synchronization
  for (uint8_t i = 0; i < 10; i++)
  {
    TC0->COUNT8.CTRLBSET.reg = TC_CTRLBSET_CMD_READSYNC;   // Trigger a read synchronization on the COUNT register
    while (TC0->COUNT8.SYNCBUSY.bit.CTRLB);                // Wait for synchronization
    while (TC0->COUNT8.SYNCBUSY.bit.COUNT);                // Wait for read synchronization
    Serial.println(TC0->COUNT8.COUNT.reg);                 // Display the TC0 COUNT register
  }
  Serial.println();
  delay(1000);                                             // Wait for 1 second
  TC0->COUNT8.CTRLA.bit.ENABLE = 0;                        // Enable timer TC0
  while (TC0->COUNT8.SYNCBUSY.bit.ENABLE);                 // Wait for synchronization
  for (uint8_t i = 0; i < 10; i++)
  {
    TC0->COUNT8.CTRLBSET.reg = TC_CTRLBSET_CMD_READSYNC;   // Trigger a read synchronization on the COUNT register
    while (TC0->COUNT8.SYNCBUSY.bit.CTRLB);                // Wait for synchronization
    while (TC0->COUNT8.SYNCBUSY.bit.COUNT);                // Wait for read synchronization
    Serial.println(TC0->COUNT8.COUNT.reg);                 // Display the TC0 COUNT register
  }
  Serial.println();
  delay(1000);                                             // Wait for 1 second
}
Title: Re: Metro M4 Express ATSAMD51 PWM Frequency and Resolution
Post by: cyborg5 on Sep 20, 2019, 06:18 pm
Many thanks. I will take a look at this. For now I've been able to reorganize the code so that I can just initialize it for every on cycle and do a analogWrite(pin,0) to turn it off. My on and off timing is not so critical especially on a 120 MHz processor. Doing a complete reinitialize every time I want to turn it on is not time prohibitive.