Go Down

Topic: MKR Fox 1200 External Interrupt Counter (Read 162 times) previous topic - next topic

lucasdrakon

Hello everyone!
I'm new to this forum, so sorry for any mistakes in etiquette (or placement of the question).

I have been trying to count the number of pulses on a pin on the MKR Fox 1200, using the Event System and Timer/Counter provided by the
SAMD21, while in deep sleep (or STANDBY) mode.

I have managed to count the pulses (FALLING edge) while not in deep sleep, but it stops working after entering deep sleep.
I have assumed that this is due to the clocks used being turned off, so I have been trying to keep them on.

I'm using the Arduino Low Power library.

This is what I have got so far:
The files are as follows:
  • SAMD21_Pin_Pulse_Count.ino: The main sketch file that #includes the counter functions
  • LowPowerEIC_Counter.cpp: The source file for the counter setup and data retrieval
  • LowPowerEIC_Counter.h: The header file that ties them together


SAMD21_Pin_Pulse_Count.ino
Code: [Select]

#include "Arduino.h"
#include <ArduinoLowPower.h>
#include "LowPowerEIC_Counter.h"

// Alarm event
void timerEvent() {

}

void setup()
{
 // Setup Serial
 Serial1.begin(115200);

 // Setup Low Power
 LowPower.attachInterruptWakeup(RTC_ALARM_WAKEUP, timerEvent, CHANGE);

 // Setup Counter
 counterSetup();
}

void loop()
{
 Serial1.print("TC4 COUNT: ");
 Serial1.println(getCount());
 Serial1.print("TC4 CC[0]: ");
 Serial1.println(getPrevCount());

 LowPower.deepSleep(5000);
// delay(5000); // USED FOR TESTING NON DEEP SLEEP

}


LowPowerEIC_Counter.cpp
Code: [Select]

// Setup Functions
void counterSetup() {
 // Oscillator
 setupOscillator();

 // Generic Clock
 startGCLK();

 // Port Setup
 setupPORT();

 // External Interrupt Controller Setup
 setupEIC();


 // Event System Setup
 setupEVSYS();

 // Timer/Counter 4 Setup
 setupTC();

 // Reset TC
 resetCount();
}

void setupOscillator() {
 SYSCTRL->XOSC32K.reg |= SYSCTRL_XOSC32K_RUNSTDBY | SYSCTRL_XOSC32K_ONDEMAND;
}

void startGCLK() {
 // Enable GCLK Clocks
 PM->APBAMASK.reg |= PM_APBAMASK_GCLK;
}

void setupPORT() {
 // Setup Input PullUp
 PORT->Group[g_APinDescription[0].ulPort].PINCFG[g_APinDescription[0].ulPin].bit.PULLEN = 1; // Enable pull resistor on pin 0
 PORT->Group[g_APinDescription[0].ulPort].OUTSET.reg |= 1 << g_APinDescription[0].ulPin; // Set pull up resistor on pin 0

 // Setup Port Multiplexer
 PORT->Group[g_APinDescription[0].ulPort].PINCFG[g_APinDescription[0].ulPin].bit.PMUXEN = 1; // Enable Multiplexer on pin 0
 PORT->Group[g_APinDescription[0].ulPort].PMUX[g_APinDescription[0].ulPin].reg |= PORT_PMUX_PMUXO_A; // Select Multiplexer Peripheral function A on pin 0
}

void setupEIC() {
 // Enable CLK_EIC_APB
 PM->APBAMASK.reg |= PM_APBAMASK_EIC;

 // Setup Generic Clock Generator 5
 GCLK->GENDIV.reg = GCLK_GENDIV_ID(5) | // Select Generic Clock Generator ID 5
 GCLK_GENDIV_DIV(1); // Set division factor to 1
 GCLK->GENCTRL.reg = GCLK_GENCTRL_SRC_XOSC32K | // Select Generic Clock Generator Source XOSC
 GCLK_GENCTRL_ID(5) | // Select Generic Clock Generator ID 5
 GCLK_GENCTRL_RUNSTDBY | // Set Generic Clock Generator to run in standby mode
 GCLK_GENCTRL_GENEN; // Enable Generic Clock Generator

 // Setup GCLK_EIC
 GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_EIC | // Select Generic Clock ID
 GCLK_CLKCTRL_GEN(5) | // Select Generic Clock Generator ID 5
 GCLK_CLKCTRL_CLKEN; // Enable Generic Clock Generator
 while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for GCLK synchronisation

 EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO6; // Enable External Interrupt 6 Event Output
 EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE6_FALL | // Set Event Detecting a FALLING edge on interrupt 6
 EIC_CONFIG_FILTEN6; // Enable Filter on interrupt 6
 EIC->CTRL.bit.ENABLE = 1; // Enable EIC
 while (EIC->STATUS.bit.SYNCBUSY); // Wait for EIC synchronisation
}

void setupEVSYS() {
 // Enable EVSYS Clock
 PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;

 // Setup EVSYS
 EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) | // Attach Event User to Channel 0 (n+1)
 EVSYS_USER_USER(EVSYS_ID_USER_TC4_EVU); // Set Event User as TC4

 EVSYS->CHANNEL.reg = EVSYS_CHANNEL_CHANNEL(0) | // Select Channel 0 to configure
 EVSYS_CHANNEL_PATH_ASYNCHRONOUS | // Set Channel as Asynchronous
 EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | // Set no Edge Detection
 EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_6); // Set Event Generator to External Interrupt 6

}

void setupTC() {
 // Enable TC4 Clock
 PM->APBCMASK.reg |= PM_APBCMASK_TC4;

 // Setup GCLK Generator ID 4
 GCLK->GENDIV.reg = GCLK_GENDIV_ID(4) | // Select Generic Clock Generator ID 4
 GCLK_GENDIV_DIV(1); // Set division factor to 1
 GCLK->GENCTRL.reg = GCLK_GENCTRL_SRC_XOSC32K | // Select Generic Clock Generator Source XOSC
 GCLK_GENCTRL_ID(4) | // Select Generic Clock Generator ID 4
 GCLK_GENCTRL_RUNSTDBY | // Set Generic Clock Generator to run in standby mode
 GCLK_GENCTRL_GENEN; // Enable Generic Clock Generator

 // Setup GCLK_TC4_TC5
 GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_TC4_TC5 | // Select Generic Clock ID
 GCLK_CLKCTRL_GEN(4) | // Select Generic Clock Generator ID 4
 GCLK_CLKCTRL_CLKEN; // Enable Generic Clock Generator
 while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for GCLK synchronisation

 // Setup TC4
 TC4->COUNT32.CTRLA.reg = TC_CTRLA_MODE_COUNT32; // Select 32-bit counter mode
// TC_CTRLA_PRESCALER_DIV1024;

 TC4->COUNT32.EVCTRL.reg |= TC_EVCTRL_EVACT_COUNT | // Set event action to COUNT
 TC_EVCTRL_TCEI; // Enable event line into TC4
 TC4->COUNT32.CTRLC.reg |= TC_CTRLC_CPTEN0; // Enable Capture Channel 0
 TC4->COUNT32.CTRLBCLR.reg = TC_CTRLBCLR_ONESHOT;

 TC4->COUNT32.CTRLA.bit.ENABLE = 1; // Enable TC4
 while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for TC4 synchronisation
}

// Getters/Re-setters
int getCount() {
 Serial1.println(TC4->COUNT32.COUNT.reg);
 return TC4->COUNT32.COUNT.reg;
}
int getPrevCount() {
 return TC4->COUNT32.CC[0].reg;
}
void resetCount() {
 TC4->COUNT32.COUNT.reg = 0;
}


LowPowerEIC_Counter.h
Code: [Select]

#ifndef LOWPOWEREIC_COUNTER_H_
#define LOWPOWEREIC_COUNTER_H_
#include <Arduino.h>

// Setup Functions
void counterSetup(void);
void setupOscillator(void);
void startGCLK(void);
void setupPORT(void);
void setupEIC(void);
void setupEVSYS(void);
void setupTC(void);

// Getters/Re-setters
int getCount(void);
int getPrevCount(void);
void resetCount(void);

#endif /* LOWPOWEREIC_COUNTER_H_ */


Any questions are most welcome!
Thanks in advance!

MartinL

#1
Sep 27, 2020, 08:09 pm Last Edit: Sep 27, 2020, 08:21 pm by MartinL
Hi lucasdrakon,

It could be that SAMD21's internal voltage regulator is unable to power all the peripheral modules you require (EIC, TC and Event System), because the regulator itself switches to low power mode during sleep.

To ensure the voltage regulator remains in normal configuration during sleep mode, just set it's RUNSTDBY bit in SYSCTRL's VREG register:

Code: [Select]
SYSCTRL->VREG.bit.RUNSTDBY = 1;      // Keep the voltage regulator in normal configuration during run standby
This only needs to be set once at the start of your sketch.

lucasdrakon

Hello! Thank you for the quick response!

That has, unfortunately, not worked.
I put it at the top of counterSetup(), which I assume is an okay place for it.

Your mention of voltage regulators reminded me I was using an external reg, so I unplugged it and used the USB port,
though sadly this didn't work with or without the suggested change.

Is there anything else it could be?

Thanks again!

MartinL

#3
Sep 28, 2020, 01:40 pm Last Edit: Sep 28, 2020, 01:56 pm by MartinL
Hi lucasdrakon,

Here's some example code that generates a 1Hz test square wave using TCC2 on PA16 (MOSI on the MKR1200). Plugging this output into the pin PA22 (D0 on the MKR1200) input will feed the pulses through to timer TC4 via the event system. TC4 is configured to simply COUNT the incoming events. Both TCC2 and TC4 are clocked by the external crystal at 32.768kHz.

The system enters sleep mode in the loop() function only to be woken by the Serial1 (on SERCOM5) and TCC2 interrupts that print the value of the TC4's COUNT register every second, before returning to sleep. The DFLL48M clock source and the Serial1 peripheral have also been set to run in standby, but only for debug purposes. DFLL48M is used as the clock source for Serial1.

Serial1 (TX, RX) is used, as entering sleep mode disables the native USB port. I've used a FTDI (USB-to-Serial) conveter with a 5V to 3.3V level shifter to view the TC4 timer's output on the Arduino IDE console.

The TC4 timer increments by 1 every second as expected.

By asychronously detecting LOW levels rather than the FALLING edge with the EIC, its possible to route the input signals to the event system without the EIC requiring a generic clock.

MartinL

#4
Sep 28, 2020, 01:42 pm Last Edit: Sep 28, 2020, 01:48 pm by MartinL
Here's the code part I:

Code: [Select]
// Set-up TC4 to count incoming pulses on the event system during SLEEP (standby) mode
void setup()
{
  Serial1.begin(115200);                           // Open the console on Serial1

  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;           // Switch on the event system peripheral
  SYSCTRL->VREG.bit.RUNSTDBY = 1;                  // Keep the voltage regulator in normal configuration during run stanby
  SYSCTRL->XOSC32K.bit.RUNSTDBY = 1;               // Set the external 32kHz crystal to run standby mode

  ///////////////////////////////////////////////////////////////////////////////////////
  // Debug - keep Serial1 alive during sleep by activating RUNSTDBY and DFLL48M
  ///////////////////////////////////////////////////////////////////////////////////////
 
  SYSCTRL->DFLLCTRL.bit.RUNSTDBY = 1;              // Set the DFLL48M to run standby mode
  while(!SYSCTRL->PCLKSR.bit.DFLLRDY);             // Wait for synchronization

  SERCOM5->USART.CTRLA.bit.ENABLE = 0;             // Disable Serial1
  while (SERCOM5->USART.SYNCBUSY.bit.ENABLE);      // Wait for synchronization
  SERCOM5->USART.CTRLA.bit.RUNSTDBY = 1;           // Set the run on standby bit for Serail1
  SERCOM5->USART.CTRLA.bit.ENABLE = 1;             // Enable Serial1
  while (SERCOM5->USART.SYNCBUSY.bit.ENABLE);      // Wait for synchronization

  ///////////////////////////////////////////////////////////////////////////////////////
  // GCLK4 Initialisation - use external 32.768kHz crystal as clock source
  ///////////////////////////////////////////////////////////////////////////////////////

  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(1) |                // Divide by 1
                     GCLK_GENDIV_ID(4);                   // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);                      // Wait for synchronization
 
  //Configure Clock Generator 4 to give output to Timer modules to generate PWM signal
  GCLK->GENCTRL.reg = GCLK_GENCTRL_RUNSTDBY |             // Set GCLK4 to run in standby mode                                       
                      GCLK_GENCTRL_IDC |                  // 50-50 duty-cycle
                      GCLK_GENCTRL_GENEN |                // Enable generic clock generator                   
                      GCLK_GENCTRL_SRC_XOSC32K |          // Use external crystal 32.768kHz oscillator
                      GCLK_GENCTRL_ID(4);                 // Select GCLK 4

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |                // Enable generic clock
                      GCLK_CLKCTRL_GEN_GCLK4 |            // Select GCLK4
                      GCLK_CLKCTRL_ID_TCC2_TC3;           // Route GCLK4 output to TCC2 & TC3

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |                // Enable generic clock
                      GCLK_CLKCTRL_GEN_GCLK4 |            // Select GCLK4
                      GCLK_CLKCTRL_ID_TC4_TC5;            // Route GCLK4 output to TC4 & TC5

  ////////////////////////////////////////////////////////////////////////////////////////
  // TCC2 Initialisation - generate pulse every 1s (1Hz) on port pin PA16
  ////////////////////////////////////////////////////////////////////////////////////////

  // Enable the port multiplexer on port pin PA16
  PORT->Group[PORTA].PINCFG[16].bit.PMUXEN = 1;
  // Set-up the pin as a TCC2/WO[0] peripheral on PA16
  PORT->Group[PORTA].PMUX[16 >> 1].reg |= PORT_PMUX_PMUXE_E;

  TCC2->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;                 // Set the TCC2 timer to normal PWM mode (NPWM)
  while(TCC2->SYNCBUSY.bit.WAVE);                         // Wait for synchronization
 
  TCC2->CTRLA.reg = TCC_CTRLA_PRESCSYNC_PRESC |           // Overflow on prescaler clock
                    TCC_CTRLA_RUNSTDBY |                  // Set TCC2 to run on standby
                    TCC_CTRLA_PRESCALER_DIV16;            // Set the prescaler to divde by 16
 
  TCC2->PER.reg = 2047;                                   // Set the period (PER) register for a PWM frequency of 1Hz
  while(TCC2->SYNCBUSY.bit.PER);                          // Wait for synchronization

  TCC2->CC[0].reg = 1024;                                 // Set the counter compare 0 (CC0) register for a PWM duty-cycle of 50%
  while(TCC2->SYNCBUSY.bit.CC0);                          // Wait for synchronization

  NVIC_SetPriority(TCC2_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC2 to 0 (highest)
  NVIC_EnableIRQ(TCC2_IRQn);         // Connect TCC2 to Nested Vector Interrupt Controller (NVIC)

  TCC2->INTENSET.reg = TCC_INTENSET_OVF;                  // Enable overflow interrupts

  TCC2->CTRLA.bit.ENABLE = 1;                             // Enable TCC2
  while (TCC2->SYNCBUSY.bit.ENABLE);                      // Wait for synchronization

MartinL

...and the code part II:

Code: [Select]

  ///////////////////////////////////////////////////////////////////////////////////////
  // TC4 Initialisation
  ///////////////////////////////////////////////////////////////////////////////////////

  TC4->COUNT32.CTRLA.reg =  TC_CTRLA_RUNSTDBY |           // Set the timer to run in standby mode                           
                            TC_CTRLA_MODE_COUNT32;        // Enable 32-bit COUNT mode                                                                     

  /////////////////////////////////////////////////////////////////////////////////////////////////////////
  // EIC Initialisation                                   
  /////////////////////////////////////////////////////////////////////////////////////////////////////////

  // Enable the port multiplexer on port pin PA22
  PORT->Group[PORTA].PINCFG[22].bit.PMUXEN = 1;
  PORT->Group[PORTA].PINCFG[22].bit.PULLEN = 1;
  // Set-up the pin as an EIC (interrupt) peripheral on PA22
  PORT->Group[PORTA].PMUX[22 >> 1].reg |= PORT_PMUX_PMUXE_A;
 
  EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO6;                                 // Enable event output on external interrupt 6
  EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE6_LOW;                             // Set event detecting a LOW level
  EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT6;                                // Disable interrupts on external interrupt 6
  EIC->CTRL.bit.ENABLE = 1;                                                // Enable the EIC peripheral
  while (EIC->STATUS.bit.SYNCBUSY);                                        // Wait for synchronization
 
  /////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Event System Initialisation                                   
  /////////////////////////////////////////////////////////////////////////////////////////////////////////
 
  EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT |                // No event edge detection
                       EVSYS_CHANNEL_PATH_ASYNCHRONOUS |                   // Set event path as asynchronous                     
                       EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_6) |    // Set event generator (sender) as EIC, channel 6
                       EVSYS_CHANNEL_CHANNEL(0);                           // Attach the generator (sender) to channel 0
                   
  EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) |                                // Attach the event user (receiver) to channel 1 (n + 1)
                    EVSYS_USER_USER(EVSYS_ID_USER_TC4_EVU);                // Set the event user (receiver) as timer TC4, channel 0

  TC4->COUNT32.EVCTRL.reg |= TC_EVCTRL_TCEI |                              // Enable TC4 event inputs                               
                             TC_EVCTRL_EVACT_COUNT;                        // Set timer TC4 to COUNT upon receiving event
                                                                         
  TC4->COUNT32.CTRLA.bit.ENABLE = 1;                                       // Enable TC4
  while (TC4->COUNT32.STATUS.bit.SYNCBUSY);                                // Wait for synchronization

  TC4->COUNT32.READREQ.reg = TC_READREQ_RCONT |                            // Enable a continuous read request
                             TC_READREQ_ADDR(TC_COUNT32_COUNT_OFFSET);     // Offset of the 32 bit COUNT register
       
  SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;                                       // Put the SAMD21 in deep sleep upon executing the __WFI() function
  NVMCTRL->CTRLB.reg |= NVMCTRL_CTRLB_SLEEPPRM_DISABLED;                   // Disable auto power reduction during sleep - SAMD21 Errata 1.14.2
}

void loop()
{
  // Due to a hardware bug on the SAMD21, the SysTick interrupts become active before the flash has powered up from sleep, causing a hard fault
  // To prevent this the SysTick interrupts are disabled before entering sleep mode
  SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;           // Disable SysTick interrupts
  __DSB();                                              // Complete outstanding memory operations - not required for SAMD21 ARM Cortex M0+
  __WFI();                                              // Put the SAMD21 into deep sleep, Zzzzzzzz... 
  SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;            // Enable SysTick interrupts 
}

void TCC2_Handler()                                     // Handler for the TCC2 timer - wakes up CPU
{
  if (TCC2->INTFLAG.bit.OVF)                            // Check if the overflow (OVF) flag has been set
  {
    TCC2->INTFLAG.bit.OVF = 1;                           // Clear the OVF interrupt flag
    Serial1.println(TC4->COUNT32.COUNT.reg);            // Display the TC4's COUNT register
  }
}

lucasdrakon

Awesome!
That's working great! :D

I copied most of it (excluding the TCC wave generator), and now it is working with the pulses from my input.
I didn't keep the voltage regulator in normal config when in standby, and it still worked.
I also left out the Serial1 debug related stuff because I only need debug output when out of deep sleep.
It still uses the Arduino Low Power library to go into deep sleep, because I need it to wake up after a while.

Thanks for your help!

One last thing, do you know what I could do about debouncing the input. Since it comes from a reed switch I am assuming it will bounce. I previously kept the EIC with its own clock and put it detecting FALLING edge and with the majority vote filter on it, because that then allows me to regulate the speed it works at.
Hope it isn't too hard to do.

And once again, thank you for fixing my code!

MartinL

#7
Sep 28, 2020, 06:38 pm Last Edit: Sep 28, 2020, 06:45 pm by MartinL
Hi lucasdrakon

Quote
One last thing, do you know what I could do about debouncing the input. Since it comes from a reed switch I am assuming it will bounce. I previously kept the EIC with its own clock and put it detecting FALLING edge and with the majority vote filter on it, because that then allows me to regulate the speed it works at.
Hope it isn't too hard to do.
In that case, if you're using either edge triggered interrupts (FALLING) and/or enabling the interrupt filter then it's necessary to clock the EIC with a generic clock, as your were doing previously:

Code: [Select]
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |        // Enable generic clock
                    GCLK_CLKCTRL_GEN_GCLK4 |    // Select GCLK4 at 32.768kHz (XOSC32K)
                    GCLK_CLKCTRL_ID_EIC;        // Route GCLK4 output to the EIC

Code: [Select]
EIC->CONFIG[0].reg |= EIC_CONFIG_FILTEN6 |        // Enable the interrupt filter
                      EIC_CONFIG_SENSE6_FALL;     // Set event detecting a FALLING edge

lucasdrakon

Cool!

I shall try that tomorrow!

Thank you for all your assistance! I do believe that is all!
Hope you have a nice week!

Go Up