Using TCC and TC to trigger a regular interrupt SAMD51

Hi,

I am working on a project that requires an interrupt that is triggered regularly, every 2ms. I am using a SAMD51 board, the ItsyBitsyM4, which has plenty of TCs and TCCs. I have tried available libraries such as SAMD_TimerInterrupt and SAMD_InterruptTimer but since they both only have TC3 available for SAMD51 boards I have encountered a problem since TC3 is already used by project (or so I think from the testing I have done). I have connected to my ItsyBitsyM4 2 rotary encoder from Pololu which use the attachInterrupt function to keep track of the distance travelled by the robot. The rotary encoder sensor outputs are connected to pins 11, 4, 9 and 7, three of these pins also have access to the TC3 module through various channels eg 0,1,2.

The code below outputs counter values that keep on increasing and a DT value of 2000. However once I press the button and the motors begin to spin, DT decreases to 800 (to exactly 40% of the set interrupt time - I have tested this with other values such as 10000 and 4000). This I am guessing happens because the encoder interrupts are triggered which causes a change somewhere though I am not sure.

Motor_Drive.ino

#include <Arduino.h>
#include "config.hpp"
#include "sensors.hpp"
#include "motors.hpp"
#include "encoder.hpp"
#include "systick.hpp"
#include "movements.hpp"// Select only one to be true for SAMD21. Must must be placed at the beginning before #include "SAMDTimerInterrupt.h"


int buttonState = 0;
int startTime = 0;
uint32_t returnCode;


int switchOne = false;
int switchTwo = false;

void setup() {

  Serial1.begin(38400);

  Serial.begin(38400);
    
  //systickSetup();
  motorSetup();
  ISRSetup();
  //systickSetup();
  encoderSetup();
  //sensorSetup();

  delay(250);
  pinMode(switch1, INPUT);
  pinMode(switch2, INPUT);

  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  digitalWrite(LED1, LOW);
  digitalWrite(LED2, LOW);


}

void loop() {
  switchOne = digitalRead(switch1);
  switchTwo = digitalRead(switch2);

  if(switchOne == HIGH){
    digitalWrite(LED1, HIGH);
    delay(1000);
    resetEncoders();
    setRightMotorVolts(3.0);
    setLeftMotorVolts(3.0);
    delay(1500);
    digitalWrite(LED1, LOW);
    stopMotors();
    Serial1.println("\n1");
    
  } else if(switchTwo == HIGH){
    setLeftMotorVolts(3.0);
    digitalWrite(LED2, HIGH);
    delay(1500);
    stopMotors();
    digitalWrite(LED2, LOW);
    Serial1.print("\n2");
    
  }
  if (updated) {
    Serial.print("DT:");
    Serial.print(dT);
    Serial.print("\n");

    noInterrupts();
    updated = false;
    interrupts();
  }
  
}

Encoder ISR and setup

void encoderSetup(){
  CriticalSection crit_sec;
  pinMode(RENCA, INPUT);
  pinMode(RENCB, INPUT);
  attachInterrupt(digitalPinToInterrupt(RENCA), readEncoderRight, RISING);
  pinMode(LENCA, INPUT);
  pinMode(LENCB, INPUT);
  attachInterrupt(digitalPinToInterrupt(LENCA), readEncoderLeft, RISING);
}
void readEncoderLeft(){
  int outa = digitalRead(LENCA);
  int outb = digitalRead(LENCB);
  int leftEncoderState = (outa << 1) | outb;

  if (leftEncoderState == 1 || leftEncoderState ==2 ){
    encoderLeftCounter += ENCODER_LEFT_POLARITY;
  }  else if(leftEncoderState == 3 || leftEncoderState == 0 ){
    encoderLeftCounter -= ENCODER_LEFT_POLARITY;
  }
  Serial.print("\nLeft");
  Serial.print(encoderLeftCounter);
}

void readEncoderRight(){
  int outa = digitalRead(RENCA);
  int outb = digitalRead(RENCB);

  int rightEncoderState = (outa << 1) | outb;

  if(rightEncoderState == 1 || rightEncoderState == 2){
    encoderRightCounter += ENCODER_RIGHT_POLARITY;
  } else if(rightEncoderState == 3 || rightEncoderState == 0){
    encoderRightCounter -= ENCODER_RIGHT_POLARITY;
  }
  Serial.print("\nRight");
  Serial.print(encoderRightCounter);
}

The Interrupt using SAMD51_InterruptTimer

#include "sensors.hpp"
#include "samd51/include/samd51g19a.h"
#include "systick.hpp"
#include "encoder.hpp"
#include "motor_profile.hpp"
#include "motors.hpp"
#include <Arduino.h>

int counter = 0;
volatile bool toggle = true;
volatile bool updated = false;
volatile uint32_t dT = 0;

void ISRSetup(){
  /*SysTick->CTRL = 0;                    // Disable the SysTick Module
  SysTick->LOAD = 0x0003A97F;           // Set the Reload Register for 2mS interrupts 239999
  NVIC_SetPriority(SysTick_IRQn, 3);    // Set the interrupt priority to least urgency
  SysTick->VAL = 0;                     // Clear the Current Value register
  SysTick->CTRL = 0x00000007;          // Enable SysTick, Enable SysTick Exceptions, Use CPU Clock
  NVIC_EnableIRQ(SysTick_IRQn);*/    //Enable SysTick interrupts globally

  TC.startTimer(1000, myISR);
  
}



void myISR(){
  updateEncoders();
  uint32_t now = micros();
  static uint32_t prev_tick = now;
  dT = now - prev_tick;
  prev_tick = now;
  updated = true;
  counter++;
  Serial.print("counter: ");
  Serial.println(counter);
}

Back to the main topic, to bypass this I want to use another TC or TCC on pin D12, as it is unused in my project. I have read the datasheet for the SAMD51 and have written some code although it seems that the interrupt never triggers as the Handler is never called and there is no output in the Serial Monitor. The code below shows a different sketch that is used to test the interrupt:

#include <Arduino.h>

volatile uint32_t interruptCount = 0;

void TCC0_3_Handler() {
  Serial.println("HI");
  if (TCC0->INTFLAG.bit.OVF) {
    interruptCount++;
    TCC0->INTFLAG.bit.OVF = 1;
  }
}

void setupTCC0() {

  TCC0->CTRLA.bit.ENABLE = 0;
  //GCLK->PCHCTRL[TCC0_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK0_Val | GCLK_PCHCTRL_CHEN;

  MCLK->APBBMASK.reg |= MCLK_APBBMASK_TCC0;
  GCLK->PCHCTRL[TCC0_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK4 | GCLK_PCHCTRL_CHEN;
  GCLK->PCHCTRL[TCC0_GCLK_ID].bit.GEN = 0x4;

  TCC0->CTRLA.reg = TC_CTRLA_MODE_COUNT16_Val | TC_CTRLA_PRESCALER_DIV2;
  TCC0->PER.reg = 96000; // F_CPU is the CPU frequency in Hz (160 MHz)

  TCC0->INTENSET.bit.OVF = 1;
  NVIC_EnableIRQ(TCC0_3_IRQn);

  TCC0->CTRLA.bit.ENABLE = 1;
}

void setup() {
  Serial.begin(9600);
  setupTCC0();
  __enable_irq();
}

void loop() {
  if (interruptCount >= 1000) {
    interruptCount = 0;
    Serial.println(interruptCount);
    // Perform your desired actions here
  }
}

Sorry for the long post but I don't know how to explain my problem. In case this amount of code isn't comprehensive enough I will look into creating a GitHub repository.

I have solved my problem but I will leave this post up in case anyone needs a solution.

Below is the code for configuring TCC0 to trigger an interrupt approximately every 4ms (4368uS in reality). This function can be called in setup:

void TCC0Setup(){       
  while (!Serial);                                              // Wait for the console to be opened
  GCLK->PCHCTRL[TCC0_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |         // Enable perhipheral channel for TCC0
                                   GCLK_PCHCTRL_GEN_GCLK0;      // Connect generic clock 0 at 120MHz

  TCC0->CTRLA.bit.PRESCALER =  TCC_CTRLA_PRESCALER_DIV8_Val;    // Change the div to 2^n n<10
  TCC0->PER.reg = 0xFFFF;                                       // Period register set to trigger an interrupt after 1ms at prescaler div2, 2ms at prescaler div4 etc.
  TCC0->INTENSET.bit.OVF =  0X01;                               // Enable interrupt on overflow
  TCC0->CTRLA.bit.ENABLE = 1;                                   // Enable timer TC0
  while (TCC0->SYNCBUSY.bit.ENABLE);                            // Wait for synchronization
  NVIC_EnableIRQ(TCC0_0_IRQn);                                  // Enable the Handler for TCC0 Channel 0
  NVIC_SetPriority(TCC0_0_IRQn,15);                             // Set the priority to the lowest for my application (0 is the highest)
}

Also below is the code for the Handler:

void TCC0_0_Handler(){
  if (TCC0->INTFLAG.bit.OVF == 1) {  //if overflow happens 
    TCC0->INTFLAG.bit.OVF =  0X01;  //resets the overflow bit so that the counter starts counting again
    // put what you want to do when the interrupt is triggered here
 }
}

I only got it to work with TCC0_0_Handler even though there were many more channels with handlers available in the source files. Also, I recommend printing only with Serial via USB and not Bluetooth especially if you need the timer to be very accurate as I got my board to crash sometimes.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.