Go Down

Topic: Arduino ZERO timer interrupts (Read 28206 times) previous topic - next topic

MartinL

#30
Jul 18, 2018, 04:23 pm Last Edit: Jul 18, 2018, 04:23 pm by MartinL
Hi Dan,

Looking at the code of the second example, the interrupt service routine on line 114 needs to be changed from:

Code: [Select]
void TCC1_Handler()
to:

Code: [Select]
void TC4_Handler()

cbob

Oh jeez... sure! Thats it!
How could I've missed it ?! Copy and paste - obviously the problem
-> something like that comes from something like that :(   (german: sowas kommt von sowas :) )

Thank you MartinL! Now I try to make the best out of this  - perhaps it is sufficient.

But well maybe you could give some hints for changing the frequency on the run. Not the whole procedure but some important notes for what should be payed attention for (syncing?).

And again thanks MartinL and pardon for having wasted your time with problems like this!

Oh, and PS: can there appear problems with other libraries? I think it could be difficult to get I2C running because of interrupts? I plan to use an Adafruit LCD shield for printing out some informations about position and velocity.

MartinL

Hi Dan,

Sometimes it's good to have a fresh pair of eyes go over things. I've wasted countless days of my life hunting down similar logical errors in my code, I guess it comes with the territory.

The SAMD21 microcontroller on the Arduino Zero has two main types of timer, namely TCC (Timer/Counter for Control Applications) and TC (Timer/Counter).  

While both types of timer are capable of PWM output, the TCC timers (TCC0, TCC1 and TCC2) are more fully featured than the TC timers (TC3, TC4 and TC5). Usually, I use the TCCx timers for PWM or pulsed outputs and the TCx timers for internal timing.

One of the main advantages of the TCCx timers is that they offer buffered counter compare (CCBx) and period (PERB) registers, that only update the timer outputs on the next update (start of timer cycle), this prevents glitches from appearing on your output when you change the PWM signal's duty-cycle or frequency.

It's possible to use the TC timers in a similar manner, but it means that the duty cycle or frequency changes must occur in an interrupt service routine, triggered at the start of each timer cycle.

The TCCx timers work completely autonomously and independently of your code, so won't affect your other libraries, provided you don't use any interrupt service routine.

If you require any TCCx timer example code just let me know?

Kind regards,
Martin

cbob

Thank you MartinL for the quick reply.

And yes I know that the TCCx timer have those advantages but same here - it was not possible for me to get a working example. I've tried to read the samd21 datasheet but there was no chance to find out which registers are to be set or to be initialized to get it working even with one of the few examples i've found in the www.
Especially the possibility of TCCx to work independently of the code is one of the most interesting things for me (for creating a controllable pulse sequence).

btw: I've got another question for my second example: it is always the same frequency the LED is blinking no matter which value I've set in lines 68/70 (REG_TC4_COUNT8_CCx). I thought these are the compare registers and if I set it to a very low value the frequency is getting higher?!

Dan

MartinL

Hi Dan,

Changing the TC's CCx register just changes duty cycle or in other words the pulse width. To change the frequency you need to alter the TC's PER (period) register:

Code: [Select]
REG_TC4_COUNT8_PER = 0xFF;                   // Set the timer TC4 period register in 8-bit mode
while (TC4->COUNT8.SYNCBUSY.bit.PER);        // Wait for synchronization

Note that on the TC timers the PER register only works for the timer in 8-bit mode.

Reducing the value of the PER register will have the effect of increasing the frequency, but at the expense of losing some resolution.

Regarding the TCCx timers, what pins, duty-cycle, frequency range and resolution (number of steps) do you require? Are your requirements similar to your current example code?

Kind regards,
Martin

cbob

Hi Martin,

oh, ah, okay, the duty cycle. Then yes only 8-bit counter is not the best solution.
The example code was only to test the timer/counter.

Well I got to switch over to the TCCx. It does not matter which pins are used. The driver input is on pin D2 (PA08/TCC0 I guess) at the moment. Pins D3 and D4 are used for controlling direction and enable of the driver. Pins 8 and 9 are used for the incremental encoder and pin8 rises an interrupt for counting.
The duty cycle can be 50/50 because only the rising edge is important for the stepper driver. The datasheet of the driver shows that the max pulse rate should not exceed 300kHz so thats the max frequency.

Dan

MartinL

#36
Jul 19, 2018, 02:05 pm Last Edit: Jul 19, 2018, 02:21 pm by MartinL
Hi Dan

Here's some example code for timer TCC0 that outputs PWM on digital pins D10 and D12. It cycles between 10kHz and 20kHz at 50% duty cycle every 2 seconds:

Code: [Select]
// Output PWM on timer TCC0, digital pins D10 and D12
void setup()
{
  GCLK->GENDIV.reg = 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

  GCLK->GENCTRL.reg = 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 2 PWM channels: timer TCC0 outputs CC2 and CC3 on D10 and D12
  PORT->Group[g_APinDescription[10].ulPort].PINCFG[g_APinDescription[10].ulPin].bit.PMUXEN = 1;
  PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].bit.PMUXEN = 1;
 
  // Connect the TCC timers to the port outputs - port pins are paired odd PMUO and even PMUXE
  // F & E specify the timers: TCC0, TCC1 and TCC2
  PORT->Group[g_APinDescription[10].ulPort].PMUX[g_APinDescription[10].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;
 
  // Feed GCLK4 to TCC0 and TCC1
  GCLK->CLKCTRL.reg = 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 

  TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;          // Single slope PWM operation         
  while (TCC0->SYNCBUSY.bit.WAVE);                 // Wait for synchronization
 
  TCC0->PER.reg = 4799;                            // Set the frequency of the PWM on TCC0 to 10kHz
  while (TCC0->SYNCBUSY.bit.PER);                  // Wait for synchronization

  TCC0->CC[2].reg = 2399;                          // TCC0 CC2 - 50% duty-cycle on D10
  while (TCC0->SYNCBUSY.bit.CC2);                  // Wait for synchronization
  TCC0->CC[3].reg = 2399;                          // TCC0 CC3 - 50% duty-cycle on D12
  while (TCC0->SYNCBUSY.bit.CC3);                  // Wait for synchronization
 
  // Divide the 48MHz signal by 1 giving 48MHz (20.83ns) TCC0 timer tick and enable the timer
  TCC0->CTRLA.reg |= 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()
{
  //Set the frequency to 10kHz
  TCC0->PERB.reg = 4799;                           // Set the frequency to 10kHz
  while (TCC0->SYNCBUSY.bit.PERB);                 // Wait for synchronization
  TCC0->CCB[2].reg = 2399;                         // Set the duty cycle to 50% on channel 2
  while (TCC0->SYNCBUSY.bit.CCB2);                 // Wait for synchronization
  TCC0->CCB[3].reg = 2399;                         // Set the duty cycle to 50% on channel 3
  while (TCC0->SYNCBUSY.bit.CCB3);                 // Wait for synchronization
  delay(2000);                                     // Wait for 2 seconds
  //Set the frequency to 20kHz
  TCC0->PERB.reg = 2399;                           // Set the frequency to 10kHz
  while (TCC0->SYNCBUSY.bit.PERB);                 // Wait for synchronization
  TCC0->CCB[2].reg = 1199;                         // Set the duty cycle to 50% on channel 2
  while (TCC0->SYNCBUSY.bit.CCB2);                 // Wait for synchronization
  TCC0->CCB[3].reg = 1199;                         // Set the duty cycle to 50% on channel 3
  while (TCC0->SYNCBUSY.bit.CCB3);                 // Wait for synchronization
  delay(2000);                                     // Wait for 2 seconds
}

The frequency calculation is given by the formula:

PERB = 48MHz / PWM frequency - 1

For a 50% duty cycle, the CCBx registers must be half the PERB value.

cbob

#37
Jul 19, 2018, 04:14 pm Last Edit: Jul 19, 2018, 05:05 pm by cbob
AMAZING! You are my hero! Thank you Martin!

Although it works like a charm I've got one questions for understanding.
Why couldn't I use it on Pins 2 and 3 by changing the following:

Code: [Select]

 [..]

  // Enable the port multiplexer for the 2 PWM channels: timer TCC0 outputs CC2 and CC3 on D10 and D12
 [--] PORT->Group[g_APinDescription[10].ulPort].PINCFG[g_APinDescription[10].ulPin].bit.PMUXEN = 1;
 [--] PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].bit.PMUXEN = 1;

 [++] PORT->Group[g_APinDescription[2].ulPort].PINCFG[g_APinDescription[2].ulPin].bit.PMUXEN = 1;
 [++] PORT->Group[g_APinDescription[3].ulPort].PINCFG[g_APinDescription[3].ulPin].bit.PMUXEN = 1;

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

 [++] PORT->Group[g_APinDescription[2].ulPort].PMUX[g_APinDescription[2].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;
 
 [..]

 [--] TCC0->CC[0].reg = 2399;                          // TCC0 CC2 - 50% duty-cycle on D10
 [--] while (TCC0->SYNCBUSY.bit.CC0);                  // Wait for synchronization
 [--] TCC0->CC[1].reg = 2399;                          // TCC0 CC3 - 50% duty-cycle on D12
 [--] while (TCC0->SYNCBUSY.bit.CC1);                  // Wait for synchronization
  

 [++] TCC0->CC[0].reg = 2399;                          // TCC0 CC2 - 50% duty-cycle on D2
 [++] while (TCC0->SYNCBUSY.bit.CC0);                  // Wait for synchronization
 [++] TCC0->CC[1].reg = 2399;                          // TCC0 CC3 - 50% duty-cycle on D3
 [++] while (TCC0->SYNCBUSY.bit.CC1);                  // Wait for synchronization
 
 [..]

 [--]  TCC0->CCB[2].reg = 2399;                         // Set the duty cycle to 50% on channel 2
 [--]  while (TCC0->SYNCBUSY.bit.CCB2);                 // Wait for synchronization
 [--]  TCC0->CCB[3].reg = 2399;                         // Set the duty cycle to 50% on channel 3
 [--]  while (TCC0->SYNCBUSY.bit.CCB3);                 // Wait for synchronization
 
 [++]  TCC0->CCB[0].reg = 2399;                         // Set the duty cycle to 50% on channel 0
 [++]  while (TCC0->SYNCBUSY.bit.CCB0);                 // Wait for synchronization
 [++]  TCC0->CCB[1].reg = 2399;                         // Set the duty cycle to 50% on channel 1
 [++]  while (TCC0->SYNCBUSY.bit.CCB1);                 // Wait for synchronization


Learning by having some working exercises is a good way ;)

EDIT: And another question would be: can I count the pulses on pin 10? Maybe there is still an interrupt?

Thank you and kind regards
Dan

MartinL

#38
Jul 20, 2018, 09:49 am Last Edit: Jul 20, 2018, 09:53 am by MartinL
Hi Dan

Quote
Why couldn't I use it on Pins 2 and 3 by changing the following:
This is because the TCC0 channels 0 and 1 for digital pins D2 (D4 on Arduino Zero) and D3 are on peripheral E:

Code: [Select]
PORT->Group[g_APinDescription[3].ulPort].PMUX[g_APinDescription[3].ulPin >> 1].reg = PORT_PMUX_PMUXO_E | PORT_PMUX_PMUXE_E;
Quote
And another question would be: can I count the pulses on pin 10? Maybe there is still an interrupt?
The number of interrupt service routine (ISR) calls increases proportional to the increase in PWM frequency. This means that at very high PWM frequencies the processor will become overwhelmed by the number of ISR calls.

To mitigate this, one possible solution is to use the SAMD21's event system. The event system is a 12 channel highway allowing communication between peripherals without the need for CPU intervention.

In the code below the TCC0 timer generates an event each time it overflows (update condition). This event in turn is received by, and increments the TC4 timer in 16-bit mode. The code outputs PWM on D2 (D4 Arduino Zero) and D3:

Code: [Select]
// Output up to PWM on timer TCC0, digital pins D2/D4 and D3
void setup()
{
  SerialUSB.begin(115200);
  while(!SerialUSB);
 
  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;           // Switch on the event system peripheral
 
  GCLK->GENDIV.reg = 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

  GCLK->GENCTRL.reg = 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 2 PWM channels: timer TCC0 outputs CC0 and CC1 on D2/D4 and D5
  //PORT->Group[g_APinDescription[4].ulPort].PINCFG[g_APinDescription[4].ulPin].bit.PMUXEN = 1;     // Comment out for Arduino M0/M0 Pro
  PORT->Group[g_APinDescription[2].ulPort].PINCFG[g_APinDescription[2].ulPin].bit.PMUXEN = 1;     // Comment out for Arduino Zero
  PORT->Group[g_APinDescription[3].ulPort].PINCFG[g_APinDescription[3].ulPin].bit.PMUXEN = 1;
 
  // Connect the TCC timers to the port outputs - port pins are paired odd PMUO and even PMUXE
  // F & E specify the timers: TCC0, TCC1 and TCC2
  PORT->Group[g_APinDescription[3].ulPort].PMUX[g_APinDescription[3].ulPin >> 1].reg = PORT_PMUX_PMUXO_E | PORT_PMUX_PMUXE_E;
 
  // Feed GCLK4 to TCC0 and TCC1
  GCLK->CLKCTRL.reg = 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 

  // Feed GCLK4 to TC4 and TC5
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TC4 and TC5
                      GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                      GCLK_CLKCTRL_ID_TC4_TC5;     // Feed GCLK4 to TC4 and TC5
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 0 (n + 1)
                    EVSYS_USER_USER(EVSYS_ID_USER_TC4_EVU);                // Set the event user (receiver) as timer TC4
 
  EVSYS->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_TCC0_OVF) |        // Set event generator (sender) as TCC0 overflow
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0                               

  TC4->COUNT16.EVCTRL.reg |= TC_EVCTRL_TCEI |              // Enable asynchronous input events on the TC timer
                             TC_EVCTRL_EVACT_COUNT;        // Increment the TC timer each time an event is received

  TCC0->EVCTRL.reg |= TC_EVCTRL_OVFEO;             // Enable an event output on overflow of the TCC0 timer
 
  TC4->COUNT16.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1 = 48MHz
                            TC_CTRLA_ENABLE;               // Enable TC4
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);                // Wait for synchronization
 
  TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;          // Single slope PWM operation         
  while (TCC0->SYNCBUSY.bit.WAVE);                 // Wait for synchronization
 
  TCC0->PER.reg = 4799;                            // Set the frequency of the PWM on TCC0 to 10kHz
  while (TCC0->SYNCBUSY.bit.PER);                  // Wait for synchronization

  TCC0->CC[0].reg = 2399;                          // TCC0 CC2 - 50% duty-cycle on D10
  while (TCC0->SYNCBUSY.bit.CC0);                  // Wait for synchronization
  TCC0->CC[1].reg = 2399;                          // TCC0 CC3 - 50% duty-cycle on D12
  while (TCC0->SYNCBUSY.bit.CC1);                  // Wait for synchronization
 
  // Divide the 48MHz signal by 1 giving 48MHz (20.83ns) TCC0 timer tick and enable the timer
  TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV1 |    // Divide GCLK4 by 1
                     TCC_CTRLA_ENABLE;             // Enable the TCC0 output
  while (TCC0->SYNCBUSY.bit.ENABLE);               // Wait for synchronization

  TC4->COUNT16.READREQ.reg = TC_READREQ_RCONT |            // Enable a continuous read request
                             TC_READREQ_ADDR(0x10);        // Offset of the 16 bit COUNT register
  while (TC4->COUNT16.STATUS.bit.SYNCBUSY);                // Wait for (read) synchronization
}

void loop()
{
  //TC4->COUNT16.CTRLB.reg = TC_CTRLBCLR_CMD_STOP;        // Stop the TC4 timer
  //while(TC4->COUNT16.STATUS.bit.SYNCBUSY);              // Wait for synchronization
  //TC4->COUNT16.COUNT.reg = 0;                           // Reset the TC4 timer
  //while(TC4->COUNT16.STATUS.bit.SYNCBUSY);              // Wait for synchronization
  //TC4->COUNT16.CTRLB.reg = TC_CTRLBCLR_CMD_RETRIGGER;   // Restart the TC4 timer
  //while(TC4->COUNT16.STATUS.bit.SYNCBUSY);              // Wait for synchronization
 
  //Set the frequency to 10kHz
  TCC0->PERB.reg = 4799;                           // Set the frequency to 10kHz
  while (TCC0->SYNCBUSY.bit.PERB);                 // Wait for synchronization
  TCC0->CCB[0].reg = 2399;                         // Set the duty cycle to 50% on channel 2
  while (TCC0->SYNCBUSY.bit.CCB0);                 // Wait for synchronization
  TCC0->CCB[1].reg = 2399;                         // Set the duty cycle to 50% on channel 3
  while (TCC0->SYNCBUSY.bit.CCB1);                 // Wait for synchronization
  delay(2000);                                     // Wait for 2 seconds
  //Set the frequency to 20kHz
  TCC0->PERB.reg = 2399;                           // Set the frequency to 10kHz
  while (TCC0->SYNCBUSY.bit.PERB);                 // Wait for synchronization
  TCC0->CCB[0].reg = 1199;                         // Set the duty cycle to 50% on channel 2
  while (TCC0->SYNCBUSY.bit.CCB0);                 // Wait for synchronization
  TCC0->CCB[1].reg = 1199;                         // Set the duty cycle to 50% on channel 3
  while (TCC0->SYNCBUSY.bit.CCB1);                 // Wait for synchronization
  delay(2000);                                     // Wait for 2 seconds
 
  //while(TC4->COUNT16.READREQ.bit.RREQ);            // Wait for read synchronization
  SerialUSB.println(TC4->COUNT16.COUNT.reg);          // Count the number of pulses
}

In this example, I've enabled continous read synchronization on TC4. However, if you need to stop and start the you'll need to manually perform a read synchronization each time you read the TC4's COUNT register, this is the commented out code. The TC4 COUNT register value is output on the SAMD21's native USB port console.

If you need more than a 16-bit timer for TC4, than it's possible to chain two TC timers together to create a 32-bit one, but I haven't included this in the code.

cbob

Oh yes - and again: a dazzling working example! great!

Thank you Martin!

And again: like on your example before - I tried to convert it to my working copy - now with pin10 as the PWM-Pin - and what can I say? I did not get it managed :( - I've changed the PMUX-registers to the correct values but the programm hangs in lines:

Code: [Select]

 TC4->COUNT16.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1 = 48MHz
                           TC_CTRLA_ENABLE;               // Enable TC4
 while (TC4->COUNT16.STATUS.bit.SYNCBUSY);  


... and I've got no clue what's wrong. Is there again another register to pay attention for?
But this is only for my information - I think with your examples I get my problem solved with pin2...

MartinL

#40
Jul 20, 2018, 12:58 pm Last Edit: Jul 20, 2018, 12:58 pm by MartinL
Hi Dan,

Regarding PWM output on digital pin D10, have you switched the perhipheral multiplexer back to peripheral F?

Code: [Select]
PORT->Group[g_APinDescription[10].ulPort].PMUX[g_APinDescription[10].ulPin >> 1].reg |= /*PORT_PMUX_PMUXO_F |*/ PORT_PMUX_PMUXE_F;
...and the TCC0 channel back to channel 2?

Code: [Select]
TCC0->CCB[2].reg = 2399;                         // Set the duty cycle to 50% on channel 2
while (TCC0->SYNCBUSY.bit.CCB2);                 // Wait for synchronization

cbob

Yes, thats what I did, maybe a typo or what ever - but okay - it doesn't matter... It is working now.

But may I ask another last question : what is the right way to stop the PWM and restart it again later? which flags/registers must be set or unset or cleared?

MartinL

Hi Dan,

The simple way is to set the CCBx registers to 0, effectively producing 0V on the output.

The other way is to force the TCC0 counter to start/stop:

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

and to start again:

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

cbob

Many many thanks Martin!

It is absolutely amazing that there are guys like you out there ;)

I wish you a happy weekend!

Regards
Dan

mwolter

Hello MartinL,

Wondering if you might be able to help? I read the previous posts between you and cbob and I believe he is trying to do something similar to my project. My goal is to use PWM from a SAMD21G18 based board to control a stepper driver. The stepper needs to be smoothly accelerated from 0 RPM to 1000 RPM (6667hz) and smoothly decelerated back down to 0 RPM. The duty cycle is ~50%.

Looking at the posts above it appeared the code I needed was there but it doesn't seem to be working. I can get the initial PWM at 1hz but changing it has proven difficult. I have removed the code from your examples for pin 12 since I only need to control pin 10. Not sure if this is an issue or not.

Code: [Select]


unsigned int stepperFrequencyDivisor = 46875; //sets the starting frequency equal to 1hz
double stepperHzToRPM = 6.67; //6.67hz runs the stepper at 1 RPM

void setup() {

// Output PWM on timer TCC0, digital pin D10

GCLK->GENDIV.reg = 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

GCLK->GENCTRL.reg = 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 2 PWM channels: timer TCC0 outputs CC2 and CC3 on D10 and D12
PORT->Group[g_APinDescription[10].ulPort].PINCFG[g_APinDescription[10].ulPin].bit.PMUXEN = 1;

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

// Feed GCLK4 to TCC0 and TCC1
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable GCLK4 to TCC0 and TCC1
GCLK_CLKCTRL_GEN_GCLK4 |                                 // Select GCLK4
GCLK_CLKCTRL_ID_TCC0_TCC1; // Fe                        ed GCLK4 to TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization 

TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Single slope PWM operation         
while (TCC0->SYNCBUSY.bit.WAVE); // Wait for synchronization

TCC0->PER.reg = round(stepperFrequencyDivisor); // Set the starting frequency of the PWM on TCC0, 46875 equals 1hz 
while (TCC0->SYNCBUSY.bit.PER); // Wait for synchronization

TCC0->CC[2].reg = round(stepperFrequencyDivisor / 2); // TCC0 CC2 - 50% duty-cycle on D10
while (TCC0->SYNCBUSY.bit.CC2); // Wait for synchronization

// Divide the 48MHz signal by 1024 giving 46,875Hz TCC0 timer tick and enable the timer
TCC0->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV1024 | // Divide GCLK4 by 1024
TCC_CTRLA_ENABLE;                                 // Enable the TCC0 output
while (TCC0->SYNCBUSY.bit.ENABLE); it for synchronization

//Stop TCC0 counter on MCU initialization (stepper initially stopped)
TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_STOP;        // Force the TCC0 timer to STOP
while (TCC0->SYNCBUSY.bit.CTRLB);                               // Wait for synchronization
}


The code above appears to work properly and outputs a 1hz PWM with a 50% duty cycle (when the timer is enabled). I then run the code below via a serial command to start the timer (PWM) and change the frequency. This code is ran in the loop and a value for stepperRPM passed in via serial. For instance, a value of 1 should create a 6.67hz PWM signal.

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

stepperFrequencyDivisor = round(46875 / (stepperHzToRPM * stepperRpm));

TCC0->PER.reg = stepperFrequencyDivisor; // Set the starting frequency of the PWM on TCC0 to 1Hz
while (TCC0->SYNCBUSY.bit.PER); // Wait for synchronization

TCC0->CC[2].reg = stepperFrequencyDivisor / 2; // TCC0 CC2 - 50% duty-cycle on D10
while (TCC0->SYNCBUSY.bit.CC2); // Wait for synchronization


I left out the code to increment the RPM, not sure it's necessary as the problem that I am experiencing is the frequency goes to zero and sometimes changes several minutes later . Is there something wrong with the code? Should the frequency change be performed differently or at a specific time?

Any help would be greatly appreciated.

P.S. Apologize for the formatting, tried to make it as readable as possible

Go Up