My shed lacks mains electricity and during the winter it lacks light as well. To fix this I built some battery powered lights, powered by a 4-cell LiPo pack and controlled by a PIC 16F690.

I used the project as an excuse to play with low-power design. The final version has an average current consumption of about 35µA but I think this could be halved without much difficulty.

All the design files are available on GitHub.1

Basic desiderata

I wanted a controller to:

There are couple of potential power sinks: the light might be left on accidentally, or the controller might consume lots of power itself. Neither of these would be acceptable.

I usually go to the shed to get something, and don’t spend long periods of time there. Accordingly it seems sensible to provide a button to turn the lights on, then turn them off automatically after a few minutes.

It’s helpful to put some numbers on this. Suppose that when the light is triggered it stays on for 2 minutes, and this happens five times per week. Assume too that the light draws about 1A. Thus, the average power draw will be about

\[ \begin{align} I_{ave} &= \frac{1\textrm{A} \times 2 \times 5}{60 \times 24 \times 7}, \\ &\approx 1\textrm{mA}. \end{align} \]

The battery claims a capacity of 2500mAh so we might see about one hundred days between charges: about three months. This seems reasonable to me. As a ballpark target, it would be nice to get the average power consumption of the controller down to about 50µA, so that 95% of the current drives the light.

I think the most sensible implementation would involve a handful of FETs, diodes, and passive components. The Art of Electronics has a suitable two-FET switch circuit which draws essentially no power when off, but lacks over-discharge protection for the battery. I suspect it could be added though.

A spurious desideratum

The main problem with the FET solution isn’t that it lacks battery protection: rather I wanted to do this project to get some experience of low-power microcontroller design, and the FET circuit lacks any kind of CPU!

I had some Microchip PIC16F690s lying around, and their datasheet has long boasted of nanoWatt performance so I added this to my desiderata:

Now, it should be said that Microchip now make XLP3 devices which promise to be even more frugal. You might well be able to do as well, or better, with an AVR chip. More interestingly, perhaps you could do it with an ARM as well.

Further, although I am keen to do a reasonable job with the controller, my main goal is to gain insight into the sorts of trade offs involved, rather than heroically optimizing this particular case.

LiPo notes

Internally, the LiPo pack consists of four cells in series. You can get the full voltage via the high-current leads, but you can often get the inter-cell voltages as well on the balance connector.4 High and low current here are relative: over 40A is available on the main output leads, the balance connector will easily provide the amp or so we need.

Voltage-wise, a fully changed LiPo cell generates about 4.2V which falls slowly to a ‘nominal’ 3.7V, and then 3.5V. Below 3.5V the voltage drops rapidly: discharging beyond 3.0V is generally regarded as dangerous.

We want the controller to flag voltages below 3.5V or above 4.2V as an error, and voltages between 3.5V and 3.7V as a warning that the battery will need to be charged soon.

This means that the useful full-pack voltage ranges from 14V to 16.8V, and the single-cell voltage from 3.5V to 4.2V. This latter range is ideal for powering the PIC directly.

One caveat: by drawing the PIC current from just one cell, we will slightly unbalance the battery. Hopefully this won’t be significant though.

Basic design

The main constraint relates to power, so it’s likely that the broad features of our design will be determined by power issues. Let’s get quantitative!

The PIC16F690 data sheet quotes the following as typical current consumption in selected different CPU modes:

ClockTypical current / µA @ VddCharge per
SourceFreq., f3V5VMean, Itick / nC
Power Down-0.150.350.25-
32kHz T1OSC-

Given that we’d like to see an average current consumption of about 50µA, just using a slow clock seems unlikely to be enough: the CPU alone will use over half the current budget.

On the other hand, if the chip is fully asleep it draws about 0.4µA which is wonderful if unrealistic. In practice we’ll probably want some sort of slow timer to wake us from sleep to check the battery voltages. A 32kHz watch crystal would be a good solution: this would raise our sleep current to about 3µA or 6% of the total budget.

Having considered sleep, let’s think about being awake. Although higher clock-speeds draw more current, they also do things faster, and as the table shows the speed increases faster than the current. So, for a fixed amount of work it is better to run fast then sleep: the hare wins. In rough terms, we’d expect the system to draw about 1mA when busy, so we’ll need to sleep for about 95% of the time to get our average consumption down to 50µA.

Clock choice

Given the discussion above this seems reasonably clear:

Light control

Before we go any further, we should consider the primary task: letting me control some lights. The most obvious approach is:

The push button is connected to RB6 (pin 11). Changes to this pin are configured to trigger an interrupt, which wakes the device from sleep.

Instead of a fixed delay it might be better to use a PIR detector to turn off the lights after a period of inactivity. I left this refinement for a future version.

Actually controlling the lights is easy: just use a chunky power MOSFET. The most important parameter is the threshold voltage: the device must be fully turned-on at 3V. I used a STP80NF03L-045 from ST, driven by RC0 (pin 16).

Battery voltage sense

Conceptually sensing the four voltages from the battery is simple: after all the PIC has ADCs. In practice though this is where most of the work was needed.

The voltage across the whole battery ranges from about 12–17V, so a full-scale range of about 20V seems appropriate. The ADC has 10-bit resolution, so the voltage quantum is about 20mV. Each cell has a useful voltage range of about 700mV so our effective resolution is about 5-bits: that seems reasonable.

Using the same input range for all four inputs makes the software easier because we can simply substract the voltages without scaling them.

So far so good. However, the PIC’s ADC needs a source impedence of less than about 10kΩ which implies a current draw of about 500µA at 5V. That’s much too large for our power budget if it’s drawn continuously, so we’ll need a bit more than a simple potential divider. There are two obvious topologies:

I adopted the latter approach, because it reduces the sense current to almost zero when not in use. The switches were made by a couple of MOSFETs, which also multiplex the four voltages onto a single analogue input (specifically AN2 on pin 17).

Some points of note:

ADC voltage reference

In the PIC, the ADC’s voltage reference is Vdd, which makes it hard to measure the battery voltage in absolute terms. There is an internal 0.6V reference which can be measured, and thus Vdd inferred, but it seemed simpler to use an external 2.5V reference.

I wasn’t entirely sure how much current the ADC needs: the datasheet talks about an initial 10–1000µA, followed by a maximum of 50µA during conversions. These are large enough that the source must be turned off when not needed: this is accomplished by driving it from RC3 (pin 7).

I used a LM385-2.58 reference with a 3k3Ω current limiting resistor which will deliver 300-500µA depending on battery voltage. Given that this is below the 1mA figure in the data sheet, I introduced a delay between applying power to the reference and trusting the ADC readings.

Irritatingly the external reference must be supplied on pin 18 (RA1 et al.) which is also needed for the ICSP port. Provision must be made to disconnect the reference during programming.

With the 22k/3k3 potential divider, the 2.5V reference gives a full-scale reading of about 19.2V.

An empirical interlude

The yellow scope trace below shows the voltage on AN2 during the monitoring process, whilst the green trace shows when the voltage reference is powered.

Debug support

There’s enough complexity in the analogue side to warrant a proper diagnostic channel. Happily the PIC has a full UART which can dump data to a serial port on the host computer. These days, that’s usually via a FTDI serial to USB converter.

Serial data are sent to pin 10 (RB7 et al.).

Status LED

Although the UART is a great way to send data during development, when deployed in the shed a simpler indication is needed. I added a high-efficiency RGB LED to show battery status. In comparison to the 20mA old-fashioned LEDs typically consumer, experiment shows that 300-400µA is enough to make the LED shine brightly, but we’ll still have to flash it to keep within the power budget.

Unused pins

All unused pins were configured as digital outputs and driven high.

Incidentally, the bare PIC with digital pins left floating draws 100-140µA when asleep. Over time the current falls, but rises again at the slightest disturbance.


The prototype was constructed on a Microchip low-pin count demo board in a mixture of through-hole and SMD construction. Good MOSFETs seemed only available in SMD packages.

A combination of laziness and lack of space led to the omission of any input protection circuitry.

You can see a full schematic below, but you might prefer a PDF.9

The source for the firware and the KiCad schematic are available from GitHub.10


The firmware was simple enough to write easily in assembler (the final code has about 260 instructions), and the task was made still easier by using Charles McManis’ 16-bit arithmetic library.11 I suspect using assembler did bias me against more numerically fiddly solutions e.g. those which scaled the different voltage readings. More positively, using assembler made it easy to see exactly what was happening at every stage.

The main program loop is a infinite sleep loop: all the interesting behaviour is interrupt driven.

Input change interrupt

RB6 is connected to a push button. If it’s pressed an interrupt is generated which:

No effort is made to debounce the switch, though it would be easy to add it.

Timer 1 interrupt

Timer 1 fires at 2Hz, maintaining a clock which is used to extinguish the light at an appropriate time.

Every second tick i.e. at one second intervals, it also:

It would be better if the timer fired at 1Hz and did the same thing every tick. That it doesn’t is an accident of history.

ADC interrupt

Most of the interesting code is driven from the ADC interrupt.

We cycle through the four channels calculating the voltage across each cell and warning on voltages below 3.5V or over 4.2V. All the arithmetic is unsigned, so if a cell voltage is negative e.g. because a connection is broken, it will appear as a (very) large positive voltage and be flagged as a problem.

When all the readings have been taken the ADC is disabled until Timer 1 restarts it.


All the key performance measures are current related.

The plot below shows the current consumption over a four-second period when Vdd was 3.85V. The current is measured in the ground lead of the battery using a µCurrent12 powered by 3 x AAA batteries to give a maxiumum reading of about 2.1mA. The vertical scale on the scope plots is 500µA per division.

The current bumps are both narrow and (foolishly) aligned with the graticule. Sadly this makes them hard to see!

Recall that most of the time the CPU is sleeping, and the current consumption during these periods is lost in the noise. However, more careful measurements show that it’s about 3µA.

The CPU is woken every half-second by Timer 1, and these brief wakeful moments correspond to the bumps on the graph.

The small bump

The small bump is about 45µs wide and 530µA high: this corresponds to the interrupt handler incrementing the clock but doing little else.

We can conclude:

The big bump

During the big bump the PIC checks the battery voltages and flashes the status LED. In other words, this is where the fun and interesting things happen, and if we zoom in on the bump we can see an interesting current trace.

There are eight regions in the graph:

You’ll see that these regions match the graph of voltage on AN2 shown above.


We can zoom in on the first gap:

During this time the PIC is making repeated ADC measurements but discarding the results. More accurately the PIC:

The ADC is driven by the dedicated internal RC oscillator which has a period of about 4µs and conversions take 11 ticks: 44µs. The interrupt handler takes about 15 instructions, plus a delay equivalent to three more, so that’s 18µs.

Adding these together implies a period of 62µs which corresponds to a frequency of 16.2kHz: close enough to the measured 16.95kHz that we’re confident that we’re seeing the processor’s sleep-wake cycle.

We can now interpret the amplitude of the oscillation as the current consumed by the CPU when awake (taking the sleep current as zero): 524µA. Happily this is consistent with the value inferred from the small bump.

Further we can interpret the current whilst sleeping, 762µA, as the consumption from the non-CPU components:

Note that the average current drawn during this phase is 1.102mA. Ignoring the 720µA used by the LED and voltage reference, this leaves 382µA as the average current drawn by the PIC: about 72% of the peak value.


Let’s turn now to the four sense periods. To a good approximation the current drawn during these periods follows the same pattern as above, but is higher because extra current flows through the battery voltage sense circuitry.

Here’s the current during the first sense period:

The only complication is that the current to voltage converter saturates during the last period:

Happily we can correct for this because the ‘Base’ voltage on the plot is measured correctly, so we can assume the average CPU current and calculate the average total draw.

CellsCurrent / µA
BaseSenseAmplitudeAve. totalAve. CPU
4 (corrected)1703941-2032329

At an earlier stage of development (and battery charge), I measured the current drawn by the sense circuitry directly, and the results broadly agree.

CellsCurrent / µA
From aboveTotalBiasTrue sense

We can go further and divide the current into that used to bias the p-channel MOSFET and that which is fed to the ADC. By apportioning the measured currents from the scope in the ratio above, we can estimate an average currents: 297µA of bias and 388µA of sense.


It seems helpful to collect the results above by functional block rather than time:

ConsumerCurrent / µATime active / msAverage current / µA
PIC sleeping39822.9
PIC running382186.9
Voltage reference390187.0
Status LED330185.9
Monitor sense38812.85.0
Monitor bias29712.83.8

As a final check, asking the scope to average the current gives a 32.8µA after aboout 600 cycles. However, the current consumption is very spikey so I am not sure if this measurement is reliable.


I wanted to build this to solve a couple of problems: a dark shed, and ignorance of low-power gadgets. The design above gives an average current consumption of about 32µA which easily meets the 50µA goal.

I have avoided putting error bounds on the figures above, mainly because most of the figures are correlated. However the average consumption is dominated by the draw during main current bump, which is about 1.8mA with an error surely lower than 0.2mA. So the error in the average consumption is likely to be better than ±3.2µA.

Although I did not try to reduce the current consumption any further, I think 15µA is within easy reach:

In both analogue cases above, I think there’s a trade-off: higher currents stabilize more quickly, but I suspect that it’s better to reduce the current and wait a bit.

Such improvements are for a future release though.

Although I think it is quite surprising how much you can infer from measuring the total current draw (because of the variation over time), were I building the device now, I’d also include more jumpers so I could measure the current at various places directly.

Finally, besides gaining some insight into low-power microcontroller design, I can now get things from my shed at night without needing a torch. Very handy: especially now that the nights are getting lighter!