LoRaWAN Battery Monitor - Part 1

Raspberry Pi-based remote wireless battery monitoring system

Matt Gilpin

Issue 61, August 2022

Build this Raspberry Pi-based remote wireless battery monitoring system to keep watch on your 12V battery system.

If you’ve ever had a small scale solar and battery setup, you’ll know that battery voltage monitoring can be quite useful. In essence, it allows you to roughly estimate how much energy is left in the battery at a given time. Ideally, you’d also measure the current flowing through the battery. While this project can be easily extended to measure current, I’ll only focus on voltage monitoring.

With a multimeter or even a more permanent voltmeter, checking the voltage is quite trivial, however, it might be beneficial to keep an eye on the battery's health remotely. Perhaps the battery is awkwardly situated or is quite far away from the desired monitoring location. A wireless battery monitor can be quite helpful in any case.

In my situation, I have a used lead acid battery in the garden that gets charged from a 40W solar panel during the day and powers some driveway lights at night. However, because it is an older second-hand battery, it doesn't have the best life. Therefore, having this monitor can aid in determining, among other things, the status of the battery and solar setup.

Numerous wireless communication options exist, including WiFi, Bluetooth, and even native 2.4GHz modules like the nRF24L01. However, I chose to use LoRaWAN in this project. I initially attempted utilising an ESP32 and transmitting the data over WiFi when I first started this project, which was a while ago. In many situations, WiFi or Bluetooth is a perfect solution for short-range applications. Unfortunately, since this battery system is situated on the edge of our wireless network, receiving data was, at best, intermittent. This effectively ruled out similar 2.4GHz choices like Bluetooth or other native 2.4GHz solutions. Although LoRaWAN is overkill in my situation, I was exploring it at the time and thought this project would be a great application to learn it on.

Two key terms are frequently used on the internet: LoRa and LoRaWAN. When you first start looking into them, you may think they are talking about the same thing, but that isn't quite the case. Despite both having "LoRa" in the name, the two technologies are in fact quite distinct. Cycleo (later acquired by Semtech) developed LoRa ('Lo'ng 'Ra'nge), a proprietary standardised modulation protocol that operates on the physical layer. It is based on a modified form of the chirp spread spectrum scheme, which facilitates the wireless data transmission. As each region has their own rules, LoRa typically operates on one of many bands, where the specific band you use will be a result of your location. In Australia, the "915MHz" or "AU915" band (which is technically 915-928Mhz) is used. Other noteworthy bands include: EU868, US915 (different to AU915 – 902 to 928Mhz) and IN865.

So what’s special about LoRa then? The combination of low frequency and low bandwidth allows us to transfer data (albeit not very much) over vast distances while using very little power, which can make it quite useful in a range of situations. Unfortunately, we are bound by the rules of physics, so if we wanted a higher bitrate, we would have to either raise frequency or increase bandwidth (decreases receiver sensitivity). Both of which have an adverse effect on the range. The diagram that demonstrates LoRa's position within the wireless ecosystem is shown here.

What’s really great about LoRa is that it has a high link budget, thanks to the low bandwidth and chirp spread spectrum. Although the precise link budget is dependent on a number of factors, the RFM95 modules we'll be using in this project have a link budget of up to 168dB. As part of the chirp spread spectrum, we additionally have this concept called "spreading factors," which are rather valuable when seeking to extract the greatest possible distance given the hardware and environment.

Spreading factors essentially regulates the modulation scheme's "chirp rate" - the speed at which the chirps change frequency. An example of the modulation technique can be seen here and will hopefully make it easier to understand.

We slice the signal based on time, and without delving into too much detail we can define a symbol (set of bits) for each slice based on the phase offset, or "steppings." Remember that this is an oversimplification. In light of this, the longer chirps (high spreading factor) have a lower data rate but they are easier to distinguish – the receiver can be more sensitive. This is the best option for weak signals (i.e., the transmitter is far away). However, if the two transceivers are nearby to one another, we can reduce the spreading factor to boost throughput and cut down on time spent on air.

LoRa is a shared band, therefore, there are restrictions on how much airtime we can use. Ideally, we want to spend as little of that time as possible while transmitting. You should check your local laws and regulations, but the typical airtime duty cycle is between 0.1 and 1 percent. If you follow TTN's fair use policy, which is typically within this range, you should be okay.

With that all said, LoRa uses 6 spreading factors, with SF7 being the lowest and SF12 being the highest (slowest throughput). With each increase in SF, the sweep and transmission rate are cut in half.

So, with all this information and provided that we have two LoRa compatible radios, we can send data between two transceivers (LoRa is bi-directional). This begs the question, what is LoRaWAN and why do we need it if we can already send data from one device to another? Well, that’s a great question, but to answer that, we first need to understand what LoRaWAN is.

The LoRa Alliance, a non-profit organisation, developed the open standard protocol known as LoRaWAN that operates at the Media Access Control (MAC) layer. This is a software-based layer that is directly placed on top of the LoRa modulation.

Having this layer of abstraction gives us the flexibility to define a standardised format for messages, and ultimately, allows us to manage/communicate with multiple end nodes to a single ‘gateway’. That way, we can make it more robust to interference as we can pseudo-randomly switch channels (within a frequency plan) on each transmission.

Benefits like data encryption and built-in security are also included with this standardisation. Another fantastic feature is over-the-air firmware updates, although this project will not explore them. The ecosystem, however, is arguably one of the most compelling features for makers. There are numerous LoRaWAN networks offered by a number of different providers, including "The Things Network" (TTN) which is free to use. You can check the TTN coverage map to see whether there is a LoRaWAN gateway nearby. If not, you can easily set up your own gateway and add it to the things network. One added benefit of networks like TTN is the availability of APIs and other services like MQTT that allow us to take the data and use it however we like.

While this all sounds great – and it mostly is, I need to address one point. LoRa at the end of the day is a proprietary standard, so naturally, there are other options that are available. While I won’t delve into any of these, it’s worth mentioning a few other options to LoRa. Among these are the open-source Dash7, NB-IOT, SigFox and LTE-M, to name a few. Each have their own benefits and drawbacks, but this is outside the scope of the project.

The project’s broad overview

In a nutshell, the core platform in this project that conducts measurements and communicates with the LoRa hardware is an Adafruit RP2040 Feather. The RP2040 was developed by the Raspberry Pi foundation and released at the beginning of 2021. It can be found on a number of boards from various suppliers, including the Raspberry Pi Pico from the foundation.

The RP2040 on our board includes an internal dual core Cortex M0+ processor clocked at 125MHz (down from 133MHz in the Pico), 264KB of built-in RAM, but no internal flash - external storage must be used instead. Thankfully, the feather has 8MB of external flash, and although it may initially seem excessive, the LoRaWAN protocol requires a little bit of extra power and memory. This is because we'll be utilising the RFM95 FeatherWing. This takes care of everything related to LoRa modulation and the physical layer but does not support the LoRaWAN specification on its own. Instead, it must be implemented in software.

Thankfully, a few libraries exist and have already done a lot of the groundwork for us. I utilised the pico-lorawan library for this project, which is available at:

We'll be connecting to The Things Network (TTN), as I alluded to before, as they provide a MQTT service that can be easily accessed in Node-RED running on a Raspberry Pi. MQTT is just a lightweight messaging protocol that uses a publish-subscribe philosophy. Using Node-RED, we can then quickly process, convert, and insert the data into an InfluxDB database. Finally, we’ll use Grafana to display this data as it’s a great and powerful web-based statistics viewing dashboard.

Returning to the hardware, the batteries I plan to monitor are approximately 12V, however, our RP2040 feather and RFM95 FeatherWing only operate on 3.3V (and 5V for the RP2040 feather). A DC to DC converter/regulator is required. We'll utilise the MPM3610 buck converter from Adafruit for this project. Although you could use a linear regulator, efficiency is what we're looking for here, and fortunately, there are a couple of great prebuilt solutions available.

Since the real time clock (RTC) will be powered continually, it’s important to use a regulator with a low quiescent current. In this project, the RTC's function is to wake the processor so that it can measure and transmit data over LoRaWAN. I have chosen to utilise the RV-3028 module by Core Electronics, particularly since it has a low current consumption of 45nA and employs a supercapacitor as a backup power supply.

As the RTC can’t actually switch power to the RP2040 and RFM95 Feather, we need to employ some MOSFETS (P-Channel and a N-Channel) to disconnect the power source. The concept is that the RTC sends an interrupt that turns on the MOSFETS which powers the RP2040. Once all the measurements have been sent over the air, the RP2040 then only needs to pull down (turn off) the N-channel MOSFET to turn the power off again.

Even in "low power sleep" mode, the RP2040 with the supplementary components on the Feathers, can draw a considerable amount of power. This is why the power is turned off from the whole board. This perhaps is less significant when measuring a large battery, as I am, but it is still something to think about, especially if you plan to use it with smaller batteries.

In regards to voltage measuring, we’ll just use a regular resistor divider. You may wish to use a P-channel MOSFET to detach the resistor divider from the battery in sleep mode if you are especially concerned with achieving the lowest sleep current possible. Although we are only saving a few hundred microamps at best with large values for the divider.

How it works

After giving you a high-level overview of the system, I’ll go into a bit more detail and talk about some project specifics. Feel free to skip ahead to the build, otherwise stick around.

Let's first take a closer look at the LoRa hardware. At the core of this project, you’ll find a HopeRF RFM95. This is perhaps one of the more iconic LoRa modules, and you’ll find them in a plethora of different LoRa and LoRaWAN projects. However, this isn't your only choice. The STM32WL5 series of chips, which contain an integrated LoRa headend in the same silicon, are among the new players that have entered the market.

These STM32s are typically incorporated into modules like the Seeed Studio E5 or the RAKwireless RAK3162. These are fantastic since they provide you access to a fully functional STM32 that you can program. Alternately, you can leave the chip's default firmware in place, which manages the LoRaWAN protocol and makes it accessible over UART/serial with the AT commands and eliminates a lot of boilerplate code. What’s more, is that they’re roughly the same price, and in some cases cheaper than the RFM95 boards. So this begs the question, why did I not just use one of these modules and call it a day? Well, I’m glad you asked.

One key benefit of using the RFM95 is that it is simple to solder and is quite user-friendly in that it can be connected up without the use of any special breakout boards - all you need to do is solder some headers to it and a length of wire for the antenna.

These other STM32-based modules are a little trickier to solder due to their finely pitched castellated edges. In addition, the RFM95 FeatherWing from Adafruit already has the RFM95 soldered, pairs well with the RP2040 feather and keeps everything in a single stackable block. Hence I've opted to use it to make everything just that little bit extra clean. Finally, given the present shortages, you'll probably have a better chance of finding an RFM95 if you wanted to recreate this project at home since they are readily available.

It’s for this same reason that I decided to go with the RP2040 Feather. Quite simply, the RP2040 based boards (at least at the time of writing) have been in great supply. Anyone that has recently searched for an STM32 chip would be aware of the difficulty in finding parts. The Raspberry Pi foundation just had fortunate timing and ordered adequate stock. Don’t get me wrong, the RP2040 is a fantastic microcontroller for the price, but it does lack some nice-to-have features including an onboard DAC, which we won’t need for this project anyway.

What we’ll be using however is its RAM and flash. It turns out that we require a sizable amount of memory because we'll be implementing the LoRaWAN protocol in software. The code we flash to the board itself is just a smidge over 350 kilobytes – we can’t fit that on just any microcontroller. Fortunately for us, the RP2040 feather has 8MB of flash.

Ultimately, we could have written our own library by looking up all the SPI commands and only write what we need. It might have reduced the size of the application. But LoRaWAN is quite a large protocol to implement and I’m not necessarily the most qualified to do so either. Fortunately, there’s a range of libraries that have already been written for us. As I mentioned earlier, we’ll be using one called “pico-lorawan” which essentially is a wrapper for Semtech’s LoRaMac-node implementation:

It’s worthy of mentioning here that the current layer 2 version is 1.0.4, with the regional parameters version 2-1.0.3. We’ll need this later when we set up TTN.

Next, let’s take a look at how all these different services work together. To aid with the understanding of how this is all linked, I have created a small diagram.

At the heart of it, we’re using ‘The Things Network’ (TTN) which provides access to our data over a few different channels. One of them is an authenticated MQTT server that can be connected to Node-RED with ease.

In essence, Node-RED is merely another layer in our stack that, in this case, pulls the raw ADC data we sent via LoRaWAN from MQTT and converts it into a format we can insert into InfluxDB. It's important to remember that we are providing raw ADC values, which in our instance range from 0 to 4095 (12bit ADC). This could be converted onboard, and the value could be transmitted wirelessly. However, in doing so, is more limiting and requires you to reflash the board every time you want to deploy it in a scenario with a different voltage level.

Additionally, in order to transmit the value over the radio, we must convert it to a hexadecimal format (naturally not a floating point integer). Theoretically, you could encode the value using the IEEE 754 single precision floating point scheme, but in doing so, would require 4 bytes of data versus the 2 we are currently using. Ideally, it’s best to keep it in the most compact and exact form for as long as possible – and only round when you have to. Technically, you could even extend this to the database and simply record the ADC values and convert them on the display. However, in order to keep things simple, I simply converted it in Node-RED and inserted the floating point value into InfluxDB.

I’ve decided to go with InfluxDB as it’s a time-series database (db), which means that everything revolves around a timestamp. A timestamp is automatically applied to each piece of data when it is added to the database and is used for indexing. As a result, it’s easy and effective to deal with periodic sensor data.

Although we could have used a standard database like MySQL or PostgreSQL, using InfluxDB allows us to neglect the timestamps and also provides us with a data retention policy. While in our case you might want to retain the data over time, but for applications where you record data more regularly and want to only store the most recent data, a retention policy can be handy in minimising disk usage.

Finally, to present this data, we’ll use Grafana due to its flexibility and feature rich dashboards. There are a few different options out there, however, I personally like Grafana’s interface more. Feel free to use something else if you wish. Having Grafana run on a local system is possibly one of its drawbacks (and this is true for any other locally hosted dashboard) as you can't easily access it from outside your local network.

Technically, you could run it on a little server in the cloud or set up port-forwarding if you're ready to accept the risks. There are alternative solutions if you wish to view the graphs outside your local network such as creating your own webpage, but these are out of the scope of this article.

The Prototype

The way I like to prototype is to break down the overall project into many subsections. After constructing each section on its own, I’ll test it in isolation – I occasionally test a couple of subsystems together as well. After validating each of these subsystems, I move on to the full-scale application.

In this project, there are essentially three parts: the LoRa hardware, power/RTC circuity and the measurement frontend. Despite this, I only prototyped the LoRa and RTC/power components because the measurement-related work is comparatively trivial once the rest of the board is functional.

Initially, when building this circuit, it was intended to turn on and off power to the board using a P-Channel MOSFET (PMOS) and to wake the device using interrupts from the RTC. The RV-3028's datasheet reveals that the Interrupt pin has an active low output and is coupled with a pull-up resistor on the module.

With this in mind, we know that we can connect the interrupt pin to MOSFET's gate directly – preferably with a current-limiting resistor connected in series. With that done, we need another MOSFET to hold the power on while the RP2040 does its stuff. We can simply use an N-channel MOSFET (NMOS) hooked up to a GPIO pin and have it pull down the gate of the PMOS – keeping it switched on. After adding some supplementary resistors, we get the following circuit.

After wiring this up on a breadboard we can see that by default the board remains off. The board, however, turns on when the P-channel MOSFET's gate is connected to ground. After some programming and configuration of the RTC it is time to test to see if we can wake up the RP2040 by itself.

This is where we find that it doesn’t quite work. Well, hold on a second, it worked when we manually triggered the MOSFETs – what gives? If we take a look on an oscilloscope, we do in fact see a pulse generated by the interrupt line on the RTC, but nothing on the GPIO pin for keeping the power on.

It seems as though the 7.8ms pulse that the interrupt line generates is too short for the RP2040 to boot and latch on to. Following further investigation, it did appear that we required a little longer time in order to latch the pin.

Although this is a minor issue, it can be resolved. What we need to do is add another resistor between the drain of the PMOS and the gate of the NMOS. In this manner, the moment power is applied to the board, it is also applied to the NMOS. All this means is that now, rather than letting go of the power to the GPIO pin to shut it off, we need to pull the pin low – essentially suffocating itself.

Given that the RP2040's maximum pin voltage is just above 3.3V, and that I have chosen to use a 10k resistor to pull the NMOS base low when not in use, we can use Ohm's law to determine the necessary resistance of this new resistor. Conveniently, this comes to 5.1k.

V = IR

3.3 = 5 (10 / 10 + R)

10 + R = 50 / 3.3

R = (50 / 3.3) - 10

R = 5.1kΩ

After adding in the resistor and adjusting the code, we find that the circuit can now periodically power and shut off itself. Great!

With that out of the way, it’s time to get a start on the LoRaWAN stuff. Essentially, we just need to be able to connect to the network and send some data over it. After downloading the library, I modified the code so that it would connect with my credentials and left everything as bog standard.

In an anticlimactic fashion, this also worked without too much hassle – It’s always worth spending time finding a good library as there’s nothing worse than spending hours on end trying to get a dodgy library working. If we decode the MAC payload (0x68656C6C6F20776F726C6421) then we get “Hello World!” which is what the example app sent. Awesome! With this all working, it is time to tie it all together in the build.

Matt Gilpin

Matt Gilpin

Engineering & Computer Science Student