Skip to content

Latest commit

 

History

History
183 lines (141 loc) · 8.1 KB

README.md

File metadata and controls

183 lines (141 loc) · 8.1 KB

Raspberry Pi battery monitor

This project monitors the voltage and, indirectly, charge status of a 12 volt lead-acid battery, e.g. a car starter battery or a marine deep-cycle battery. The voltage readings are sent to a Raspberry Pi where you can plot them, set up alerts, send them over the network or do anything else you want. This sort of thing has been done many times before, but you might find my approach interesting.

The hardware

Because the Raspberry Pi doesn't have an analog-to-digital converter, we need some kind of external component to take voltage readings. Many people use dedicated SPI or I²C ADC chips for this. I elected to use an ATtiny13 AVR microcontroller because it's what I had in my parts bin. Here's a schematic:

battery monitor circuit schematic

The load is just whatever you're powering from the battery. In my case I'm powering the RPi itself from the battery, through a car-style USB power adapter. But that's not essential to the operation of the circuit, as long as all the grounds are tied together.

The positive terminal of the battery is connected to an analog input pin on the ATtiny13, through two resistors functioning as a voltage divider. I measured these resistors myself which is why they're slightly off of the standard values. You can use any pair of resistors in approximately the same ratio. Just be sure to adjust the R1 and R2 values in src/main.rs. However, if you make the resistors too small, the system will waste more power. At these values, the divider will consume about 20 μA, which isn't much to an 85 amp-hour battery!

The maximum analog reading on the ATtiny13 is equal to its supply voltage, 3.3 V here. So the maximum battery voltage we can measure is

3.3 V × (447 kΩ + 118 kΩ) / 118 kΩ

= 15.8 V.

Since the ADC reads 10 bits, the voltage resolution is 15.8 V / 1024 = 15.4 mV.

Here's the full setup:

Battery power setup with a large battery, USB power to a Raspberry Pi, and sensor hardware.

And a close-up of the sensor circuit:

A custom home-made circuit on perfboard, with a DIP-8 microcontroller, two resistors, and some wires held on with hot glue.

Parts list, including powering the Pi:

The software

The ATtiny13 sends an ADC sample to the RPi every 100 ms. There is no need for bidirectional communication, so I used just a single data line. However the ATtiny13's internal RC oscillator does not provide enough clock stability for asynchronous serial transmission. I solved this problem using a Manchester code, which allows the receiver to recover the sender's bit clock. Each bit transmission takes 1 ms, divided into two halves for the Manchester code. So the data rate is 1000 bps.

Each transmission consists of 17 bits: a start bit which is always 1, a 10-bit ADC sample, and a 6-bit "signature". The signature provides some ability to detect garbled transmissions, and also ensures that the signal includes the necessary waveforms for clock recovery. The ADC sample is sent with the least significant bit first. Between transmissions, the bus is at 0 V, a logic low state.

The software on the RPi side is in src/. Most of it is written in Rust, including the Manchester decoding and the computation of battery voltage. Low-level access to the Raspberry Pi GPIO pins is coded in C, based on a code sample from the eLinux wiki. Rust could do this as well, but the C code contains some gnarly macros and I didn't want the hassle of porting them to Rust.

For speed, I am using direct access to the memory-mapped GPIO registers, rather than the Linux sysfs interface. The era of PEEK and POKE lives on!

I've tested this on a Raspberry Pi 3, however it's likely to work on other models as well.

Build and install

The ATtiny13 code is very simple and can be found in the avr/ directory. To get the tools in Debian:

sudo apt-get install gcc-avr avr-libc avrdude

Then run ./build-and-upload.sh. The script is configured to use an AVRISP mkII programmer over USB. If you have a different programmer, change the -c argument to avrdude. If you're using a different AVR microcontroller, e.g. an Arduino Uno, you will need to edit the script as well as make changes to the code in avr/monitor.c.

For the RPi side of things, you can install the Rust toolchain on your RPi in the usual way. I've tested this with Raspbian jessie. You'll also need gcc, which is installed by default. The RPi is fairly slow; a clean build from scratch takes almost 40 seconds. I haven't tried cross-compiling from a more powerful system. Once you have built dependencies, a build takes only about 6 seconds.

After you've installed the tools, you can build the code by running

cargo build --release

in the root directory of this repository. You will get a binary at ./target/release/rpi-battery-monitor. Run it with no arguments to get a battery voltage reading.

This program uses two system features which require it to run as root:

  • Direct access to GPIO registers through /dev/mem
  • Realtime scheduler priority while reading data

Munin setup

rpi-battery-monitor can be used as a Munin plugin to get pretty graphs. The plugin must run as root, and the simplest way to accomplish this is by marking it setuid. This is a major security hazard because, although simple, this program has not been carefully audited for setuid safety. Thus I recommend you only do this on a single-user machine.

# Install the plugin.
sudo cp target/release/rpi-battery-monitor /usr/share/munin/plugins
sudo chown root:root /usr/share/munin/plugins/rpi-battery-monitor
sudo chmod u+s /usr/share/munin/plugins/rpi-battery-monitor
sudo ln -f -s /usr/share/munin/plugins/rpi-battery-monitor /etc/munin/plugins/battery_voltage

# Test run.
sudo munin-run battery_voltage
# Expect output like this: battery_voltage.value 12.197

# Restart Munin so it starts graphing.
sudo service munin-node restart

# Regenerate graphs immediately.
# If you get a lockfile error, try again in a minute.
sudo -u munin /usr/share/munin/munin-html --debug

Configuration

If you want to put the sensor on a different RPi GPIO pin, edit static const int pin in src/cbits.c. This uses the Broadcom GPIO numbering, not the RPi GPIO header pin numbering. You can find a helpful chart here. You will also need to change BCM2708_PERI_BASE to 0x20000000 if you're using a Raspberry Pi 1.

There are many other configuration variables documented in src/main.rs. This includes the values for the voltage divider and the input voltage to the ATtiny13.

Configuration for the Munin graphs is stored in src/munin.cfg. This file is baked into the binary and therefore you must rebuild after changing it.

Happy hacking!