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