Qball's Weblog

Low power with atmega328pb and NRF24L01+

Tags atmega328pb  nrf24  Sensors  Low Power 

Recently at work I had to do some experiments with the classic nrf24l01 modules. These are simple transceiver modules allowing you to send/receive small messages. Even thought I found two in my collection, I never really played with these modules.

So I decided to rebuild my esp32c3 ESPNow sensor nodes with this as a test. So from a local electronics shop I ordered a few more modules and some arduino pro mini and build up a setup. This allowed me to compare this more ‘classical’ setup (Atmel + nrf24) with the esp32c3 setup. I then decided (for reason explained below) to design my own small PCB and use some of these sensors around the house.

IDLE Usage

The idle result was kind of what I expected, with the esp32c3 I managed to get a power down usage of ~7µA, with the atmega328pb and also some of the minimal parts around it I got around ~8µA and ~9-12µA with the NRF24 included. To get this result I did have to modify these boards to remove power leds, inefficient LDOs and more.

These measurements was what made me decide to make my own custom board, just like I made for the esp32c3. Effectively they gave the same result. What I did find out that identical PCBs with atmega parts from same batch tended to give slightly different values. Something I did not notice with the esp32c3. I ordered the atmegas from mouser, so I assume I got genuine modules and not fakes. This is something I might come back to investigate later.

Two measurements:



There is one big difference between the ‘power down’ of the esp and the atmega. The atmega can only sleep for short periods if you want to be woken up by the internal timer, but you continue executing where you went to sleep in the code. The esp32c3 can power down for much longer, but on wake-up the module resets. This in the end makes a significant difference.

PEAK Usage

The biggest problem I’ve had with the esp32c3 was the very significant current spike when transmitting, this easily went over 340mA tipping to 380mA. This limits what you can use as power source and basically required low esr battery. I used li-ion or LiFePo4 batteries. A smaller battery would be ideal, and I have many in stock. With the NRF24 we peaked around ~30mA, opening up this possibility.

This combined with the different power down mode is where we get the biggest difference. This gives the following advantage to the Atmel:

All this resulted in a very active run-time, mostly determined by the time it takes the sensor to sample and to get a clean battery voltage reading using the internal ADC (more on this later). With the esp32c3 I managed to get down to 190ms for waking up, sending a message and then back to sleeping. With the Atmel I can also take the sensor reading (and going in powerdown during this time) in the same time. Effectively the waketime of the Atmel is less than half that of the esp32c3.

Everything combined makes it that sending data has very little impact on average power consumption. For bme280 this results in an average power consumption of ~14µA while sending a reading every ~90 seconds. Each sensor has a slightly different sleep time.

The biggest thing I was happy with, the lower peaks makes it possible to use smaller batteries. I have a whole bag (~80 pieces) full of 14250 (1/2 AA ) Li-SOCI2 1200mAh batteries and matching holders. It is nice to be able to use these instead of throwing them away.

NRF24 vs ESPNow

While on paper they look very similar:

I feel the ESPNow has some advantages:

I have tried to use NRF24 modules with an extra LNA, putting it closer to the same power level as the esp32c3. My results with this was not very good, some of my modules with the LNA would just fail most of the time on higher output level. Even if I made sure, and confirmed, that the voltage was not dipping, it would throw failures on higher power levels. In the end I achieved significant further distances with ESPNow on esp32c3 modules and sensors with the esp worked where the NRF24 failed.

I might order from a different store modules with LNA to see if I can repeat this experiment, in theory it should give me slightly longer range.

Sensor PCB

I designed the following PCB where you can solder in a normal NRF24 module:


Because the NRF24 max voltage is 3.6V but my batteries start at 3.8-4V I decided to include a LDO on this board. It has a 16MHz crystal (this is slightly out of spec at 3.3V, but some experiments showed it worked fine so decided to keep this). To read the battery voltage I included a 2 times 3MOhm resistor voltage divider. In hindsight I should have added a small mosfet so I could ‘switch this off’ when not using, as I already to the parts for this and wanted to try this out, but forgot. Its very pretty small, it has an ldo, notification led, 16MHz crystal, reset button, nrf24 header, i2c sensor header and serial port for programming. There are no components on the back for easy soldering.

The boards where produced at aisler. I highly recommend them, they are located in the EU and offer competitive prices. In my experience the quality of the PCBs are better then what I got from JLCPCB, especially when having to remove/rework components. I’ve made this PCB to solder on a cheap hotplate I recently got, so it double ups as practice for that.

Pre-solder pcb paste

Soldering pcb-soldering

Done. After some practicing, no rework was needed to get it to work. pcb-done

Arduino bootloader

I flash each pcb after soldering, but before mounting the NRF24, with the arduino bootloader. The required pins are on the nrf24 header and a small rig with an arduino that auto-flashes and some pogo-pins makes this a trivial job. Just clip on the reset wire, push it on the pogo pins and it is done. Initially I tried to flash the board via the arduino IDE, however it does not (yet) support this atmega328pb chip id (this can run 328p code). After loading the bootloader the serial port on its header can be used to program it from the arduino IDE, the DTR line has a capacitor in series to the reset line allowing the IDE to reset it in bootloader and program it. I have used this sketch to do the flashing.

Arduino frustrations

I have not ‘dabbled’ with arduino for while after getting frustrated that many libraries where either broken or very inefficient. Again I ran into this issue, so at the moment my code is a random mix of C(++), assembly, arduino libraries and direct register writing/reading of sensors. Regrettable this, with a lot of if/defs for enabling different sensors (lm75,sht31/bme280/etc.) makes that its not very publishable code.

Reading battery voltage

The reading of the battery voltage on the atmega is a bit more involved, because the battery can drop below the LDO voltage we don’t have a stable Analog VCC and therefor not a stable reference. I came up with the following solution:

Now the value I read was pretty unstable and only having a 10bit DAC it was close to unusable, after some experimentation I’ve solved this by:

This gave me a pretty stable value, see the yellow line:


The red line is interesting, this is a sensor that sits outside and went through pretty high temperature swings (12 degrees Celsius up to 53 baking in the full sun). I have not tested if it is the internal reference being very temperature sensitive, or the battery chemistry. From the datasheet, the battery is rated -60 C to +85 C and can drop 0.1V per 20C difference @ 1mA load. Given these batteries are well past their ‘best before date’ it effect might be worse? It is probably a bit of both. For its current purpose this is good enough.

Battery life time

From the battery datasheet at the average current usage I should get up to 10 year battery usage. The battery curve should be pretty flat until its empty. This is not realistic for multiple reasons, I suspect the NRF24 peak usage of 30mA cause power drops that makes the NRF24 fail sooner, limiting the usable life time. The batteries are also 10 years past their best-before date, and start off at a lower voltage.

I still hope to get multi-year battery life time out of these sensors. Lithium Thionyl Chloride batteries are often used in harsh environment and can have a very long lifetime (up to 40years), so I have high hopes.

Battery Curve (graph from Eve ER14250 datasheet)

Sensor Case

I’ve also updated the sensor case. It is about 1/4 of the volume of the old one. I am pretty happy with this. It can be 3d printed, but my printer is not the biggest fans of the holes in the sides so I might redesign it.


Not visible here, the lid has two tabs to ‘click’ into the bottom part.

Hooking the whole system into the MQTT system

The receiver is now hooked up to a pc that reads the serial ports and pushes the raw messages to MQTT. Its my plan in the future to replace the PC with an esp32c3. This is the reason why I don’t translate it into the final topic in the first step, but first push the raw message to a special topic. So the esp32c3 does not have to be updated for added sensors, but a small adaptor running in a lxc container can do the translation.

Message format

The message format is binary and has a fixed header:

typedef struct MyData
  // required
  uint8_t id;
  uint8_t failed;
  // body
  uint8_t voltage;
  .. custom fields..

The first is important, this is a unique ID that the adaptor uses to parse the rest of the message. The failed is a counter that keeps track of failed transmissions, this helps with determining placing of sensor and what the range is. The voltage (is part of the body) is the battery level in centi volt (cV) - 200cV. This allows a voltage from 2.0-4.55 Volt, well within most batteries range.

Collision avoiding

There is currently no active collision avoiding implemented. I do make sure each sensor has a different interval, trying to avoid them running in lock step. If it becomes a problem I will try to fix this.


This is an atmega328pb with a nrf24l01+ (with lna) that receives each message:


I use the dynamic message size feature of the NRF24, so you only send the data you need.

The pipe is more a future extension if I want to make groups, I don’t think this will be needed. I don’t expect to have so many clients connected that this becomes needed.

Serial to MQTT bridge

This is small C program that publish the above message to:


In a json message like:

{"pipe": {pipe}, "length": {msgsize}, "failed": 0, "msg" : "C2..... 33"}

Where msgsize is the length of the original data in msg, this is the body of the original message.

MQTT parsing and re-publish

I have small adapters running that take the data from the matching nrf24bridge/{id} topic, parses the values (for now most are 32bit floats), do endianess conversion and publish it to the desired topic.

Mistakes made

Sending id as hex, parsing as dec

I kinda shot myself in the foot a bit by messing up conversion, I only found this out on the 10th sensor. For now I did not try to fix it, as enough IDs are still available.

Add support for bigger decoupling capacitor for NRF24 unit

The NRF24 are known to be bad with small drops on its supply voltage. I’ve had 44µF of decoupling after the ldo, but I’ve should have added a small pad for a larger capacitor close to the module.

Use the SMD version of the NRF24 pcb

This would have saved a bit of size, I did redesign the pcb but decided against re-ordering and using. The current version worked out good enough.

comments powered by Disqus