STFN

Reducing Raspberry Pi Pico W power consumption and a second attempt at using solar panels

30 minutes

If you don’t want to read the intro, just jump straight to conclusions.

Almost a year ago I made my first attempt at making a solar panel powered weather station using a Raspberry Pi Pico . That attempt turned out to be a failure, the solar panels I used were not able to prolong the the life of a Pico in any significant way. I abandoned the project and moved into different fields, mostly ham radio. And then came winter and any solar projects went into total hiatus.

Fast forward 10 months, and the summer is almost here, again there are long sunny days, and I have renewed interest in solar power. What is more, I am also looking into powering not only Pis, but also Meshtastic nodes with solar panels, something that I will write about very soon in an upcoming blog post.

And this time I am working in two directions, first I am investigating ways to reduce Pico’s power consumption, and secondly, looking into better panels to power the Pico from the Sun.

Let’s first start with the usage of electricity, and the move to its production.

Measuring Raspberry Pi Pico W power consumption…

What I will be describing here applies specifically for the Raspberry Pi Pico W, but I believe it can be also applied to other microcontrollers with Wi-Fi connectivity. Now as I am writing it, I think I might do at some point a comparison between a Pico W and an ESP8266 that I also have lying somewhere in a cupboard.

I am running Micropython on my Picos, however the findings here are independent of the language used.

Current monitoring

To monitor the power consumption in real time I bought a UNI-T UT658 USB monitor. With what I know now, I would buy something else, this monitor only shows three decimal places when it comes to amps, meaning I can only know the current (the current current?) in tens of miliamps. Then again, it’s better than nothing, and the meter allows for prolonged measuring of total usage, so I can calculate the average current within a period of time.

Hardware setup

In my first attempt, I used a Pico W with a BMP280 and DHT11 sensors. All three were soldered to a development board, with an ARC connector to power the microcontroller. You can see the pictures in the previous blog post linked at the top. This time I went with a much simplified setup, I am using a BME280 sensors that can check pressure, humidity and temperature from a single chip. For powering the Pico, I used the micro-USB connector for testing, and female-to-female jumper cables for the “production” run.

For testing purposes I have been powering the Pico from a standard powerbank, with the UNI-T in between the bank and the micro-USB cable going to the Pico.

Software setup

This is the initial script that I run on the Pico. It is a simplification of what I’ve been running at the previous attempt.

import json
import time

import bme280
import machine
import network
from umqtt.simple import MQTTClient

sdaPIN=machine.Pin(0)
sclPIN=machine.Pin(1)

i2c = machine.I2C(0,sda=sdaPIN, scl=sclPIN)
bme = bme280.BME280(i2c=i2c)

led = machine.Pin("LED", machine.Pin.OUT)

ssid = "SSID"
password = "PASSWORD"

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
max_wait = 10
while max_wait > 0:
    if wlan.isconnected():
        break
    max_wait -= 1
    time.sleep(1)

if not wlan.isconnected():
	machine.reset()
    
while True:
    led.on()
    time.sleep(0.2)
    led.off()
    data = bme.read_compensated_data()
    payload = {
        "temperature": data[0] / 100,
        "pressure": data[1] / 256 / 100,
        "humidity": data[2] / 1024
        }

    qt = MQTTClient("umqtt_client", "MQTT_BROKER_IP", keepalive=3600)
    qt.connect()
    qt.publish(b"weather", json.dumps(payload))
    qt.disconnect()
    time.sleep(60)

A full description of what this script does can be found in the previous post (link at the top), but let’s recap it here, it’s very simple.

First import all of the required modules. Next, initiate the I2C interface and the BME280 environment sensor, define the pin of the internal LED.

Second, connect to WiFi. If connecting to wifi timeouts, restart the Pico.

Now we enter the eternal loop in which we blink the LED, ask BME280 nicely to measure the environment, connect to the MQTT broker, send data and go to sleep for a minute.

I saved the script as main.py so that it would run when the Pico was powered, disconnected it from the PC and plugged it into a powerbank, with the UNI-T in between. And the results were surprising to say the least:

The Pico W is using ~45mA all the time when WiFi is on

45mA may not sound like a lot, but if you have a Pico running from a 3000mA battery, you’ll get 75 hours at best of runtime. And small, cheap solar panels will not provide enough current to power the Pico and charge the batteries at the same time. This is why my previous attempt failed.

Testing the Pico’s power usage

…And how to lower it

Time to finish that very long intro and move to the actual solution. I went to the Interwebs to see how others solved this issue. At first I thought that deepsleep or other low power modes would be the answer, but it turned out that the Pico’s flavour of Micropython does not support deepsleep at all, only lightsleep. And using lightsleep did not make any significant change during my testing. The actual solution was much simpler:

Turn off Wi-Fi

Turns out the Wi-Fi chip is the single largest power guzzler on the whole board, and turning it off causes the power consumption to fall dramatically.

The script I am showing above handles internet connectivity in a very simple way, the Pico connects to a wireless network once at startup and keeps the connection up for the whole time it is powered, even when it is sleeping. This is pointless and wasteful.

And so I modified my script to turn off Wi-Fi when sleeping, and power it on again only when sending data via MQTT.

import json
import time

import bme280
import machine
import network
from umqtt.simple import MQTTClient

sdaPIN = machine.Pin(0)
sclPIN = machine.Pin(1)

i2c = machine.I2C(0, sda=sdaPIN, scl=sclPIN)
bme = bme280.BME280(i2c=i2c)

led = machine.Pin("LED", machine.Pin.OUT)

ssid = "SSID"
password = "PASSWORD"

while True:
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(ssid, password)
    max_wait = 10
    while max_wait > 0:
        if wlan.isconnected():
            break
        max_wait -= 1
        time.sleep(1)

    if not wlan.isconnected():
        machine.reset()

    led.on()
    time.sleep(0.2)
    led.off()
    data = bme.read_compensated_data()
    payload = {
        "temperature": data[0] / 100,
        "pressure": data[1] / 256 / 100,
        "humidity": data[2] / 1024,
    }

    qt = MQTTClient("umqtt_client", "MQTT_BROKER_IP", keepalive=1)
    qt.connect(clean_session=True)
    qt.publish(b"weather", json.dumps(payload))
    qt.disconnect()
    wlan.active(False)
    wlan.deinit()
    time.sleep(60)

The difference between this version and the previous one is that I moved connecting to WiFi to inside of the eternal loop. Every iteration begins with the Pico connecting to WiFi. Then we do the measuring, sending data via MQTT, and once the data is sent, the Pico disconnects from the MQTT broker, disables the WiFi module, and the goes to sleep. After 60 seconds, the Pico wakes up, powers on the WiFi module, connects to the wireless network, etc, etc.

Running this script with the power monitor attached showed that the power consumption is at 40-50mA only for the second or two that it takes the Pico to connect to Wi-Fi and transmit data. During sleep the power consumption falls to levels below what the UNI-T monitor can show, so less than 10mA.

My power monitor is also capable to measure power consumption over time and provide the result in milliampere-hours. I performed tests over two and 12 hours, and they confirmed the massive difference betwen Wi-Fi adapter being on all the time, and being on only for transmitting.

With Wi-Fi on all the time, the average power consumption was ~45mAh per hour. With turning Wi-Fi off when sleeping, the Pico W used ~6mAh per hour. 7 times smaller!

6mAh is a much better result, it is now much simpler to power the Pico W with solar panels, even the small ones.

Issues with MQTT

However, a new problem arose. With the Pico’s continous establishing and ending connection with Wi-Fi and the MQTT broker, there is a now much higher risk of something going wrong at every loop. And the MQTT library that I am using is not handling connection errors well. During testing I’ve seen many times that after an hour or two the Pico would just freeze with Wi-Fi on, not sending anything to the broker.

After careful debugging (adding a ton of logging to a file) the culprit was found. Sometimes the MQTT connect() method would stop, and there was no timeout implemented by the library. Any issue with connecting to the MQTT broker would just freeze the Pico, and that would be the end, until it was rebooted manually. Not the best behavior for something that is meant to run independently for a prolonged period of time.

As the MQTT library I am using micropython-umqtt.simple. I installed it using Thonny’s package manager. Thonny also allows to easily edit the files of the installed libraries, they can be found on the Pico in the lib/ subfolder.

After going through the documentation of Micropython’s socket module, the module that the MQTT library is using underneath, I found that it does support handling timeouts. And so I added a timeout of 10 seconds, and modified the library’s file to reboot the Pi when the connection cannot be established within a given time. A crude solution but it works. Below I am providing the code for the updated connect() method.

def connect(self, clean_session=True):
	self.sock = socket.socket()
	self.sock.settimeout(10.0)
	addr = socket.getaddrinfo(self.server, self.port)[0][-1]
	try:
		self.sock.connect(addr)
	except OSError as e:
		log(f"connecting timeouted with error {e}")
		machine.reset()
	except Exception as e:
		log(f"something totally broke! {e}")
		machine.reset()

The rest of the connect() stayed the same. Also at the top of the file I added a simple error logger to have a persistent log of errors that I can check later.

def log(txt):
    with open("error_log", "a") as logfile:
        logfile.write(str(txt))
        logfile.write("\n")

This change has solved my issues with the Pi freezing. If you have a better solution, I will be very happy to hear your feedback!

Solar panels - attempt #2

Now that the power consumption issue is resolved, it’s time to go back to solving the power generation and management. The main difference between this and the previous build, is that I used much larger and better solar panels.

I bought two “3.5W 6V” solar panels from a Polish electronics shop. As the previous ones, they do not have a manufacturer label, but the overall build quality feels much better. I connected them in parallel using the additional connectors at the back and some 22AWG cables I bought previously. I also added a backplane from some sort of a plumbing bracket I found at a local hardware store. The bracket has a lot of holes for different connectors, I used the middle one to add an M5 screw. The bracket was then hot-glued to the back of the panels. The whole assembly is sturdy and universal enough to be mounted anywhere.

Solar panels assembly

Theoretically those two panels running in parallel should provide more than an 1 Amp of current (7W at 6V), but the best I got was around 300mA in full sunlight. Not great, but enough to both power the Pico and recharge the batteries after a night.

I reused the solar power manager from the previous iteration, it’s still the Solar Power Manager 5V v1.1 from DFRobot. It’s small and just works.

The battery storage is the same as in the Portable Meshtastic Node project I did a few weeks ago. Please consult that blog post for detailed instructions. It consists of three 18650 batteries connected in parallel. Together they provide around 7500mAh. I used a JST-PH 2.54mm connector to connect it to the solar power manager.

And that’s the full parts’ list. I connected the solar panels to the Solar In connector (I wish there was another JST socket for them), the batteries to the Battery In socket, and the Pico W using the 5V and ground pins. Nothing groundbreaking here.

Everything connected, placed in my not-that-clean balcony

Close-up on the Pico with the BME280 environment sensor

Current situation

I am still testing the performance of the new build, and so far I am impressed. I started the test with the batteries fully charged using a standalone charger and during the first day, the solar panels have been able to keep the full charge continuously. In the morning the batteries were depleted a bit, but the panels were able to recharge them in a few hours. Despite the fact that the whole day was cloudy and the panels are sitting on a windowsill pointing towards the West. I am sure that installed outside and pointed South, the batteries would get recharged way before noon.

Update: After 10 days of running, the battery voltage was around 4.10V at their lowest level, and they are recharging to full charge every day.

I am convinced that with the current setup, the Pi Pico W can run indefinitely, or well, at least until late Autumn, when the days will be much shorter. But we’ll measure that bridge when we get there.

The only missing step is a case, I need to work out something that will protect the Pico and the batteries, but at the same time provide free airflow for the sensor suite.

And that’s it! I am very happy with the outcome of this project. I have been talking only about the Pi Pico W here, but the lessons learned are universal, and can be applied to any device running from 5V or similar voltage. The next step for me will be to apply them to an off-grid node of Meshtastic. This project is already underway, and soon will be described here in detail.

Previous parts of my journey making a weather station with a Raspberry Pi Pico:

Raspberry Pi Pico Weather Station - part I

Raspberry Pi Pico Weather Station - part II

Powering Raspberry Pi Pico with AA batteries (Weather Station Part III)

Powering a Raspberry Pi Pico with solar panels and an 18650

Some interesting discussions that I found on the topic of Pico power consumption, that have been a great source of knowledge and inspiration when working on this project:

Reddit: Power reduction on pico w? - how to do?

PICO W - lightsleep not working after cyw43_deinit #10889

PICO W lightsleep doesn’t work when using rshell #12573

If you enjoyed this post, please consider helping me make new projects by supporting me on the following crowdfunding sites: