Disabling an External Interrupts using a Timer Counter

I’m trying to create a program that triggers an external interrupt from a level change on a pin and then sets a compare value for a basic timer to disable that external interrupt for the next 800ms. I’m using a Sodaq ExpLoRer.

I can successfully set the device to wake up on a level change of EXTINT0 and activate an EIC_Handler() routine. I also have the timer successfully waking up on a TC4_Handler() interrupt routine when it reaches its compare value stored in the CC0 register.

Heres what Ive done:
When the EXTINT0 is triggered, it disables the WAKEUP on EXTINT0 to ensure it can’t wakeup from another pin level change. Then, I attempt to read the current COUNT value of the timer and add 200 clock cycles to it (which is 800ms @ 250Hz) and set the CC0 to this new value. Finally I re-enable the Compare match on the TC4 counter.

800ms later when the compare match is triggered on the timer, I disable Timer Compare interrupts (MC0) and re-enable the EXTINT0 interrupts.

Here is the issue:
The first few times it doesn’t work. The CC0 interrupt occurs immediately after the EXTINT0 interrupt. After about 5 or 6 external interrupt triggers in quick succession, it starts to kick in and work properly, disabling the EXTINT0 for 800ms like its supposed to. If I stop triggering the pin consistently then the problem comes back, triggering the timer MC0 interrupt immediately after the EXTINT0 interrupt again. I cant figure out why this is only working after being triggered a few times. The offending code is below. Please let me know if you need to see the setup code.

/*-----------------------
   MAIN - LOOP FOREVER
-------------------------*/
void loop()
{

   //This runs after an External Interrupt
   if (sensorTriggered) 
      doSensorTriggered();

   //This runs after the 800ms timer compare
   if(timerTriggered)
      doTimerTriggered();

   //System Sleep
   __WFI();

}


/*-------------------------
   SUB ROUTINES/FUNCTIONS
---------------------------*/
//This runs after an External Interrupt
void doSensorTriggered()
{
   //Get current count and Print to terminal
   uint8_t currentCount = TC4->COUNT8.COUNT.reg;
   debug("Before: "); debugl(currentCount); flush;

   //Set CC0 to currentCount and add 800ms (wrap around at 255 cycles)
   TC4->COUNT8.CC[0].reg = currentCount + sensorDelay;
      while(TC4->COUNT8.STATUS.bit.SYNCBUSY & TC_STATUS_SYNCBUSY);

   //Re-enable Counter TC4
   TC4->COUNT8.INTENSET.reg |= TC_INTENSET_MC0;
   sensorTriggered = 0;

}

//This runs after the 800ms timer compare
void doTimerTriggered()
{
   //Get current count and Print to terminal
   uint8_t currentCount = TC4->COUNT8.COUNT.reg;
   debug("After: "); debugl(currentCount); flush;

   //Re-enable EXTINT0
   EIC->WAKEUP.reg |= EIC_WAKEUP_WAKEUPEN0;
   timerTriggered = 0;
}




/**-------------------------------------
   INTERRUPT ROUTINES - Keep them short!
---------------------------------------- */

//External Interrupts
void EIC_Handler()
{
   //If EXTINT0 is Triggered
   if (EIC->INTFLAG.bit.EXTINT0 && EIC->INTENSET.bit.EXTINT0){
      EIC->WAKEUP.reg &= ~EIC_WAKEUP_WAKEUPEN0;    //Disable EXTINT0 Wakeup
      sensorTriggered = 1;
      EIC->INTFLAG.reg |= EIC_INTFLAG_EXTINT0;     //clear the EXTINT0 flag
   }
}

//Counter Interrupts
void TC4_Handler ()
{
   //If Timer Compare CC0 is a Match
   if (TC4->COUNT8.INTFLAG.bit.MC0 && TC4->COUNT8.INTENSET.bit.MC0) {
      TC4->COUNT8.INTENCLR.reg |= TC_INTFLAG_MC0;  // Disable Compare on CC0
      timerTriggered = 1;
      TC4->COUNT8.INTFLAG.reg |= TC_INTFLAG_MC0;   // Clear the MC0 int flag
   }
}

The terminal output of the program can be seen in the picture below:

You can see from this picture that the first few times it doesn’t work at all:

Before: 17 - After: 17.
Before: 21 - After: 22.
Before: 12 - After: 13.

But after about 7 External Interrupts it starts to function correctly. Here you can see:

Before: 236 - After: 182.
Before: 182 - After: 22.
Before: 22 - After: 223

Each of these are giving a 201 clock cycle delay (wrapping around to 0 at 255)

This continues as long as I continue to trigger the EXTINT0 Pin. However, when I stop triggering the ExtInt and continue again later (even 10 seconds later) the same pattern occurs. What could possibly be making this happen so intermittently?

Thanks heaps guys.

Ps. If instead I decide to restart on the EXTINT0 and stop the counter on the MC0 then it works perfectly. Or if I use ONESHOT mode it works perfectly too. The problem only occurs when changing the CC0 value in the middle of its operation.

Could you supply your setup code here or alternatively email it to me.

You could try moving all the code out of the ISRs and into your ‘do’ methods, leaving only the code to set the flag within the ISRs. However, without seeing your configuration code, I’m not sure if this will help.

Hi GabrielNoteman,

Thank you for your prompt reply, Sorry about my delayed response, it was the weekend.
The entire code of the program is below:

The only other command inside the interrupt routine is the command to turn off the interrupt. Other than the conditional statement (which is necessary because I have/need multiple pin interrupts in the full program.)

I was thinking of possibly using a 16 bit counter, but the problem seems to be that the write of the CC0 register doesnt seem to be working properly. Perhaps there is some race condition going on here? Is it possible to pause the timer for the duration of the doTimerTriggered() routine? Because stopping the timer also seems to reset it.

Thanks again for having a look over this. Its appreciated.

#include <Arduino.h>

/*----------------------------------------------------------------------
*
*  Description: This program disables the PIRPin External Int for a short
*  duration after a level change. currently set to 800ms.
*
*  Known Issues:
*  - CC0 mistriggeres for the first few MC0s
*
*---------------------------------------------------------------------- */

#define debug(x) SerialUSB.print(x);
#define debugl(x) SerialUSB.println(x);
#define endl SerialUSB.print("\n");
#define flush SerialUSB.flush();

#define GCLK_EIC_ULP32K (0x1u)
#define GCLK_TC4_ULP32K (0x4u)

volatile int sensorTriggered = 0;
volatile int timerTriggered = 0;
const uint8_t sensorDelay = 0xC8;      //Note: 0xC8 = 200 = 800ms
volatile uint8_t currentCount = 0;

void configEIC();
void configTC4();
void doSensorTriggered();
void doTimerTriggered();

/**----------------------
   SETUP PROCEDURES
------------------------
**/

void setup() {

   SerialUSB.begin(57600);

   //Wait for serial monitor to be ready
   while(!SerialUSB);
   debugl("Initialising...\n"); delay(1000);

   configEIC();      //Configure Ext Interrupts
   debugl(" - ExtInt configured\n");

   configTC4();      //Configure Timer Counter 4
   debugl(" - Timer Configured\n");


   //Delay execution for 5 sec to allow time for a new upload after reset
   for( int i = 1; i <= 5; i++){
      digitalWrite(LED_BUILTIN, HIGH);
      delay(500);
      digitalWrite(LED_BUILTIN, LOW);
      delay(500);
      debug(".");
   } endl;


// 6. Enable External Interrupt Controller (EIC)
   EIC->CTRL.reg |= EIC_CTRL_ENABLE;            //Enable EIC
   while(EIC->STATUS.bit.SYNCBUSY & EIC_STATUS_SYNCBUSY);
   NVIC_EnableIRQ(EIC_IRQn);

// 6. Enable Counter Interrupt (TC4)
   TC4->COUNT8.CTRLA.reg |= TC_CTRLA_ENABLE;            //Enable EIC
   while(EIC->STATUS.bit.SYNCBUSY & EIC_STATUS_SYNCBUSY);
   NVIC_EnableIRQ(TC4_IRQn);     //Enable NVIC for TC4C

   debugl(" - Interrupts Enabled");

   SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;  //Set deep sleep mode

   debugl(" ...Starting");
   endl;
}




/*-----------------------
   MAIN - LOOP FOREVER
-------------------------*/
void loop()
{

   //This runs after an External Interrupt
   if (sensorTriggered)
      doSensorTriggered();

   //This runs after the 800ms timer compare
   if(timerTriggered)
      doTimerTriggered();

   //System Sleep
   __WFI();

//   debugl("WAKE");

}


    /*-------------------------
   SUB ROUTINES/FUNCTIONS
---------------------------*/
//This runs after an External Interrupt
void doSensorTriggered()
{
   //Get current count and Print to terminal
   currentCount = TC4->COUNT8.COUNT.reg;
   debug("Before: "); debugl(currentCount); flush;

   //Set CC0 to currentCount and add 800ms (wrap around at 255 cycles)
   TC4->COUNT8.CC[0].reg = currentCount + sensorDelay;
      while(TC4->COUNT8.STATUS.bit.SYNCBUSY & TC_STATUS_SYNCBUSY);

   //Re-enable Counter TC4
   TC4->COUNT8.INTENSET.reg |= TC_INTENSET_MC0;
   sensorTriggered = 0;

}

//This runs after the 800ms timer compare
void doTimerTriggered()
{
   //Get current count and Print to terminal
   currentCount = TC4->COUNT8.COUNT.reg;
   debug("After: "); debugl(currentCount); flush;

   //Re-enable EXTINT0
   EIC->WAKEUP.reg |= EIC_WAKEUP_WAKEUPEN0;
   timerTriggered = 0;
}




/**-------------------------------------
   INTERRUPT ROUTINES - Keep them short!
---------------------------------------- */

//External Interrupts
void EIC_Handler()
{
   //If EXTINT0 is Triggered
   if (EIC->INTFLAG.bit.EXTINT0 && EIC->INTENSET.bit.EXTINT0){
      EIC->WAKEUP.reg &= ~EIC_WAKEUP_WAKEUPEN0;    //Disable EXTINT0 Wakeup
      sensorTriggered = 1;
      EIC->INTFLAG.reg |= EIC_INTFLAG_EXTINT0;     //clear the EXTINT0 flag
   }
}

//Counter Interrupts
void TC4_Handler ()
{
   //If Timer Compare CC0 is a Match
   if (TC4->COUNT8.INTFLAG.bit.MC0 && TC4->COUNT8.INTENSET.bit.MC0) {
      TC4->COUNT8.INTENCLR.reg |= TC_INTFLAG_MC0;  // Disable Compare on CC0
      timerTriggered = 1;
      TC4->COUNT8.INTFLAG.reg |= TC_INTFLAG_MC0;   // Clear the MC0 int flag
   }
}

}





/**-------------------------------------
      PERIPHERALS CONFIGURATION
---------------------------------------- */

void configEIC()
{
/* ---------------Attach interrupt to PIN A1-------------------
*  1. Enable EIC in power manager
*  2. Configure Pin A1 and assign to EXTIN0
*  3. Configure External Interrupt Controller
*  4. Configure the Generic Clock 1 to take the OSCULP32K input
*  5. Assign the OSCULP32K as a clock source for the interrupt
*  6. Enable External Interrupt Controller (EIC)
* ---------------------------------------------------------------*/

// 1. Enable EIC clock in power manager
   PM->APBAMASK.reg |= PM_APBAMASK_EIC;

// 2. Configure Pin A1 and assign to EXTINT0
   debug("Configure pin A1");
   PORT->Group[1].DIRCLR.reg = 1;
   PORT->Group[1].PINCFG[0].reg= PORT_PINCFG_INEN |   //Input Enable
                                 PORT_PINCFG_PMUXEN | //MUX on
                                 PORT_PINCFG_PULLEN;  //Pull Resistor
   PORT->Group[1].DIR.reg = 0;   //Configure as Input
   PORT->Group[1].OUT.reg = 0;   //Configure as Pull_down

   PORT->Group[1].PMUX[0].bit.PMUXE = MUX_PB00A_EIC_EXTINT0; //PinMux to EXTINT0
   debugl("   - Pin Configured\n");

// 3. Configure External Interrupt Controller
   debug("Configure EIC");
   EIC->INTENSET.reg |= EIC_INTENSET_EXTINT0;      //Enable EXTINT0
   EIC->CONFIG[0].reg |=  EIC_CONFIG_SENSE0_RISE |   //Detect rising edge
                          EIC_CONFIG_FILTEN0;        //Filter signal
   EIC->WAKEUP.reg |= EIC_WAKEUP_WAKEUPEN0;    //Wakeup on Interrupt0
   EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO0;    //Enable for every detection
   debugl("   - EIC Configured\n");

// 4. Configure the Generic Clock 1 to take the OSCULP32K input
   debug("Configuring GCLK1");
   GCLK->GENDIV.reg =   GCLK_GENDIV_ID(GCLK_EIC_ULP32K) |
                        GCLK_GENDIV_DIV(1);  //Slow Down Clock. Div by 8.
      while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY)
      debugl("   - enabled");

   debug("Writing config");
   GCLK->GENCTRL.reg =  GCLK_GENCTRL_ID(GCLK_EIC_ULP32K) |
                        GCLK_GENCTRL_SRC_OSCULP32K |
                        GCLK_GENCTRL_DIVSEL |
                        GCLK_GENCTRL_GENEN;
      while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY )
      debugl("   - done");

// 5. Assign the OSCULP32K as a clock source for the interrupt
   debugl("Assign GCLK to EIC");
   GCLK->CLKCTRL.reg =  ((EIC_GCLK_ID << GCLK_CLKCTRL_ID_Pos) |
                        GCLK_CLKCTRL_GEN_GCLK2 |   //Use Generic Clock 'GEN2'
                        GCLK_CLKCTRL_CLKEN);       //Enable Generic Clock
      while ( GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY );
      debugl("   - GCLK Assigned\n");

//-----------------------------------------------------------------------
}


void configTC4()
{
/* ---------------Attach interrupt to PIN A1-------------------
*  1. Enable TC4 in power manager
*  2. Set up the generic clock (GCLK4) used to clock timers
*  3. Configure GCLK4 to use the Low Power Internal Clock
*  4. Assign GCLK4 to TC4 and TC5
*  5. Configure Counter
*  6. Enable Counter Interrupt (TC4)
* ---------------------------------------------------------------*/

// 1. Enable TC4 in power manager
   PM->APBAMASK.reg |= PM_APBCMASK_TC4;

// 2. Set up the generic clock (GCLK4) used to clock timers
   debug("Set GCLK4");
   GCLK->GENDIV.reg = GCLK_GENDIV_ID(GCLK_TC4_ULP32K) |  // Select Generic Clock (GCLK4)
                      GCLK_GENDIV_DIV(8);                // Divide the 32kHz clock source by 8: 32kHz/8=4kHz
   while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);      // Wait for synchronization
   debugl("   - GCLK4 Set.");

// 3. Configure GCLK4 to use the Low Power Internal Clock
   debug("Configure GCLK4");
   GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |                // Set the duty cycle to 50/50 HIGH/LOW
                       GCLK_GENCTRL_GENEN |              // Enable GCLK4
                       GCLK_GENCTRL_SRC_OSCULP32K |      // Set the 32kHz LP clock source
                       GCLK_GENCTRL_ID(GCLK_TC4_ULP32K); // Select GCLK4
   while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);
   debugl("   - GCLK4 Configured");

// 4. Assign GCLK4 to TC4 and TC5
   debug("Assign GCLK4 to TC4");
   GCLK->CLKCTRL.reg =  GCLK_CLKCTRL_CLKEN |             // Enable GCLK4 to TC4 and TC5
                         GCLK_CLKCTRL_GEN_GCLK4 |        // Select GCLK4
                         GCLK_CLKCTRL_ID_TC4_TC5;        // Feed the GCLK4 to TC4 and TC5
      while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);
      debugl("GCLK4 Assigned");

// 5. Configure Counter
   debug("Configure Counter");
   TC4->COUNT8.CTRLA.reg |= TC_CTRLA_MODE_COUNT8 |       // Set the counter to 8-bit mode
                            TC_CTRLA_PRESCALER_DIV16 |     // Divide the counter by 16: 4000Hz /16 = 250Hz
                            TC_CTRLA_RUNSTDBY;           // Let it run in standby
   while (TC4->COUNT8.STATUS.bit.SYNCBUSY & TC_STATUS_SYNCBUSY);

   //Set the counter to stop at a value
   REG_TC4_COUNT8_CC0 = sensorDelay;         // use 0xFA = 250 for 1sec. 0xC8 = 200 for 800ms
   while (TC4->COUNT8.STATUS.bit.SYNCBUSY & TC_STATUS_SYNCBUSY);

   REG_TC4_INTFLAG |= TC_INTFLAG_MC0;        // Clear the interrupt flag
   REG_TC4_INTENSET |= TC_INTENSET_MC0;      // Enable TC4 interrupts
   REG_TC4_INTENCLR |= TC_INTENCLR_MC1 | TC_INTENCLR_OVF;
   debugl("   - configured\n");

// -----------------------------------------------------------------------------

}

Update: I worked it out today. Mostly, now its only mistriggering once at the start of the program, I can probably alleviate that too.

If anyone is interested heres what I did:
I changed it from an 8-bit timer to a 16-bit timer
I set timerTriggered = 0 and sensorTriggered = 0 on each “do” routine.
I paused the timer for the duration of the “do” routine.
I did this by first capturing the count time at the start of each do routine and stopped the timer. Restarting it and resetting the count at the end of the routine.
I will post my code in an edit.

Thank you for your assistance Gabriel. :slight_smile: