Let’s explore the current consumption of a JeeNode Zero a bit and see how we can reduce it. The following measurements were all made with a low-cost Voltcraft VC170-1 multimeter, in series with the +5V power feed from SerPlus to JNZ - using the 400 mA range for now:
- power consumption, running just the Mecrisp core: 4.75 mA
Once we load the always.fs
, board.fs
, and core.fs
code in flash, and reset
the board, power consumption will go up, because init
in board.fs
turns the
on-board LED on:
- Mecrisp core, including on-board LED: 6.40 mA
The RFM69 powers up in standby mode, so let’s put it to sleep, and turn off that LED:
led-off \ toggle the LED to turn it off
rf69-init rf69-sleep
- LED off, RF69 in sleep mode: 3.29 mA
So this is essentially the L052 µC, running at 16 MHz - it’s running in an idle loop, with a periodic “system tick” interrupt triggered once every millisecond.
We can lower the power consumption by putting the µC processor to sleep between interrupts, as it’s not doing anything useful. Since it wakes up every time, we need to do this in a loop:
: coma begin sleep key? until ; coma
The sleep
word is defined in multi.fs
, it uses the ARM’s “WFI” instruction.
- LED off, RF69 in sleep mode, µC also in sleep: 1.38 mA
Not bad. We’re still in a context which is very responsive: the µC is running at 16 MHz, and instantly responds to every interrupt. If we were to make this the default interactive mode, we’d probably hardly notice the difference. A powerful 32-bit µC, running at less than 2 mA!
The next changes are more invasive. We can take the system clock down, but it’ll become harder to keep a full interactive session going, because it won’t be able to handle the UART communication at 115,200 baud anymore.
Let’s start by blinking the LED briefly every 10 seconds, to verify that the board is still alive:
: blips
led-off rf69-init rf69-sleep \ led off, radio sleep
2.1MHz 1000 systick-hz \ slow down the clock, adjust systick accordingly
begin
led-on sleep led-off \ a very short 1ms LED blip, but still visible
9999 0 do sleep loop \ count 9999 additional 1ms ticks, doing nothing
again ;
blips
The code has to be inside a word definition now, because it contains a loop.
Since the only source of interrupts is the systick timer running at 1000 Hz, we
can simply count off those interrupts: each call to sleep
will keep the µC in
sleep mode until the next interrupt occurs.
The 2.1MHz clock is less accurate, and is in fact just one of the many settings of the built-in MSI (medium-speed internal) clock, which can run from 65 KHz to 4.2 MHz, in powers of 2.
- LED off, RF69 and µC in sleep, running at 2.1 MHz: 0.43 mA
Note that to get the µC out of this state, we’ll need to hit CTRL-C to reset it.
You can actually predict the µC’s current consumption at lower clock rates: as we’ve seen, it draws 3.29 mA at 16 MHz (when in run mode, without WFI sleep). That’s ≈ 0.21 mA/MHz. And guess what: at 2.1 MHz, that’s … 0.43 mA - exactly as measured!
So now we have a power consumption of about 430 µA, at the cost of no longer being able to communicate with it at 115,200 baud. A lower rate could still be used, but it’s not really useful to go there since we’d lose the ability again when chasing even lower power modes anyway.
Does this mean we can go lower still? Oh, sure …
The next step is to actually stop the µC and most of its clock-based peripherals altogether. Power consumption is very much determined by the number of gates toggling, so what we need to do is switch most of the µC’s internal clocks off. Switching them off completely would be a problem though, because then the µC loses all sense of time and wouldn’t be able to come back without external pin change. Note that this also disables USARTs, SPI, I2C, and timers.
Fortunately, the STM32L series has a special low-power timer. On the L052, it’s driven by an ultra low-power (and even less accurate) internal clock, running at approximately 37 KHz.
The driver code for these very low-power modes is in flib/stm32l0/sleep.fs– including a very convenient wrapper for stopping the µC for 100 ms, 1 s, or 10 s – so let’s rewrite the code a bit:
: lp-blips
led-off rf69-init rf69-sleep \ led off, radio sleep
2.1MHz 1000 systick-hz \ slow down the clock, adjust systick accordingly
lptim-init \ initialise the low-power timer
begin
led-on sleep led-off \ a very short 1ms LED blip, but still visible
stop10s \ enter stop mode for approx 10 seconds
again ;
lp-blips
Sure enough, the LED is still blinking ever so briefly once every 10 seconds. And the power reduction is dramatic - we need to switch the multimeter to its 400 µA range to measure it:
- LED off, RF69 sleep, µC in stop mode, waking up at 2.1 MHz: 6.5 µA
The trick here, and the reason the µC was set to use the 2.1 MHz clock, is that this is how stop mode normally terminates. In other words: when coming out of stop mode, the µC defaults to resuming with its MSI clock running at 2.1 MHz, so the systick rate of 1000 Hz will be correct.
From here on, reducing power draw becomes a lot harder. We’ve shut down all major power consumers - many smaller factors remain, but they’re not always obvious or easy to eliminate.
Let’s go after one such source of current leakage for now: after reset all the I/O pins are set to input mode with a weak pull-up resistor. This is not the optimal setting during stop mode. We can set the pins to “input-ADC” mode to disable as much of the internal circuitry as possible:
: highz-gpio
IMODE-ADC PA0 io-mode!
IMODE-ADC PA1 io-mode!
IMODE-ADC PA2 io-mode!
IMODE-ADC PA3 io-mode!
IMODE-ADC PA4 io-mode!
IMODE-ADC PA5 io-mode!
IMODE-ADC PA6 io-mode!
IMODE-ADC PA7 io-mode!
IMODE-ADC PA8 io-mode!
IMODE-ADC PA9 io-mode!
IMODE-ADC PA10 io-mode!
IMODE-ADC PA11 io-mode!
IMODE-ADC PA12 io-mode!
IMODE-ADC PA13 io-mode!
IMODE-ADC PA14 io-mode!
\ IMODE-ADC PA15 io-mode! \ SSEL
IMODE-ADC PB0 io-mode!
IMODE-ADC PB1 io-mode!
IMODE-ADC PB3 io-mode!
IMODE-ADC PB4 io-mode!
\ IMODE-ADC PB5 io-mode! \ LED
IMODE-ADC PB6 io-mode!
IMODE-ADC PB7 io-mode!
IMODE-ADC PC14 io-mode!
IMODE-ADC PC15 io-mode! ;
With these ultra-low current levels, it’s very easy to run afoul on some unexpected side effects. In this example we obviously have to keep the LED pin enabled so it can still toggle, but we must also keep the radio select pull-up enabled to prevent the pin from floating in stop mode.
The improvement is fairly substantial - as you can see on the multimeter display below:
Getting a complete circuit down to ultra low-power consumption levels can be a fairly long and winding road. It requires identifying all the weak spots. App Note 4445 shows what’s possible:
Note that, while Standby mode looks tempting, it can only wake up with a reset. Stop mode is much more convenient, because the µC can resume where it left off - with registers and RAM intact. For completeness: on a JNZ, the regulator adds 0.5 µA, and the radio another 0.1 µA.
But hey, we’re already drawing less than the original JeeNode v6. With a 230 mAh coin cell and ignoring self-discharge plus the power needed to drive the radio, the estimated lifetime would be over 9 years. Even if the average ends up quite a bit higher, that’s not a bad start!