Badger_os get_battery_level failure

I just started working with the Badger 2040 W. I installed the latest firmware 1.19.15. When I try to use the badger_os function get_battery_level, I found it always returned 0. After looking at that code I realized the first statement was “return 0”. When I commented that out to let it do the calculation, I get the error that PIN_BATTERY is not defined in the badger2040w module.

When I inspect the badger2040w module, I notice that the necessary voltage pins are not defined as expected by get_battery_level (PIN_BATTERY, PIN_1V2_REF, PIN_VREF_POWER). I saw some references to issues measuring battery when the system powers back up after halting, but I’ve decided to drop my badger into a while loop that keeps it somewhat “wake”.

My ultimate goal is to determine how much power draw this wake mode has, but without those pin references, I can’t get to an answer. Does anybody have the original values of those codes that I can define and use instead and is this expected operation (i.e. Pimoroni has given up on battery measurement)?

Yes, the pirates give, the pirates take. The Badger2040W is stripped down a bit in comparison with the Badger2040.

What you can do is to measure VSYS-level. This is fairly standard but not absolutely exact. See this thread [SOLVED] How to measure VSYS - Raspberry Pi Forums for details.

Taking about power draw: measuring the voltage will not help for that task. My figures: the pico(w) with an “active” while-loop (while True: pass) will draw about 25mA. Due to the nature how time.sleep() is implemented nothing changes here when you replace the tight loop with sleeping.

There were some internal pins / functions moved around on the Pico W due to the wifi chip being added. That changes how you have to get the battery voltage.
I have this bit of code saved, haven’t tested it yet though.

# This example shows how to read the voltage from a LiPo battery connected to a Raspberry Pi Pico via our Pico Lipo SHIM
# and uses this reading to calculate how much charge is left in the battery.
# It then displays the info on the screen of Pico Display.
# Remember to save this code as main.py on your Pico if you want it to run automatically!

from machine import ADC, Pin
import time
import network
# change to DISPLAY_PICO_DISPLAY_2 for Pico Display 2.0
from picographics import PicoGraphics, DISPLAY_PICO_DISPLAY


def get_vsys():
    # Pico W voltage read function by darconeous on reddit: 
    # https://www.reddit.com/r/raspberrypipico/comments/xalach/comment/ipigfzu/
    conversion_factor = 3 * 3.3 / 65535
    wlan = network.WLAN(network.STA_IF)
    wlan_active = wlan.active()

    try:
        # Don't use the WLAN chip for a moment.
        wlan.active(False)

        # Make sure pin 25 is high.
        Pin(25, mode=Pin.OUT, pull=Pin.PULL_DOWN).high()
        
        # Reconfigure pin 29 as an input.
        Pin(29, Pin.IN)
        
        vsys = ADC(29)
        return vsys.read_u16() * conversion_factor

    finally:
        # Restore the pin state and possibly reactivate WLAN
        Pin(29, Pin.ALT, pull=Pin.PULL_DOWN, alt=7)
        wlan.active(wlan_active)


display = PicoGraphics(display=DISPLAY_PICO_DISPLAY, rotate=0)

display.set_backlight(0.8)

charging = Pin('WL_GPIO2', Pin.IN)  # reading this pin tells us whether or not USB power is connected

full_battery = 4.2                  # these are our reference voltages for a full/empty battery, in volts
empty_battery = 2.8                 # the values could vary by battery size/manufacturer so you might need to adjust them

# Create some pen colours for drawing with
BLACK = display.create_pen(0, 0, 0)
GREY = display.create_pen(190, 190, 190)
GREEN = display.create_pen(0, 255, 0)
RED = display.create_pen(255, 0, 0)

while True:
    # convert the raw ADC read into a voltage, and then a percentage
    percentage = 100 * ((get_vsys() - empty_battery) / (full_battery - empty_battery))
    if percentage > 100:
        percentage = 100.00
    
    # draw the battery outline
    display.set_pen(BLACK)
    display.clear()
    display.set_pen(GREY)
    display.rectangle(0, 0, 220, 135)
    display.rectangle(220, 40, 20, 55)
    display.set_pen(GREEN)
    display.rectangle(3, 3, 214, 129)

    # draw a green box for the battery level
    display.set_pen(GREEN)
    display.rectangle(5, 5, round(210 / 100 * percentage), 125)

    # add text
    display.set_pen(RED)
    if charging.value() == True:         # if it's plugged into USB power...
        display.text("Charging!", 15, 55, 240, 4)
    else:                             # if not, display the battery stats
        display.text('{:.2f}'.format(get_vsys()) + "v", 15, 10, 240, 5)
        display.text('{:.0f}%'.format(percentage), 15, 50, 240, 5)
    
    display.update()
    time.sleep(0.5)
2 Likes

Thank you @alphanumeric - I’ll give this code a try and report back

@bablokb - Here’s the bigger story for you as to why I care. Myabe this sparks some ideas.

I have the badge actually pull from an API to update the conference attendee’s information. If they go online and change their title (or whatever), they can push one of the side buttons to initiate a data (state) refresh. The problem I found is that when I use the Badger2040W halt function, it truly powers down, which means bye-bye wifi connection. So then when a user pushes that refresh button, it takes time to re-connect to WIFI.

I have also noticed that when I use that halt function, it loses the rtc data and the date/time resets to something like 1/1/2000. If I keep it alive, i just initiate the rtc update from ntp at the start of main and then let it do what it does.

Of the two, the rtc piece is the most puzzling because I thought the badger was supposed to keep just enough alive to retain time tracking.

Thanks

Thanks for the background. In fact, I use the display for a different, but in some aspects similar application: it is a clock. After power-loss it queries the date/time from the internet. Then it powers down to wake up after one minute. It then refreshes the display and powers done again.

The RTC seems to work fine for this setup: during uptime I set an alarm for the next full minute before powering down, and it wakes up the badger dead on. And it keeps the time. So it is indeed puzzling what is happening on your side.

I would not rule out that the rtc-driver code has a problem, the device is very new after all. I am using my own CircuitPython driver, so the fact that it works for me does not mean anything in this context.

BTW: before the Badger2040W, I used an ESP-01S as a coprocessor for wifi. And that device could run in a very low power-state keeping the wifi state and thus reconnecting very fast. But the Badger2040+ESP-01S is a much bigger package than the Badger2040W, so I rather accept the drawbacks of the integrated wifi of the PicoW.

One thing to add: are you aware of the fact that you are dealing with two RTCs? One builtin in the PicoW, one external on the Badger. When you fetch the time from ntp, you have to update both RTCs. When you wake up, you have to check if the external RTC is valid, and if so, update the internal RTC. If not, you need to refetch the time.

@bablokb - I was not aware of that and frankly am not even sure how to navigate that. I’m relatively new to micropython and IoT/hardware development (few months). I’ve been doing Python work for years, so the construct (in general) is nothing new to me. Do you have any sample code or direction you could give about accessing and managing the two difference RTCs?

@alphanumeric - I took your code and morphed it a bit. I essentially took the get_vsys function and combined it with the main part of your code to make a function I can use to gather the various piece parts and return values formatted like I need them. I put the code below if you’re curious. It appears to work, however I need to play around with the full vs. empty values (I’m using 2 AAA to power the badger).

# This example shows how to read the voltage from a LiPo battery connected to a Raspberry Pi Pico via our Pico Lipo SHIM
# and uses this reading to calculate how much charge is left in the battery.
# It then displays the info on the screen of Pico Display.
# Remember to save this code as main.py on your Pico if you want it to run automatically!

from machine import ADC, Pin
import network



def get_battery_info(full_battery=3.3, empty_battery=2.8):
    # Pico W voltage read function by darconeous on reddit: 
    # https://www.reddit.com/r/raspberrypipico/comments/xalach/comment/ipigfzu/

    # Initialize Variables
    conversion_factor = 3 * 3.3 / 65535
    voltage=0
    percentage=0
    is_charge=False
    error_message=None
    
    # prep the network
    wlan = network.WLAN(network.STA_IF)
    wlan_active = wlan.active()

    try:
        # Don't use the WLAN chip for a moment.
        wlan.active(False)

        # Make sure pin 25 is high.
        Pin(25, mode=Pin.OUT, pull=Pin.PULL_DOWN).high()
        
        # Reconfigure pin 29 as an input.
        Pin(29, Pin.IN)
        
        vsys = ADC(29)
        
        # get the voltage
        voltage=vsys.read_u16() * conversion_factor
        
        # figure out the percentage of available battery
        if voltage:
            try:
                percentage = 100 * ((voltage - empty_battery) / (full_battery - empty_battery))
            except:
                percentage = 0
                
            if percentage > 100:
                percentage = 100.00
            elif percentage < 0:
                percentage = 0
                
            charging = Pin('WL_GPIO2', Pin.IN)  # reading this pin tells us whether or not USB power is connected
            is_charge = charging.value()
        
    except Exception as e:
        error_message=str(e)

    finally:
        # Restore the pin state and possibly reactivate WLAN
        Pin(29, Pin.ALT, pull=Pin.PULL_DOWN, alt=7)
        wlan.active(wlan_active)
    
    return {"error":error_message, "voltage":voltage, "percentage":percentage, "full_battery":full_battery, "empty_battery":empty_battery, "is_charge":is_charge}

The builtin rtc is available as machine.RTC (see class RTC – real time clock — MicroPython latest documentation). The external RTC must be instantiated with the pcf85063a driver (see pimoroni-pico/micropython/modules/pcf85063a at main · pimoroni/pimoroni-pico · GitHub).

There are some examples here,

I have added RV3028’s to several of my Pico builds. The dedicated backup battery means only having to set it the once, on first time use.