Input Capture hardware SAMD21

Hello!
I'm trying to use the hardware timers on my SAMD21 (Arduino Zero) to measure the phase shift of my motor, but I don't understand how to set it up properly.

I tried coding it, but nothing seems to work. Could someone help me convert my code to use hardware timers (e.g., TC or TCC modules) instead of micros() in interrupts?

My goal is to measure the time between a phase signal and a Hall signal with better accuracy and less noise.

Any example or explanation would be really appreciated!

// Déclaration des broches pour les signaux (fronts montants)
const int Phase1 = 8;          // Signal de phase
const int Hall_PulsePin1 = 0;   // Signal Hall
const int Phase2 = 11;          // Signal de phase
const int Hall_PulsePin2 = 2;   // Signal Hall
const int Phase3 = 13;           // Signal de phase
const int Hall_PulsePin3 = 6;   // Signal Hall

// Déclaration des broches pour les signaux (fronts descendants)
const int phasePinsFall[] = {9, 12, 15};
const int hallPinsFall[] = {1, 3, 7};


//création d'un tableau flag pour synchroniser l'arriver des signaux
volatile bool flag_PhaseReady[3] = {false};
volatile bool flag_PhaseFallReady[3] = {false};

// Variables pour la période et le déphasage (fronts montants)
volatile unsigned long startTime_Phase[3] = {0};
volatile unsigned long period_Phase[3] = {0};
volatile unsigned long startTime_Hall[3] = {0};
volatile unsigned long phase_shift[3] = {0};

// Variables pour la période et le déphasage (fronts descendants)
volatile unsigned long startTime_Phase_fall[3] = {0};
volatile unsigned long period_Phase_fall[3] = {0};
volatile unsigned long startTime_Hall_fall[3] = {0};
volatile unsigned long phase_shift_fall[3] = {0};

// Anti-rebond
const unsigned long debounceDelay = 20;//µs
volatile unsigned long lastInterruptTime[6] = {0}; // 0-2: rising, 3-5: falling

void setup() {
  // Configuration des broches en entrée (fronts montants)
  pinMode(Phase1, INPUT);
  pinMode(Hall_PulsePin1, INPUT);
  pinMode(Phase2, INPUT);
  pinMode(Hall_PulsePin2, INPUT);
  pinMode(Phase3, INPUT);
  pinMode(Hall_PulsePin3, INPUT);

  // Configuration des broches en entrée (fronts descendants)
  for (int i = 0; i < 3; i++) {
    pinMode(phasePinsFall[i], INPUT);
    pinMode(hallPinsFall[i], INPUT);
  }

  // Démarrage de la communication série
  Serial.begin(115200);

  // Attachement des interruptions (fronts montants)
  attachInterrupt(digitalPinToInterrupt(Phase1), []{countPulse_Phase(0, false);}, RISING);
  attachInterrupt(digitalPinToInterrupt(Hall_PulsePin1), []{countPulse_Hall(0, false);}, RISING);
  attachInterrupt(digitalPinToInterrupt(Phase2), []{countPulse_Phase(1, false);}, RISING);
  attachInterrupt(digitalPinToInterrupt(Hall_PulsePin2), []{countPulse_Hall(1, false);}, RISING);
  attachInterrupt(digitalPinToInterrupt(Phase3), []{countPulse_Phase(2, false);}, RISING);
  attachInterrupt(digitalPinToInterrupt(Hall_PulsePin3), []{countPulse_Hall(2, false);}, RISING);

  // Attachement des interruptions (fronts descendants)
  attachInterrupt(digitalPinToInterrupt(phasePinsFall[0]), []{countPulse_Phase(0, true);}, FALLING);
  attachInterrupt(digitalPinToInterrupt(hallPinsFall[0]), []{countPulse_Hall(0, true);}, FALLING);
  attachInterrupt(digitalPinToInterrupt(phasePinsFall[1]), []{countPulse_Phase(1, true);}, FALLING);
  attachInterrupt(digitalPinToInterrupt(hallPinsFall[1]), []{countPulse_Hall(1, true);}, FALLING);
  attachInterrupt(digitalPinToInterrupt(phasePinsFall[2]), []{countPulse_Phase(2, true);}, FALLING);
  attachInterrupt(digitalPinToInterrupt(hallPinsFall[2]), []{countPulse_Hall(2, true);}, FALLING);
}

void loop() {
  // Lecture sécurisée des valeurs
  noInterrupts();
  
  // Fronts montants
  unsigned long p[3] = {period_Phase[0], period_Phase[1], period_Phase[2]};
  unsigned long shift_us[3] = {phase_shift[0], phase_shift[1], phase_shift[2]};
  
  // Fronts descendants
  unsigned long p_fall[3] = {period_Phase_fall[0], period_Phase_fall[1], period_Phase_fall[2]};
  unsigned long shift_us_fall[3] = {phase_shift_fall[0], phase_shift_fall[1], phase_shift_fall[2]};
  
  interrupts();

  // Affichage des résultats
  displayResults("Montant", p, shift_us);
  displayResults("Descendant", p_fall, shift_us_fall);
  
  delay(100); // Délai pour éviter de saturer la sortie série
}

void displayResults(const char* type, unsigned long periods[3], unsigned long shifts[3]) {
  Serial.print("=== Front ");
  Serial.println(type);
  
  for (int i = 0; i < 3; i++) {
    if (periods[i] > 0) {
      float shift_deg = (float)shifts[i] / periods[i] * 360.0;
      shift_deg = shift_deg >= 180 ? shift_deg - 360 : shift_deg;

      Serial.print("Phase");
      Serial.print(i+1);
      Serial.print(": Période=");
      Serial.print(periods[i]);
      Serial.print("us, Déphasage=");
      Serial.print(shifts[i]);
      Serial.print("us, Angle=");
      Serial.print(shift_deg, 2);
      Serial.println("°");
    }
  }
}

// Fonction unique pour gérer les interruptions de phase
void countPulse_Phase(int phase, bool isFalling) {
  unsigned long now = micros();
  int index = phase + (isFalling ? 3 : 0);
  
  // Anti-rebond
  if (now - lastInterruptTime[index] < debounceDelay) return;
  lastInterruptTime[index] = now;

  if (isFalling) {
    if (startTime_Phase_fall[phase] == 0) {
      startTime_Phase_fall[phase] = now;
      flag_PhaseFallReady[phase] = true;
      startTime_Hall_fall[phase] = 0;
    } else {
      period_Phase_fall[phase] = now - startTime_Phase_fall[phase];
      startTime_Phase_fall[phase] = now;
      flag_PhaseReady[phase] = true;
    }
  } else {
    if (startTime_Phase[phase] == 0) {
      startTime_Phase[phase] = now;
      startTime_Hall[phase] = 0;
    } else {
      period_Phase[phase] = now - startTime_Phase[phase];
      startTime_Phase[phase] = now;
    }
  }
}

// Fonction unique pour gérer les interruptions Hall
void countPulse_Hall(int phase, bool isFalling) {
  unsigned long now = micros();
  int index = phase + (isFalling ? 3 : 0);
  
  // Anti-rebond
  if (now - lastInterruptTime[index] < debounceDelay) return;
  lastInterruptTime[index] = now;

  if (isFalling) {
    if (startTime_Phase_fall[phase] != 0) {
      startTime_Hall_fall[phase] = now;
      phase_shift_fall[phase] = now - startTime_Phase_fall[phase];
      flag_PhaseFallReady[phase] = false;
    }
  } else {
    if (startTime_Phase[phase] != 0) {
      startTime_Hall[phase] = now;
      phase_shift[phase] = now - startTime_Phase[phase];
      flag_PhaseReady[phase] = false;
    }
  }
}

Try doing some Web and Forum searches for "SAMD21 Input Capture". Here are a couple hits:
https://forum.arduino.cc/t/arduino-zero-tcc-capture/382329
https://forum.microchip.com/s/topic/a5C3l000000UjKuEAK/t187383

Hi everyone,

I found this thread about using PPW (Period and Pulse Width capture) mode on the SAMD21, and I’d like to do something similar on my board.

I’ve tried to adapt the code to my setup, but unfortunately, it doesn’t work — nothing happens, and I don’t know why. If someone could help me modify my code to properly on top of the topic to use a SAMD21 hardware timer in PPW mode, I’d really appreciate it.

Here’s the link to the original topic I’m trying to follow:

And here is my current code i have tried to do it:

#include <Arduino.h>

// Variables partagées avec l’ISR
volatile bool     periodComplete = false;  // drapeau fin de cycle
volatile uint32_t isrPeriod;               // période capturée
volatile uint32_t isrPulsewidth;           // largeur capturée

// ---------------------------------------------------------------------
// setup() : configuration globale
// ---------------------------------------------------------------------
void setup() {
  SerialUSB.begin(115200);
  while (!SerialUSB);

  // 1) Activer EVSYS (Event System) pour router les événements EIC→TCC0 :contentReference[oaicite:0]{index=0}
  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;

  // 2) Configurer FDPLL pour 96 MHz à partir de l’horloge 32 kHz :contentReference[oaicite:1]{index=1}
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK1 | GCLK_CLKCTRL_ID_FDPLL;
  SYSCTRL->DPLLCTRLB.reg = SYSCTRL_DPLLCTRLB_REFCLK_GCLK;
  SYSCTRL->DPLLRATIO.reg = SYSCTRL_DPLLRATIO_LDRFRAC(11) | SYSCTRL_DPLLRATIO_LDR(2928);
  SYSCTRL->DPLLCTRLA.reg = SYSCTRL_DPLLCTRLA_ENABLE;
  while (!SYSCTRL->DPLLSTATUS.bit.LOCK);

  // 3) GCLK4 = FDPLL96M /1 → 96 MHz, used for TCC0 :contentReference[oaicite:2]{index=2}
  GCLK->GENDIV.reg  = GCLK_GENDIV_ID(4) | GCLK_GENDIV_DIV(1);
  GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(4) | GCLK_GENCTRL_SRC_FDPLL
                    | GCLK_GENCTRL_IDC | GCLK_GENCTRL_GENEN;
  while (GCLK->STATUS.bit.SYNCBUSY);

  // 4) Router GCLK4 vers TCC0 :contentReference[oaicite:3]{index=3}
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK4
                    | GCLK_CLKCTRL_ID_TCC0_TCC1;
  while (GCLK->STATUS.bit.SYNCBUSY);

  // 5) Configurer TCC0 en PPW (CC0=période, CC1=largeur)
  //    - prescaler ÷1 → 96 MHz
  //    - capture hardware sur CC0 et CC1
  PM->APBCMASK.reg |= PM_APBCMASK_TCC0;      // horloge APBC pour TCC0 :contentReference[oaicite:4]{index=4}
  TCC0->CTRLA.reg &= ~TCC_CTRLA_ENABLE;      // désactiver avant config :contentReference[oaicite:5]{index=5}
  while (TCC0->SYNCBUSY.bit.ENABLE);
  TCC0->CTRLA.reg  = TCC_CTRLA_PRESCALER_DIV1
                   | TCC_CTRLA_CPTEN0    // enable capture CC0 :contentReference[oaicite:6]{index=6}
                   | TCC_CTRLA_CPTEN1;   // enable capture CC1 :contentReference[oaicite:7]{index=7}
  while (TCC0->SYNCBUSY.bit.ENABLE);

  // EVCTRL: activer MCEI0 et MCEI1 + mode PPW (Period into CC0, Pulse into CC1) :contentReference[oaicite:8]{index=8}
  TCC0->EVCTRL.reg = TCC_EVCTRL_MCEI0 | TCC_EVCTRL_MCEI1
                   | TCC_EVCTRL_EVACT1_PPW;

  // Interruptions MC0/MC1 pour lire en ISR :contentReference[oaicite:9]{index=9}
  TCC0->INTENSET.reg = TCC_INTENSET_MC0 | TCC_INTENSET_MC1;

  // 6) Configurer les EIC + EVSYS pour D6 (cellule Hall) et D13 (phase)
  //    D6  = PA20 → EXTINT4 :contentReference[oaicite:10]{index=10}
  //    D13 = PA17 → EXTINT1 :contentReference[oaicite:11]{index=11}

  // -- PIN D6 (PA20) en EIC EXTINT4
  PORT->Group[PORTA].PINCFG[20].bit.PMUXEN = 1;
  PORT->Group[PORTA].PMUX[20>>1].bit.PMUXO = PORT_PMUX_PMUXO_A;
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE4_HIGH;   // détecte niveau HIGH :contentReference[oaicite:12]{index=12}
  EIC->EVCTRL.reg  |= EIC_EVCTRL_EXTINTEO4;       // événement sur extint4 :contentReference[oaicite:13]{index=13}

  // -- PIN D13 (PA17) en EIC EXTINT1
  PORT->Group[PORTA].PINCFG[17].bit.PMUXEN = 1;
  PORT->Group[PORTA].PMUX[17>>1].bit.PMUXO = PORT_PMUX_PMUXO_A;
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE1_HIGH;   // détecte niveau HIGH :contentReference[oaicite:14]{index=14}
  EIC->EVCTRL.reg  |= EIC_EVCTRL_EXTINTEO1;       // événement sur extint1 :contentReference[oaicite:15]{index=15}

  EIC->CTRL.reg |= EIC_CTRL_ENABLE;
  while (EIC->STATUS.bit.SYNCBUSY);

  // 7) Configurer EVSYS pour deux canaux
  //    Ch0: EIC EXTINT4 → TCC0_EV_0 (CC0) :contentReference[oaicite:16]{index=16}
  EVSYS->CHANNEL.reg = EVSYS_CHANNEL_CHANNEL(0)
                     | EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_4)
                     | EVSYS_CHANNEL_PATH_ASYNCHRONOUS;
  EVSYS->USER.reg    = EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_0)
                     | EVSYS_USER_CHANNEL(0);    // :contentReference[oaicite:17]{index=17}

  //    Ch1: EIC EXTINT1 → TCC0_EV_1 (CC1) :contentReference[oaicite:18]{index=18}
  EVSYS->CHANNEL.reg = EVSYS_CHANNEL_CHANNEL(1)
                     | EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_1)
                     | EVSYS_CHANNEL_PATH_ASYNCHRONOUS;
  EVSYS->USER.reg    = EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_1)
                     | EVSYS_USER_CHANNEL(1);    // :contentReference[oaicite:19]{index=19}

  // 8) Activer TCC0
  TCC0->CTRLA.reg |= TCC_CTRLA_ENABLE;
  while (TCC0->SYNCBUSY.bit.ENABLE);

  // 9) NVIC pour TCC0 :contentReference[oaicite:20]{index=20}
  NVIC_SetPriority(TCC0_IRQn, 0);
  NVIC_EnableIRQ(TCC0_IRQn);
}

// ---------------------------------------------------------------------
// loop() : lecture CPU lorsque période complète
// ---------------------------------------------------------------------
void loop() {
  if (periodComplete) {
    noInterrupts();
      uint32_t p = isrPeriod;
      uint32_t w = isrPulsewidth;
    interrupts();
    SerialUSB.print("PW="); SerialUSB.print(w);
    SerialUSB.print("  P="); SerialUSB.println(p);
    periodComplete = false;
  }
}

// ---------------------------------------------------------------------
// TCC0_Handler() : ISR de capture PPW
// ---------------------------------------------------------------------
extern "C" void TCC0_Handler() {
  // MC0 → nouvelle période
  if (TCC0->INTFLAG.bit.MC0) {
    while (TCC0->SYNCBUSY.bit.CC0);
    isrPeriod = TCC0->CC[0].reg;
    periodComplete = true;
    TCC0->INTFLAG.reg = TCC_INTFLAG_MC0;
  }
  // MC1 → nouvelle largeur
  if (TCC0->INTFLAG.bit.MC1) {
    while (TCC0->SYNCBUSY.bit.CC1);
    isrPulsewidth = TCC0->CC[1].reg;
    TCC0->INTFLAG.reg = TCC_INTFLAG_MC1;
  }
}

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