Arduino Giga R1 Crashes When Measuring 4 Frequencies Simultaneously Using TIM2 Input Capture – Need Help

Hi everyone,

I'm working on a project using the Arduino Giga R1 to measure the frequency of 4 unknown signals simultaneously. Each signal comes from a separate circuit card assembly (CCA), and all of them are expected to be in the low-frequency range (~32 Hz). I also have a 10 MHz OCXO connected to the TIM2_ETR (pin A7), which I use as an accurate reference clock.

My Goal:
Measure the frequency of 4 signals simultaneously by counting the OCXO clock cycles between a fixed number (e.g., 32) of rising edges on each input.

Current Setup

  • CCA 1 → TIM2_CH4 (D2 / PA3)
  • CCA 2 → TIM2_CH1 (D85 / PA5/DAC1)
  • CCA 3 → TIM2_CH3 (D3 / PA2)
  • CCA 4 → TIM2_CH4 (D66 / PA1)
  • OCXO 10 MHz → TIM2_ETR (A7 / PA0)

Timer (TIM2) is set up in input capture mode, and I am using interrupts to detect rising edges and record the TIM2->CNT values for frequency calculation.


The Problem:

The program works perfectly when only 3 signals are connected and active. But when I connect all 4 inputs (Specially D85 pin) and all signals are active (i.e., pulses are coming simultaneously), the board either:

  • Crashes / Freezes
  • Restarts unexpectedly
  • Skips measurements or shows corrupted values

It seems like handling multiple interrupts or concurrent timer usage is causing instability or overflow issues. I’ve verified that my ISR is minimal and just stores captured values and sets flags.


My Questions:

  1. Is there a limitation on the number of hardware timers or capture channels active simultaneously on Giga R1?
  2. Do the timers interfere with each other when using a shared clock source like OCXO via TIM2_ETR?
  3. Would using DMA or polling instead of interrupts help stabilize it?
  4. Should I try using an external frequency counter IC like SN74LV8154 per channel instead?
  5. Is there a better recommended method for synchronized, multi-signal frequency measurement using Giga R1?

Additional Info:

  • I use a timeout to check for "no signal detected" on each channel.
  • Timer prescalers are not used; clock is external (10 MHz).
  • Target measurement window: 32 pulses per signal.
  • Compiler: Arduino IDE 2.x
  • Board: Arduino Giga R1
  • Libraries: STM32 HAL-based low-level register access (no external library)

Any suggestions, corrections, or experience sharing would be very helpful! Thank you in advance for your support.

Best regards,
Arya Patel

#define OCXO_FREQUENCY 10000000UL  // 10 MHz OCXO reference
#define OCXO_REFERENCE_PIN A7      // TIM2_ETR (PA0)
#define REFERENCE_FREQUENCY 32

// Channel Pins
#define CHANNEL_D2_PIN D2        // TIM2_CH4 (PA3)
#define CHANNEL_D3_PIN D3        // TIM2_CH3 (PA2)
#define CHANNEL_D66_PIN D66      // TIM2_CH2 (PA1)
#define CHANNEL_D85_PIN D85      // TIM2_CH1 (PA15)

// Global variables
volatile uint32_t prevCaptureD2 = 0, prevCaptureD3 = 0, prevCaptureD66 = 0, prevCaptureD85 = 0;
volatile uint32_t lastCaptureTimeD2 = 0, lastCaptureTimeD3 = 0, lastCaptureTimeD66 = 0, lastCaptureTimeD85 = 0;
volatile float measuredFrequencyD2 = 0.0, measuredFrequencyD3 = 0.0, measuredFrequencyD66 = 0.0, measuredFrequencyD85 = 0.0;
volatile uint32_t lastMeasurementTime = 0;

void setup() {
    Serial.begin(115200);
    Serial.println("Setup Started...");
    
    RCC->APB1LENR |= RCC_APB1LENR_TIM2EN;

    // Configure PA0 (A7 - TIM2_ETR)
    GPIOA->MODER &= ~(0b11 << (0 * 2));
    GPIOA->MODER |=  (0b10 << (0 * 2));
    GPIOA->AFR[0]  |= (0b0001 << (0 * 4));

    // PA3 (D2 - TIM2_CH4)
    GPIOA->MODER &= ~(0b11 << (3 * 2));
    GPIOA->MODER |=  (0b10 << (3 * 2));
    GPIOA->AFR[0]  |= (0b0001 << (3 * 4));
    GPIOA->PUPDR  &= ~(0b11 << (3 * 2));
    GPIOA->PUPDR  |=  (0b10 << (3 * 2));

    // PA2 (D3 - TIM2_CH3)
    GPIOA->MODER &= ~(0b11 << (2 * 2));
    GPIOA->MODER |=  (0b10 << (2 * 2));
    GPIOA->AFR[0]  |= (0b0001 << (2 * 4));
    GPIOA->PUPDR  &= ~(0b11 << (2 * 2));
    GPIOA->PUPDR  |=  (0b10 << (2 * 2));

    // PA1 (D66 - TIM2_CH2)
    GPIOA->MODER &= ~(0b11 << (1 * 2));
    GPIOA->MODER |=  (0b10 << (1 * 2));
    GPIOA->AFR[0]  |= (0b0001 << (1 * 4));
    GPIOA->PUPDR  &= ~(0b11 << (1 * 2));
    GPIOA->PUPDR  |=  (0b10 << (1 * 2));

    // PA15 (D85 - TIM2_CH1)
    GPIOA->MODER &= ~(0b11 << (15 * 2));
    GPIOA->MODER |=  (0b10 << (15 * 2));
    GPIOA->AFR[1]  |= (0b0001 << ((15 - 8) * 4));
    GPIOA->PUPDR  &= ~(0b11 << (15 * 2));
    GPIOA->PUPDR  |=  (0b10 << (15 * 2));

    // TIM2 External Clock Mode 1 (from OCXO)
    TIM2->SMCR &= ~(TIM_SMCR_SMS);
    TIM2->SMCR |= (0b111 << TIM_SMCR_SMS_Pos); 
    TIM2->SMCR &= ~(TIM_SMCR_TS);
    TIM2->SMCR |= (0b101 << TIM_SMCR_TS_Pos); 

    TIM2->PSC = 0;

    // Input Capture Configurations
    TIM2->CCMR2 &= ~(TIM_CCMR2_CC4S | TIM_CCMR2_CC3S);
    TIM2->CCMR2 |= ((0b01 << TIM_CCMR2_CC4S_Pos) | (0b01 << TIM_CCMR2_CC3S_Pos));
    TIM2->CCER  &= ~(TIM_CCER_CC4P | TIM_CCER_CC3P);
    TIM2->CCER  |= (TIM_CCER_CC4E | TIM_CCER_CC3E);

    TIM2->CCMR1 &= ~(TIM_CCMR1_CC2S | TIM_CCMR1_CC1S);
    TIM2->CCMR1 |= ((0b01 << TIM_CCMR1_CC2S_Pos) | (0b01 << TIM_CCMR1_CC1S_Pos));
    TIM2->CCER  &= ~(TIM_CCER_CC2P | TIM_CCER_CC1P);
    TIM2->CCER  |= (TIM_CCER_CC2E | TIM_CCER_CC1E);

    TIM2->DIER |= (TIM_DIER_CC4IE | TIM_DIER_CC3IE | TIM_DIER_CC2IE | TIM_DIER_CC1IE);
    NVIC_EnableIRQ(TIM2_IRQn);

    TIM2->CR1 |= TIM_CR1_CEN;

    Serial.println("Setup Completed.");
}

extern "C" void TIM2_IRQHandler(void) {
    if (TIM2->SR & TIM_SR_CC4IF) {
        uint32_t captureValue = TIM2->CCR4;
        if (prevCaptureD2 != 0) {
            uint32_t timeDiff = (captureValue >= prevCaptureD2) ? (captureValue - prevCaptureD2) : (0xFFFFFFFF - prevCaptureD2 + captureValue + 1);
            measuredFrequencyD2 = (float)OCXO_FREQUENCY / timeDiff;
        }
        prevCaptureD2 = captureValue;
        lastCaptureTimeD2 = millis();
        TIM2->SR &= ~TIM_SR_CC4IF;
    }

    if (TIM2->SR & TIM_SR_CC3IF) {
        uint32_t captureValue = TIM2->CCR3;
        if (prevCaptureD3 != 0) {
            uint32_t timeDiff = (captureValue >= prevCaptureD3) ? (captureValue - prevCaptureD3) : (0xFFFFFFFF - prevCaptureD3 + captureValue + 1);
            measuredFrequencyD3 = (float)OCXO_FREQUENCY / timeDiff;
        }
        prevCaptureD3 = captureValue;
        lastCaptureTimeD3 = millis();
        TIM2->SR &= ~TIM_SR_CC3IF;
    }

    if (TIM2->SR & TIM_SR_CC2IF) {
        uint32_t captureValue = TIM2->CCR2;
        if (prevCaptureD66 != 0) {
            uint32_t timeDiff = (captureValue >= prevCaptureD66) ? (captureValue - prevCaptureD66) : (0xFFFFFFFF - prevCaptureD66 + captureValue + 1);
            measuredFrequencyD66 = (float)OCXO_FREQUENCY / timeDiff;
        }
        prevCaptureD66 = captureValue;
        lastCaptureTimeD66 = millis();
        TIM2->SR &= ~TIM_SR_CC2IF;
    }

    if (TIM2->SR & TIM_SR_CC1IF) {
        uint32_t captureValue = TIM2->CCR1;
        if (prevCaptureD85 != 0) {
            uint32_t timeDiff = (captureValue >= prevCaptureD85) ? (captureValue - prevCaptureD85) : (0xFFFFFFFF - prevCaptureD85 + captureValue + 1);
            measuredFrequencyD85 = (float)OCXO_FREQUENCY / timeDiff;
        }
        prevCaptureD85 = captureValue;
        lastCaptureTimeD85 = millis();
        TIM2->SR &= ~TIM_SR_CC1IF;
    }
}

void loop() {
    if (millis() - lastMeasurementTime >= MEASUREMENT_INTERVAL) {
        lastMeasurementTime = millis();

        if (millis() - lastCaptureTimeD2 > MEASUREMENT_INTERVAL) measuredFrequencyD2 = 0.0;
        if (millis() - lastCaptureTimeD3 > MEASUREMENT_INTERVAL) measuredFrequencyD3 = 0.0;
        if (millis() - lastCaptureTimeD66 > MEASUREMENT_INTERVAL) measuredFrequencyD66 = 0.0;
        if (millis() - lastCaptureTimeD85 > MEASUREMENT_INTERVAL) measuredFrequencyD85 = 0.0;

        auto printChannel = [](const char* name, float freq) {
            if (freq == 0.0) {
                Serial.print("⚠️ NO SIGNAL DETECTED ON CHANNEL ");
                Serial.print(name);
                Serial.println("! ⚠️");
            } else {
                float ppm = ((freq - REFERENCE_FREQUENCY) / REFERENCE_FREQUENCY) * 1e6;
                Serial.print(name);
                Serial.print(" - Measured Frequency: ");
                Serial.print(freq, 6);
                Serial.print(" Hz | PPM Error: ");
                Serial.print(ppm, 3);
                Serial.println(" ppm");
            }
        };

        printChannel("D2", measuredFrequencyD2);
        printChannel("D3", measuredFrequencyD3);
        printChannel("D66", measuredFrequencyD66);
        printChannel("D85", measuredFrequencyD85);
    }
}

Hi @aryapatel-23 If you use CH5 for D85 instead of CH1, does it still fail?

CH5 is not available in Arduino Giga R1. Only CH1 to CH4. D85 only mapped for CH1, we cant change that.