Ultra low-power and coin cells Dec 2016

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:

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:

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
rf-init rf-sleep

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.

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  rf-init rf-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.

Note that to get the µC out of this infinite loop, 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  rf-init rf-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:

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!

Weblog © Jean-Claude Wippler. Generated by Hugo.