Arduino Zero Timed Interrupts & Stepper Motor Control

Hello all,

Is there anyone who can help me with the following questions, i have no idea what approach would be best so i'm thinking about a timed interrupt to control the motors but i can't seem to get it to work. As a test i used the following code:

void Initialize() {
PM->CPUSEL.reg = PM_CPUSEL_CPUDIV_DIV1;
  while (GCLK->STATUS.bit.SYNCBUSY) { };

  // Set The Global Clock: Improve Duty Cycle, Enable And Clock Selection
  GCLK->GENCTRL.reg |= GCLK_GENCTRL_IDC;
  GCLK->GENCTRL.reg |= GCLK_GENCTRL_GENEN;
  GCLK->GENCTRL.reg |= GCLK_GENCTRL_SRC_DFLL48M;
  GCLK->GENCTRL.reg |= GCLK_GENCTRL_ID(4);

  // Feed Clock To Timer, Select Clock 4 & Enable Clock
  GCLK->CLKCTRL.reg |= GCLK_CLKCTRL_ID_TCC2_TC3;
  GCLK->CLKCTRL.reg |= GCLK_CLKCTRL_GEN_GCLK4;
  GCLK->CLKCTRL.reg |= GCLK_CLKCTRL_CLKEN;
  while (GCLK->STATUS.bit.SYNCBUSY) { };

  // Configure The Timed Interrupt For MotorControl
  PM->APBCMASK.reg |= PM_APBCMASK_TCC2;
  TCC2->WAVE.reg |= TCC_WAVE_WAVEGEN_NFRQ;
  TCC2->PER.reg = 100;
  while (TCC2->SYNCBUSY.bit.PER) { };
  TCC2->CC[0].reg = 50;
  while (TCC2->SYNCBUSY.bit.CC0) { };
  TCC2->INTENSET.reg |= TCC_INTENSET_OVF;
  TCC2->CTRLA.reg |= TCC_CTRLA_PRESCALER_DIV1024;
  TCC2->CTRLA.reg |= TCC_CTRLA_PRESCSYNC_GCLK;
  TCC2->INTFLAG.reg |= TCC_INTFLAG_OVF;
  TCC2->CTRLA.reg |= TCC_CTRLA_ENABLE;
  while (TCC2->SYNCBUSY.bit.ENABLE) { };

  // Set The Timed Interrupt Priority To The Highest & Enable
  NVIC_SetPriority(TCC2_IRQn, 0);
  NVIC_EnableIRQ(TCC2_IRQn);
};

void TCC2_Handler() {
  _step();
  TCC2->INTFLAG.reg |= TCC_INTFLAG_OVF;
};

Where the function _step() works just fine as in the normal loop it steps the motor properly. Now i have been thinking about using PWM but then i need need to shift the PWM signal of the two wires. I have no idea how to do this and i would like to know how to do this. Then i thought of this solution but the timer doesn't seem to work so can someone tell me what i'm doing wrong here?

I made a class which will control the motor but is it possible to define and use the interrupt in that certain class so it is defined for both motors?

Another option is using output compare output pins. Can someone help me with this option as well?

I would like to know about all options and then also which option you think is best. I also want to accelerate the stepper motor and decelerate.

I would greatly appreciate your help.

Kind regards,

Bob

Hello everyone,

After some more testing i found out that my code stays stuck at the following 3 points:

while (TCC2->SYNCBUSY.bit.PER) { };
  while (TCC2->SYNCBUSY.bit.CC0) { };
  while (TCC2->SYNCBUSY.bit.ENABLE) { };

Do any of you have an idea on how to fix this? Still open for suggestions about other solutions though.

Kind regards,

Bob

Hi Bob,

To get a PWM output, you need to enable the pin multiplexer and switch to your peripheral of choice (in this case a TCC timer):

// Enable the port multiplexer for the digital pins D7
  PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;
 
  // Connect the TCC0 timer to the port output D7 - port pins are paired odd PMUXO and even PMUXE
  // F & E specify the timers: TCC0, TCC1 and TCC2
  PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg = PORT_PMUX_PMUXO_F; //| PORT_PMUX_PMUXE_F;

The following code gives an example of setting up 1kHz, single slope PWM signal on D7, together with an interrupt service routine (ISR). (It uses TCC0 rather than TCC2, but the principle is the same):

// Output 1kHz single slope PWM on timer TCC0
volatile unsigned long count;

void setup()
{
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) |          // Divide the 48MHz clock source by divisor 1: 48MHz/1=48MHz
                    GCLK_GENDIV_ID(4);            // Select Generic Clock (GCLK) 4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                     GCLK_GENCTRL_GENEN |         // Enable GCLK4
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                     GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Enable the port multiplexer for the digital pins D7
  PORT->Group[g_APinDescription[7].ulPort].PINCFG[g_APinDescription[7].ulPin].bit.PMUXEN = 1;
 
  // Connect the TCC0 timer to the port output D7 - port pins are paired odd PMUXO and even PMUXE
  // F & E specify the timers: TCC0, TCC1 and TCC2
  PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg = PORT_PMUX_PMUXO_F; //| PORT_PMUX_PMUXE_F;

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

  // Normal (single slope) PWM operation: timers countinuously count up to PER register value and then is reset to 0
  REG_TCC0_WAVE |= TCC_WAVE_WAVEGEN_NPWM;         // Setup single slope PWM on TCC0
  while (TCC0->SYNCBUSY.bit.WAVE);                // Wait for synchronization

  // Each timer counts up to a maximum or TOP value set by the PER register,
  // this determines the frequency of the PWM operation:
  REG_TCC0_PER = 45714;                           // Set the frequency of the PWM on TCC0 to 1kHz
  while(TCC0->SYNCBUSY.bit.PER);                  // Wait for synchronization
 
  // Set the PWM signal to output 75% duty cycle on D6, 25% on D7
  REG_TCC0_CCB3 = 22857;                          // TCC0 CCB3 - on output on D7 50% duty cycle
  while(TCC0->SYNCBUSY.bit.CCB3);                 // Wait for synchronization

  //NVIC_DisableIRQ(TCC0_IRQn);
  //NVIC_ClearPendingIRQ(TCC0_IRQn);
  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)

  REG_TCC0_INTFLAG |= TC_INTFLAG_OVF;             // Clear the overflow interrupt flag
  REG_TCC0_INTENSET = TC_INTENSET_OVF;            // Enable TCC0 overflow interrupt

  // Divide the 48MHz signal by 1 giving 48MHz (20.83ns) TCC0 timer tick and enable the outputs
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 |    // Divide GCLK4 by 1
                    TCC_CTRLA_ENABLE;             // Enable the TCC0 output
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization
}

void loop() {}

// Ineterrupt handler called every time the TCC0 timer overflows
void TCC0_Handler()                              // Interrupt Service Routine (ISR) for timer TCC0
{     
  // Check for overflow (OVF) interrupt
  if (TCC0->INTFLAG.bit.OVF && TCC0->INTENSET.bit.OVF)             
  {
    // Put your timer overflow (OVF) code here:     
    // ...
    count++;
    
    REG_TCC0_INTFLAG = TC_INTFLAG_OVF;         // Clear the OVF interrupt flag
  }
}