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);