Reed Switch Low-power Switch Detection Code Example

This example code allows you to develop low-power switch state detection for reed and tamper switch sensors on Silicon Labs EFR32 chipsets. The new low-power switch state detection method increases the battery life of reed and tamper switch applications by 30 percent compared to the traditional approach based on pull-up and pull-down resistors.

 

Reed Switch Code Example

/***************************************************************************//**

* @file

* @brief Low Power Switch Detection using LESENSE

*        This project initializes uses LESENSE to generate a pulse on the

*        LESENSE_SWITCH_OUT pin, and sense the LESENSE_SWITCH_IN pin using ACMP1.

*        Based on the result, a change in the switch state

*       (OPEN->CLOSED or CLOSED->OPEN) can be detected and can wakeup the system

*        from EM2.

*

*

* This file is licensed under the Silabs License Agreement. See the file

* "Silabs_License_Agreement.txt" for details. Before using this software for

* any purpose, you must agree to the terms of that agreement.

*

******************************************************************************/

#include <stdnint.h>

#include <stdbool.h>

#include <stdio.h>

 

#include "em_device.h"

#include "em_acmp.h"

#include "em_assert.h"

#include "em_chip.h"

#include "em_cmu.h"

#include "em_emu.h"

#include "em_gpio.h"

#include "em_core.h"

#include "em_lesense.h"

#include "em_pcnt.h" 

 

#include "bspconfig.h"

#include "bsp.h"

#include "bsp_trace.h" 

 

/***************************************************************************//**

* Macro definitions*/

/******************************************************************************/

// LESENSE Channel.  Must match the LES_CHx assignment to the LESENSE_SWITCH_OUT pin in the datasheet Alt. Function table

#define LESENSE_CHANNEL 9 

 

// LESENSE PWM output tied to one side of the switch

// PA1=EXP14

#define LESENSE_SWITCH_OUT_PORT gpioPortA

#define LESENSE_SWITCH_OUT_PIN 1

 

// ACMP input, tied to other side of the switch and the pull-down resistor

// Must match the ACMP_APORT selection in the datasheet APORT Client Map

// PC9=EXP10

#define LESENSE_SWITCH_IN_PORT gpioPortC

#define LESENSE_SWITCH_IN_PIN 9 

 

// Switch State Output

// High=Closed, Low=Open

// PC11=EXP16

#define SWITCH_STATE_GPIO_PORT gpioPortC

#define SWITCH_STATE_GPIO_PIN 11

 

#define LESENSE_INPUT_PIN_MODE  gpioModeDisabled // gpioModeInputPullFilter //gpioModeInput (should be disabled for ACMP usage)

#define LESENSE_INPUT_PIN_DOUT 0 // (setting DOUT=1 for glitch filter causes additional ~700nA)

#define LESENSE_INPUT_PIN_OVTDIS true // Set true to disable OVT 

 

#define ACMP_WARMUP_MODE  lesenseWarmupModeNormal // (lesenseWarmupModeACMP causes +3.8uA increase)

#define ACMP_ACCURACY acmpAccuracyLow //acmpAccuracyHigh (high is ~30nA higher)

#define ACMP_POWER_SUPPLY acmpPowerSourceDvdd // acmpPowerSourceAvdd (don't see any uA difference)

#define ACMP_FULLBIAS false // (true = +150nA)#define ACMP_BIASPROG   0x01 // 0x1F // (BIASPROG=0x01 saves about 50nA compared to 0x1F)

 

#define ACMP_VA_INPUT acmpVAInputVDD

#define ACMP_VB_INPUT acmpVBInput1V25

#define ACMP_VLPSOURCE acmpVLPInputVADIV //acmpVLPInputVBDIV

#define ACMP_NEG_INPUT acmpInputVLP //acmpInputVBDIV // acmpInputVADIV // (acmpInputVLP seems 40nA lower)

#define ACMP_POS_INPUT acmpInputAPORT1YCH9 // ACMP Positive APORT selection.  Must match LESENSE_CHANNEL

#define ACMP_APORT acmpExternalInputAPORT1Y

 

#define LESENSE_SCAN_FREQ 10 // In Hz

#define LESENSE_BIASMODE lesenseBiasModeDontTouch // (lesenseBiasModeDutyCycle causes +380nA) 

 

// For ULFRCO

#define LESENSE_CLK_SOURCE cmuSelect_ULFRCO

#define EXTIME    0x01

#define SAMPLEDLY  0x01

#define MEASDLY  0x00

#define LESENSE_CLKDIV lesenseClkDiv_1 

 

//// For LFXO / LFRCO (LFRCO seems about 600nA higher than LFXO, LFXO is about 250nA higher than ULFRCO)

//#define LESENSE_CLK_SOURCE cmuSelect_LFXO

//#define  EXTIME    0x04

//#define SAMPLEDLY  0x02

//#define MEASDLY  0x01

//#define LESENSE_CLKDIV lesenseClkDiv_2 

 

/***************************************************************************//**

* Global variables*/

/******************************************************************************/

volatile uint32_t delay;

volatile bool SWITCH_CLOSED = false; // Leave set to false by default, code should correct this later if wrong

 

/***************************************************************************//**

* Configuration flags*/

/******************************************************************************/

bool ENABLE_LOW_POWER_SWITCH_DETECTION = true;  // Set to false for measurement baseline

bool GO_TO_EM2 = true;  // If false, will sit in loop in EM0/1

bool DRIVE_STATE_GPIO = true; // Can cause extra uA if scope lead attached to GPIO

bool EM2_POWER_DOWN_RAM = true;

bool EM2_DISABLE_HFCLKS = false; // Doesn't really save any current in EM2, also seems to break app

bool EM2_DISABLE_LFCLKS = true;  // Shuts down LFCLKS except LFACLK in EM2

bool USE_DCDC = true;

//  Determine whether the switch is closed or open at power up

bool DETERMINE_INITIAL_SWITCH_STATE = true;

 

/***************************************************************************//**/

/* Prototypes*/

/******************************************************************************/

void enter_EM2(void);

void Toggle_Switch_State(void);

 

 /**************************************************************************//**

* @brief GPIO initialization

*****************************************************************************/

void initGPIO(void)

  // Enable GPIO clock 

  CMU_ClockEnable(cmuClock_GPIO, true); 

 

  // To monitor switch state on a GPIO, configure switch state GPIO

  if (DRIVE_STATE_GPIO) GPIO_PinModeSet(SWITCH_STATE_GPIO_PORT, SWITCH_STATE_GPIO_PIN, gpioModePushPull, 0);

 

if (ENABLE_LOW_POWER_SWITCH_DETECTION) {    

  if (DETERMINE_INITIAL_SWITCH_STATE) {      

    // Determine initial switch state by temporarily driving a static level on OUT and reading IN      

    // Pull switch output pin high      

   GPIO_PinModeSet(LESENSE_SWITCH_OUT_PORT, LESENSE_SWITCH_OUT_PIN, gpioModeInputPull, 1); 

 

    // Set switch input pin to input

   GPIO_PinModeSet(LESENSE_SWITCH_IN_PORT, LESENSE_SWITCH_IN_PIN, gpioModeInput, 1);

 

    // Read state of switch input pin

    // Switch State Output => // High=Closed, Low=Open

    if (GPIO_PinInGet(LESENSE_SWITCH_IN_PORT,LESENSE_SWITCH_IN_PIN)==1) { 

      SWITCH_CLOSED = true;

      if (DRIVE_STATE_GPIO) GPIO_PinOutSet(SWITCH_STATE_GPIO_PORT, SWITCH_STATE_GPIO_PIN);

    } else {  

      SWITCH_CLOSED = false;   

      if (DRIVE_STATE_GPIO) GPIO_PinOutClear(SWITCH_STATE_GPIO_PORT, SWITCH_STATE_GPIO_PIN);

    }    

  }       

 

// Configure GPIOs for Low Power Switch Detection

// Configure LESENSE channel output pins

GPIO_PinModeSet(LESENSE_SWITCH_OUT_PORT, LESENSE_SWITCH_OUT_PIN, gpioModePushPull, 0); 

 

// Configure switch input

GPIO_PinModeSet(LESENSE_SWITCH_IN_PORT, LESENSE_SWITCH_IN_PIN, LESENSE_INPUT_PIN_MODE, LESENSE_INPUT_PIN_DOUT);

// Disable Overvoltage on Input Pin

if (LESENSE_INPUT_PIN_OVTDIS) GPIO->P[LESENSE_SWITCH_IN_PORT].OVTDIS |= (1<<LESENSE_SWITCH_IN_PIN); 

   } 

}

/************************************************************************************//*

* @brief  Sets up the ACMP to count LC sensor pulses

**************************************************************************************/

static void setupACMP(void)

// ACMP configuration constant table.

static const ACMP_Init_TypeDef initACMP =  

{    

.fullBias                 = ACMP_FULLBIAS,                  // fullBias

.biasProg                 = ACMP_BIASPROG,                  // biasProg

.interruptOnFallingEdge   = false,                 // interrupt on rising edge

.interruptOnRisingEdge    = false,                 // interrupt on falling edge

.inputRange               = acmpInputRangeFull,    // Full ACMP rang

.accuracy                 = ACMP_ACCURACY,      // Set accuracy

.powerSource              = ACMP_POWER_SUPPLY,   // Use AVDD as power source, default to 3.3V

.hysteresisLevel_0        = acmpHysteresisLevel2,  // hysteresis level 2

.hysteresisLevel_1        = acmpHysteresisLevel2,  // hysteresis level 2

.vlpInput                 = ACMP_VLPSOURCE,       // Set the VLP input source.

.inactiveValue            = false,                 // no inactive value

.enable                   = false                   // Enable after init

}; 

 

// If using VA  static const ACMP_VAConfig_TypeDef initVa =  

{

ACMP_VA_INPUT,                                     // Use VDD as input for VA

32,                                                // ~0.55V, VA divider when ACMP output is 0, VDD/2

32                                                 // ~0.55V, VA divider when ACMP output is 1, VDD/2

}; 

// If using VB

static const ACMP_VBConfig_TypeDef initVb =  

{

ACMP_VB_INPUT,                                    // Use 1.25V REF as input for VB

32,                                                // ~0.64V with 1.25V ref, VB divider when ACMP output is 0, VDD/2

32                                                 // ~0.64V with 1.25V ref, VB divider when ACMP output is 1, VDD/2

};

 

CMU_ClockEnable(cmuClock_ACMP1, true);

//Initialize ACMP

ACMP_Init(ACMP1, &initACMP);

//Setup VADIV

ACMP_VASetup(ACMP1, &initVa);

ACMP_VBSetup(ACMP1, &initVb);

 

// Setup ACMP1 inputs

ACMP_ChannelSet(ACMP1, ACMP_NEG_INPUT, ACMP_POS_INPUT);

//Enable LESENSE control of ACMP

ACMP_ExternalInputSelect(ACMP1, ACMP_APORT);

// Enable ACMP1

ACMP_Enable(ACMP1);

/**********************************************************************************************//*

* @brief  Sets up the LESENSE

************************************************************************************************/

static void setupLESENSE(void)

// LESENSE configuration structure

static const LESENSE_Init_TypeDef initLesense = 

{    

  .coreCtrl         = 

   {

      .scanStart    = lesenseScanStartPeriodic,                // set scan to periodic scan

      .prsSel       = lesensePRSCh0,                           // PRS selection channel 0

      .scanConfSel  = lesenseScanConfDirMap,                   // lesenseScanConfInvMap, //direct scan configuration register usage

      .invACMP0     = false,                                   // no invert ACMP0

      .invACMP1     = true,                                     // invert ACMP1

      .dualSample   = false,                                   // no dualSample

      .storeScanRes = false,                                   // do not Store SCANERS in RAM after each scan

      .bufOverWr    = false, //true,                                    // never write to buffer even if it is full

      .bufTrigLevel = lesenseBufTrigHalf,                      // set DMA and interrupt flag when buffer is half full

      .wakeupOnDMA  = lesenseDMAWakeUpDisable,                 // Disable DMA wakeup

      .biasMode     = LESENSE_BIASMODE,                         // Don't Touch is lowest power

      .debugRun     = true                                    // LESENSE not running on debugging mode

    },

       .timeCtrl          =

    {      .startDelay      = 0

    },

       .perCtrl           =

    {

      .acmp0Mode       = lesenseACMPModeDisable,                // Disable ACMP0

      .acmp1Mode       = lesenseACMPModeMux,                    // Enable ACMP1 Mux

      .warmupMode      = ACMP_WARMUP_MODE

         },

  };

   // Channel configuration

  static const LESENSE_ChDesc_TypeDef initLesenseCh_Excite =  

{

    .enaScanCh     = true,

    .enaPin        = true, 

   .enaInt        = true, 

   .chPinExMode   = lesenseChPinExHigh,                   //Enable excitation

    .chPinIdleMode = lesenseChPinIdleDis,                           //Disable idle

    .useAltEx      = false,

    .shiftRes      = false,

    .invRes        = false,

    .storeCntRes   = false, //true,

    .exClk         = lesenseClkLF,

    .sampleClk     = lesenseClkLF,

    .exTime        = EXTIME,

    .sampleDelay   = SAMPLEDLY,

    .measDelay     = MEASDLY, 

   .acmpThres     = 0,  //

    .sampleMode    = lesenseSampleModeACMP, //Sample directly from ACMP

    .intMode       = lesenseSetIntLevel,   //Interrupt on level

    .cntThres      = 0x01,    .compMode      = lesenseCompModeGreaterOrEq,

  };

   // Use LFXO as LESENSE clock source

  CMU_ClockSelectSet(cmuClock_LFA, LESENSE_CLK_SOURCE);

  CMU_ClockEnable(cmuClock_HFLE, true);

  CMU_ClockEnable(cmuClock_LESENSE, true);

 

   //Initialize LESENSE interface _with_ RESET

  LESENSE_Init(&initLesense, true);

   // Configure LESENSE channel

  LESENSE_ChannelConfig(&initLesenseCh_Excite, LESENSE_CHANNEL);

   // If intial switch state was determined to be OPEN, toggle the ACMP1 detection

  // to allow it to detect the CLOSED state

  if ((DETERMINE_INITIAL_SWITCH_STATE)&&(!SWITCH_CLOSED)) Toggle_Switch_State();

 

   // Set scan frequency

  LESENSE_ScanFreqSet(0, LESENSE_SCAN_FREQ);

 

   // Set clock divisor for LF clock

  LESENSE_ClkDivSet(lesenseClkLF, LESENSE_CLKDIV);

 

   // Set clock divisor for HF clock

  LESENSE_ClkDivSet(lesenseClkHF, lesenseClkDiv_1);

 

  //Enable interrupt in NVIC

  NVIC_EnableIRQ(LESENSE_IRQn);

   // Start continuous scan 

   LESENSE_ScanStart();

}

 /***************************************************************************//**

* @brief  Main function

******************************************************************************/

int main(void)

 /*Initialize DCDC for series 1 board*/

  EMU_DCDCInit_TypeDef dcdcInit = EMU_DCDCINIT_DEFAULT;

 

   /* Chip errata */

  CHIP_Init();

 

   // Init DCDC regulator with kit specific parameters

  if (USE_DCDC) EMU_DCDCInit(&dcdcInit);

   // Set SysTick Timer for 1 msec interrupts

  //if (SysTick_Config(CMU_ClockFreqGet(cmuClock_CORE)/1000))while(1);

 

     // Initialize Low Power Switch Detection

  if (ENABLE_LOW_POWER_SWITCH_DETECTION) {

    /* Initialize GPIO */

    initGPIO();

   /* Initialize ACMP */

    setupACMP();

    /*Initialize LESENSE*/

    setupLESENSE();

  } 

 

   while(1) {

    if (GO_TO_EM2) enter_EM2();  //put system into EM2 mode

  }

}

 

 

   /***************************************************************************//**

* @brief

*   LESENSE_IRQHandler

*

******************************************************************************/

void LESENSE_IRQHandler(void)

{

  /* Clear all LESENSE interrupt flag */

  LESENSE_IntClear((0x1UL << LESENSE_CHANNEL) | LESENSE_IFC_SCANCOMPLETE);

 

   Toggle_Switch_State();

   Led_Flash(0, 1);

}

 /***************************************************************************//**

* @brief

*   Toggle LESENSE ACMP1INV bit to invert ACMP result

*

*

******************************************************************************/

void Toggle_Switch_State()

{

  if (SWITCH_CLOSED)  { // case where ACMP1INV=1, SWITCH transitions from CLOSED to OPEN

    if (ENABLE_LOW_POWER_SWITCH_DETECTION ) LESENSE->PERCTRL &= ~(1 << _LESENSE_PERCTRL_ACMP1INV_SHIFT);

    SWITCH_CLOSED = false;

    //High=Closed, Low=Open

    if (DRIVE_STATE_GPIO) GPIO_PinOutClear(SWITCH_STATE_GPIO_PORT, SWITCH_STATE_GPIO_PIN);

  } else { // case where ACMP1INV=0, SWITCH transitions from OPEN to CLOSED

    if (ENABLE_LOW_POWER_SWITCH_DETECTION ) LESENSE->PERCTRL |= (1  << _LESENSE_PERCTRL_ACMP1INV_SHIFT);

    SWITCH_CLOSED = true;

    //High=Closed, Low=Open

    if (DRIVE_STATE_GPIO) GPIO_PinOutSet(SWITCH_STATE_GPIO_PORT, SWITCH_STATE_GPIO_PIN);

  }

}

 /***************************************************************************//**

* @brief   Disable high frequency clocks

******************************************************************************/

static void disableHFClocks(void)

{

  // Disable High Frequency Peripheral Clocks

  CMU_ClockEnable(cmuClock_HFPER, false);

  CMU_ClockEnable(cmuClock_USART0, false);

  CMU_ClockEnable(cmuClock_USART1, false);

  CMU_ClockEnable(cmuClock_TIMER0, false);

  CMU_ClockEnable(cmuClock_TIMER1, false);

  CMU_ClockEnable(cmuClock_CRYOTIMER, false);

  CMU_ClockEnable(cmuClock_ACMP0, false);

  CMU_ClockEnable(cmuClock_ACMP1, false);

  CMU_ClockEnable(cmuClock_IDAC0, false);

  CMU_ClockEnable(cmuClock_ADC0, false);

  CMU_ClockEnable(cmuClock_I2C0, false);

 

   // Disable High Frequency Bus Clocks

  CMU_ClockEnable(cmuClock_CRYPTO, false);

  CMU_ClockEnable(cmuClock_LDMA, false);

  CMU_ClockEnable(cmuClock_GPCRC, false);

  CMU_ClockEnable(cmuClock_GPIO, false);

  //CMU_ClockEnable(cmuClock_HFLE, false);

  CMU_ClockEnable(cmuClock_PRS, false);

}

 /***************************************************************************//**

* @brief   Disable low frequency clocks

******************************************************************************/

static void disableLFClocks(void)

{

  // Enable LFXO for Low Frequency Clock Disables

  //CMU_OscillatorEnable(cmuOsc_LFXO, true, true);

 

   // Disable Low Frequency A Peripheral Clocks

  // Note: LFA clock must be sourced before modifying peripheral clock enables

  //CMU_ClockSelectSet(cmuClock_LFA, LESENSE_CLK_SOURCE);

  CMU_ClockEnable(cmuClock_LETIMER0, false);

  CMU_ClockEnable(cmuClock_PCNT0, false);

  if (!ENABLE_LOW_POWER_SWITCH_DETECTION) CMU_ClockSelectSet(cmuClock_LFA, cmuSelect_Disabled); // Keep enabled

 

   // Disable Low Frequency B Peripheral Clocks

  // Note: LFB clock must be sourced before modifying peripheral clock enables

  CMU_ClockSelectSet(cmuClock_LFB, LESENSE_CLK_SOURCE);

  CMU_ClockEnable(cmuClock_LEUART0, false);

  CMU_ClockSelectSet(cmuClock_LFB, cmuSelect_Disabled);

 

   // Disable Low Frequency E Peripheral Clocks

  // Note: LFE clock must be sourced before modifying peripheral clock enables

  CMU_ClockSelectSet(cmuClock_LFE, LESENSE_CLK_SOURCE);

  CMU_ClockEnable(cmuClock_RTCC, false);

  CMU_ClockSelectSet(cmuClock_LFE, cmuSelect_Disabled);

 

   // Disable Low Frequency Oscillator

  //if ((!ENABLE_LOW_POWER_SWITCH_DETECTION)||(USE_ULFRCO)) CMU_OscillatorEnable(cmuOsc_LFXO, false, true);   // Keep enabled for use by LESENSE

}

 

 

  /***************************************************************************//**

* @brief

*   Enter EM2

*

******************************************************************************/

void enter_EM2()

{  // Make sure clocks are disabled.

  // Disable Low Frequency Clocks  if (EM2_DISABLE_LFCLKS) disableLFClocks();

 

   // Disable High Frequency Clocks

  if (EM2_DISABLE_HFCLKS) disableHFClocks();

 

   // Power down all RAM blocks except block 1

  if (EM2_POWER_DOWN_RAM) EMU_RamPowerDown(SRAM_BASE, 0);   // Enter EM2.  EMU_EnterEM2(true);

Close
Loading Results
Close