Several years on a coin cell? Mar 2017
So far, the power consumption of the rotary encoder node has been optimised by taking the current draw from 5.0 mA to 45 µA - that’s an estimated coin cell battery life of 6 months.
Unfortunately, this is where diminishing returns start to kick in. To progress beyond this point requires rethinking the node’s logic. We need to put the µC into a really low-power mode, in such a way that it’ll still wake up every time that control dial is touched.
Another tricky aspect, is that once you start fiddling with clock rates and low-power sleep modes, it becomes very difficult to keep track of time. This makes it hard to determine when something happened.
After some contemplation, the following approach came to mind:
- use edge-triggered interrupts to capture rotary encoder changes and update the counter
- go to sleep for 100 milliseconds at a time, in an infinite loop
- send out a packet only when no edges were seen in the last sleep period, and EITHER the counter has changed OR 10s (100x 100 ms) have passed since the last transmission
This will avoid sending out packets in rapid-fire succession (as the current version still does), and will keep the µC in low-power mode while not missing a beat when pulses come in on the PA3 and PA5 GPIO pins. Each edge-triggered interrupt will wake up the µC.
Let’s first find out what we get by taking a node into repetitive 100 ms low-power “stop” mode:
begin stop100ms again ;
The result: 9 .. 11 µA (it varies a bit, probably some external effects).
Getting the quadrature pulse decoding working again requires implementing “edge-triggered interrupts”, as described earlier.
Time to try it out - the code is in jz4/ex/rot7.fs on GitHub. This is a first test, which sends the counter value every 10 seconds, and goes into low-power stop mode in between:
: read-enc [...] led-off 2.1MHz only-msi 1000 systick-hz lptim-init count-pulses begin 7 <pkt counter @ +pkt pkt>rf rf-sleep stop10s again ;
Those 10 seconds in stop mode cause the display to update slowly, but do allow us to check the sleep current reading: it’s around 8 µA - while still responding to the rotary encoder pulses!
Note that this is a bit lower than the
stop100ms loop above, because
start/stop configuration takes a bit of processing power, and those cycles will
also eat up some energy.
The final version of this “Rotary Encoder Node” is in jz4/ex/rot8.fs on GitHub. It draws about 9 µA when idle, captures all control knob changes, sends packets out at most 10 times per second, with a heartbeat send of the counter value every 10 seconds even if it hasn’t changed.
The main loop now looks like this:
0 \ keep previous counter value on the stack begin idle @ if counter @ tuck <> if-send \ send a packet if counter changed then 1 idle +! idle @ 100 mod 0= if-send \ send a heartbeat packet every 10 s stop100ms again
The average current could be reduced further by using longer sleep cycles, since this code still wakes up 10 times per second. This will make the main loop a bit more complicated, however.
Look ma, no hands! (yes… OLEDs always come out strange with short flash shutter times)
Many refinements are possible, for example: including the node’s hardware ID in the payload to support multiple nodes running at the same time, and adding the estimated battery voltage to give an indication of when it might be time to replace the coin cell.
But all in all, this has accomplished its goal: a JeeNode Zero which needs no on-off switch because it’s expected to run well over 2 years before its coin cell needs to be replaced. Not bad for under 70 lines of Forth code (and of course several more from the generic “flib“ library).