The Silicon Labs EFR32 family of IoT microcontrollers are very flexible and can do a ton of cool stuff. However, along with all that flexibility comes a lot of complexity. With that complexity are default settings that work fine for many applications but in some cases you want to dig into the details to come up with an optimal solution. In this post I’ll show how to speed up the wake up time for the Z-Wave ZGM130S chip from a GPIO.
But first – a caveat: This post applies to Z-Wave SDK 7.13.x. Future releases of the SDK may have different methods for sleep/wake and thus may require a different solution.
The Problem
Frequently Listening Routing Slaves (FLiRS) devices like door locks and many thermostats spend most of their time in Energy Mode 2 (EM2) to conserve battery power. Once per second they wake up briefly and listen for a Beam from an always-on device. If there is a beam, the FLiRS device will wakeup and receive the Z-Wave command. This allows battery powered devices to use very little power but still be able to respond to a Z-Wave command within one second. FLiRS devices use more battery power than fully sleeping devices like most sensors which use Hibernate Sleep mode (EM4). To wake every second the ZGM130 has to wake quickly and go right back to sleep to minimize power. The problem with EM4 is that it takes a few tens of milliseconds to wake up as the entire CPU and RAM have to be initialized as they were powered down to save power. For a FLiRS device, it’s more efficient to keep RAM powered but in a low-power state and resume quickly to go right back to sleep if there is no beam. Typically the ZGM130 can wake up in about 500 microseconds from EM2. But in many cases this is still too long of a time to stay awake if there are other interrupts such as UARTs or other sensors.

The scope shot above shows the processing that takes place by default on the ZGM130S. In this case I am using a WSTK to drive the SPI pins of another WSTK running the DoorLockKeyPad sample application. The chip is in EM2 at the start of the trace. When SPISEL signal goes low, the chip wakes up. But it is running on the HFRCO oscillator which is not accurate enough to run the radio but it is stable and usable in just a few microseconds. Thus, the SPI clock and data is captured in the USART using this clock. However, by default the Interrupt Service Routine is blocked waiting for the HFXO to stabilize. The 39MHz HFXO crystal oscillator has the accuracy required for the radio.
The question is what’s going on during this 500usec? The answer is the CPU is just waiting for the HFXO to stabilize. Can we use this time to do some other work? Fortunately, the answer is YES! The challenge is that it takes some understanding and some code which I’ll describe below.
The Solution
There are three functions that do the majority of the sleep processing. These are provided in source code so you can read the code but you should not change it. Instead you’ll provide a callback function to do your processing while the chip is waking up.
Simplified Sleep Processing Code:
- SLEEP_Sleep in sleep.c: The main function called to enter sleep
- CORE_ENTER_CRITICAL – PRIMASK=1 mask interrupts
- DO-WHILE loop
- Call enterEMx() – this is where the chip sleeps
- Call restoreCallback (return 0 to wake, 1 to sleep)
- Call EMU_Restore – waits for HFXO to be ready ~500us
- CORE_EXIT_CRITICAL – ISRs will now run
- enterEMx() in sleep.c:
- sleepCallback called
- Call EMU_EnterEM[1-4]
- wakeupCallback after returning from EMU_EnterEMx
- EMU_EnterEM2 in em_emu.c:
- Scales voltage down
- Call EMU_EM23PresleepHook()
- __WFI – Wait-For-Interrupt instruction – ZGM130 sleeps here
- Call EMU_EM23PostsleepHook() ~ 17usec after wakeup
- Voltage Scale restored which takes ~20us
The code is in sleep.c in the SDK which has a lot more detail but at a high level this is what you need to know. The important part to understand here is where the “hooks” are and how to use them.
- Use Sleep_initEx() to assign:
- sleepCallback – called just before sleeping
- restoreCallback – Return 0 to wake, 1 to sleep
- wakeupCallback – called after waking
- Sleep_initEx() input is a pointer to a structure with the three callbacks or NULL if not used
- Define the function:
- EMU_EM23PresleepHook()
- EMU_EM23PostsleepHook()
- These are both WEAK functions with nothing in them so if you define them then the compiler will install them
The two EMU_EM23* weak functions are run immediately before/after the Wait-For-Interrupt (WFI) instruction which is where the CPU sleeps. These are very low level functions and while you can use them I recommend using the callbacks from Sleep_initEx().
The SLEEP_initEx() function is the one we want to use and in particular the restoreCallback. The comments around the restoreCallback function talk about restoring the clocks but if the function returns a 0 the chip will wake up and if it returns a 1 then it will immediately go back to sleep which is what we want! You can use the other two hooks if you want but the restoreCallback is the key one since it will immediately put the chip back to sleep if everything is idle.
The key to using ANY of these function is that you CANNOT call ANY FreeRTOS functions! You cannot send any Z-Wave frames or call any Z-Wave function as they all require the RTOS. At this point in the wakeup processing the RTOS is not running! All you can do in these routines is to capture data and quickly decide if everything is idle and to go back to sleep. If there is more processing needed, then return 0 and wait for the event in the RTOS and process the data there. You also don’t want to spend too much time in these routines as it may interfere with the timing of the RTOS. A hundred microseconds is probably fine but longer you should wait for the HFXO.
In ApplicationInit() you will call Sleep_initEx() like this:
const SLEEP_Init_t sleepinit = {NULL, NULL, CheckSPI};
...
ZW_APPLICATION_STATUS ApplicationInit(EResetReason_t eResetReason) {
...
SLEEP_InitEx(&sleepinit); // call checkSPI() upon wakeup from EM2.
...
}
...
uint32_t CheckSPI(SLEEP_EnergyMode_t emode) {
uint32_t retval=0; // wake up by default
if (GPIO_IntGetEnabled() & 0x0000AAAA) { // Check SPI
GPIO_ODD_IRQHandler(); // service the GPIO interrupt
// wait for all the bytes to come in and compute checksum
NVIC->ICPR[0] = NVIC->ICPR[0]; //clear NVIC pending interrupts
if (!SPIDataError && !IsWakeupCausedByRtccTimeout()) {
retval=1; // go back to sleep!
}
}
return(retval); // 0=wakeup, 1=sleep
}
Recall that every second the FLiRS device has to check for a Z-Wave beam which is triggered by the RTCC timer. Thus the check for IsWakeupCausedByRtccTimer ensures that the beaming still works.

This scope shot shows the wake up processing of the ZGM130S:
- SPISEL_N SPI chip select signal goes low triggering a GPIO_ODD interrupt
- The chip wakes up, the HFRCO begins oscillating
- HFRCO begins oscillating in a few microseconds
- Once HFRCO is running, the peripherals are functional
- SPI data can begin shifting once the HFRCO is running
- The default HFRCO frequency is 19MHz but can be increased
- Higher frequencies for HFRCO also may need more wait states for the CPU and will use more power
- The WFI instruction that put the CPU to sleep is exited here
- EMU_EM23PostSleepHook function is called if defined
- After returning from PostSleepHook, the VSCALE is returned to full power which takes about 10usec
- It is best to wait for the voltage to be powered up to ensure all logic is running at optimal speeds
- EMU_EnterEM2 is exited and restoreCallback is called if initialized
- This is the function where the ISR should be called to process data
- If the data says things are idle and want to go back to sleep, return 1
- If more analysis is needed, then return 0
- Carefully clear the interrupt bits
- First clear the peripheral Interrupt Flags
- Then clear the NVIC Interrupt pending register
- NVIC->ICPR[n]=NVIC->ICPR[n] where n is 0-1 depending on your interrupt
- Make sure there aren’t other reasons to wake up fully
- !IsWakeupCausedByRtccTimeout() is the 1s FLiRS interrupt
- There may be other reasons to wake up which is application dependent
- In this example the SPI data is being fetched from the USART at each toggle of the GPIO
- The final toggle shows that the checksum was computed and the data is idle so go back to sleep
- The chip returns back to sleep in a few more microseconds
- Total processing time of this interrupt is less than 200usec which is a fraction of the time just waiting for the HFXO to stabilize
- Much of that time is receiving and processing the SPI data
- It is possible to sleep in under 50usec if the check for idle is quicker
If your peripheral processing will take significantly less than 500usec, then it may be more efficient to process the data using the HFRCO and not wait for the HFXO to power up. But if your application needs more processing, then you are probably better off waiting. Each application must make their own calculations to determine the most efficient path.
What About Sleeping Devices?
Fully sleeping devices (EM4 also known as RSS – Routing Sleeping Slaves) have entirely different wake/sleep processing. For sleeping slaves the processor and RAM have to be re-initialized and the chip essentially boots out of reset. All that initialization takes quite a bit of time – a few tens of milliseconds. If your device needs to do a lot of frequent checking of a sensor, then it might make more sense to force it to stay in EM2 by setting a Power Lock to PM_TYPE_PERIPHERAL. For more details on power locks see INS14259 section 7.6. Deciding which way to go is application specific so you have to make the calculations or measurements to find the right balance for your project.
This is a complex posting but I hope I’ve made it clear enough to enable you to optimize your application firmware. Let me know what you think by leaving a comment below.