This blog post is concerned with how we got the MCU in the Batteryless Energy Harvesting Remote Control to go into a Low Leakage Mode. It is also concerned with the Low Leakage Wake Up Unit (LLWU) and how this can be used to wake up the remote control from sleep.
In the Energy Harvesting Remote Control we used an MCU from the Freescale KL25 sub family of MCU’s have a number of different sleep modes that can be utilised for low power applications. We wanted to be able to enter into a stop mode to reduce power consumption after a degree of inactivity. We also wanted to be able to wake the MCU from a user input. The Low Leakage Stop (LLS) Modes and the Low Leakage Wake Up Unit (LLWU) are ideal for this application.
There are two modes we could have chosen to use the LLWU to wake the MCU up, Low Leakage Stop (LLS) mode or Very Low Leakage Stop (VLLS) mode (of which there are 3 options VLLS0, VLLS1 or VLLS3). The LLS mode leaves most of the peripherals in state retention mode and its normal recovery method is a Wakeup Interrupt. The VLLS mode disables most of the peripherals and its normal recovery method is a Wakeup Reset. See the Reference Manual for the MkL25Z series for more details.
We opted to put the MCU into the Low Leakage Stop Mode. The current in this mode is somewhere around 1.9uA. In VLLS0 mode this could have been reduced down to 381nA. We decided to use the LLS mode to avoid the reset, this would ultimately save time. We also wanted to wake the remote control using an Accelerometer, because the LLS mode keeps peripherals in a state of retention, this was ideal. The Low Leakage Stop Mode has a sufficiently low current to allow the remote control to charge up.
We entered into a LLS mode as shown in the code below:
void SMC_sleep(void) { //Enable the LLWU Pins before putting the MCU into a sleep state: PORTC->PCR[6] |= (0x1 << 8); FPTC->PDDR &= ~(0x1 << 6); PORTB->PCR[0] |= (0x1 << 8); FPTB->PDDR &= ~(0x1); LLWU_pinEnable(10,3); LLWU_pinEnable(5,3); NVIC_EnableIRQ(LLW_IRQn); SMC_sleepDeep(0); } void SMC_sleepDeep(int mode) { volatile unsigned int dummyread; /* The PMPROT register allows the MCU to enter the LLS modes.*/ SMC->PMPROT = SMC_PMPROT_ALLS_MASK; /* Set the STOPM field to 0b011 for LLS mode */ SMC->PMCTRL &= ~SMC_PMCTRL_STOPM_MASK; SMC->PMCTRL |= SMC_PMCTRL_STOPM(0x3); /*wait for write to complete to SMC before stopping core */ dummyread = SMC->PMCTRL; dummyread++; /* Set the SLEEPDEEP bit to enable deep sleep mode (STOP) */ SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; __wfi(); }
To wake the remote control up we added two options. One of the LLWU pins for the KL25z series is TSI channel 0. We configured this so a touch on this electrode would wake up the remote control. Another solution we also incorporated was to get the remote control to wake up from an Accelerometer. This was configured using one of the ADXL345 accelerometer interrupt pins and was used to wake the MCU up if any activity was detected. The code to enable the LLWU pin is shown below, we configured the LLWU to wake the MCU up if there was any change experienced on the pins, but they could also have been configured for rising or falling edge:
/*------------------------------------------------------------------------------ Enable a pin for use with the low leakage wake up unit *------------------------------------------------------------------------------*/ void LLWU_pinEnable(int pin, int mode) { if(pin < 4){ switch(mode){ case 0: LLWU->PE1 |= (0x0 << (pin*2)); break; case 1: LLWU->PE1 |= (0x1 << (pin*2)); break; case 2: LLWU->PE1 |= (0x2 << (pin*2)); break; case 3: LLWU->PE1 |= (0x3 << (pin*2)); break; } } else if((pin >= 4) && (pin < 8)){ switch(mode){ case 0: LLWU->PE2 |= (0x0 << ((pin-4)*2)); break; case 1: LLWU->PE2 |= (0x1 << ((pin-4)*2)); break; case 2: LLWU->PE2 |= (0x2 << ((pin-4)*2)); break; case 3: LLWU->PE2 |= (0x3 << ((pin-4)*2)); break; } } else if((pin >= 8) && (pin < 12)){ switch(mode){ case 0: LLWU->PE3 |= (0x0 << ((pin-8)*2)); break; case 1: LLWU->PE3 |= (0x1 << ((pin-8)*2)); break; case 2: LLWU->PE3 |= (0x2 << ((pin-8)*2)); break; case 3: LLWU->PE3 |= (0x3 << ((pin-8)*2)); break; } } else if((pin >= 12) && (pin < 16)){ switch(mode){ case 0: LLWU->PE4 |= (0x0 << ((pin-12)*2)); break; case 1: LLWU->PE4 |= (0x1 << ((pin-12)*2)); break; case 2: LLWU->PE4 |= (0x2 << ((pin-12)*2)); break; case 3: LLWU->PE4 |= (0x3 << ((pin-12)*2)); break; } } NVIC_EnableIRQ(LLW_IRQn); }
It was important to enable the interrupt in this section of code, this was done once the pin and mode were determined. The code called when the LLWU pins experience a change is shown below:
void LLW_IRQHandler() { LLWU_clear(); PMC->REGSC |= (1UL << 3); SystemCoreClockUpdate(); /* Get Core Clock Frequency */ SysTick_Config(SystemCoreClock/1000); /* Generate interrupt each 1 ms */ }
This is the code required to ensure that the MCU has fully woken from the stop mode.
The accelerometer we used was an ADXL345. The code below shows how we configured the interrupt for this accelerometer:
void sensor_int(void) { char cmd[1]; char interrupt=0; FPTC->PDOR |= (1UL << 11); Delay(500); I2C_configure(); setInterruptEnableControl(0x00); //disable all interrupts intially setActivityThreshold(150); //set the threshold for determining activity setActivityInactivityControl(0x70); //set activity to be detected on any axis setInterruptMappingControl(0xEF); //send interrupt for activity to interrupt pin 1 setInterruptEnableControl(0x7E); //enable interrupt for activity interrupt = getInterruptSource(); //get interrupt source for testing }
The Datasheet for this accelerometer shows that an interrupt can be triggered for a single tap, a double tap, activity, inactivity and free fall. In our case we configured the accelerometer interrupt simply for activity.