Pico W not reading GPIO24

Hi, i’m trying to detect if my pico w is being powered by USB.

charging = Pin(24, Pin.IN)
charging.value()

charging .value() is always 0, I don’t understand!

It’s plugged into usb, has a lipo shim and pico display attached

Has anyone successfully detected usb power using micropython on pi pico w ?

Yep, puzzled me for a while though :)

On the Pico W, vbus sense is on one of the special internal function pins, not GP24 - try:

from machine import Pin

charging = Pin('WL_GPIO2', Pin.IN)
print (charging.value())

More details on these pins on page 8 of the Pico W datasheet: https://datasheets.raspberrypi.com/picow/pico-w-datasheet.pdf

fantastic!! thank you so much, I was searching everywhere for this!

So my script is working correctly now and picking up the charging status…brilliant

I’m also trying to read the voltage input when the pico w is not plugged in via usb.
In theory I should be able to use Pin29 however this shows a very weird voltage (0.04V should be 3-4v), however after some googling it appears that pulling pin25 high corrects this

Pin(25, mode=Pin.OUT, pull=Pin.PULL_DOWN).high()

That works, but now that I am using your fix for usb detection it has stopped working.

the complete script I am running should display the charging voltage and battery level, or a ‘charging!’ status when it is plugged in.

It’s basically the script you have put together here pimoroni-pico/battery_pico.py at main · pimoroni/pimoroni-pico · GitHub … but it doesn’t seem to work for a pico w

so currently the script I have is this

# 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

# change to DISPLAY_PICO_DISPLAY_2 for Pico Display 2.0
from picographics import PicoGraphics, DISPLAY_PICO_DISPLAY
display = PicoGraphics(display=DISPLAY_PICO_DISPLAY, rotate=0)

display.set_backlight(0.5)

Pin(25, mode=Pin.OUT, pull=Pin.PULL_DOWN).high()
Pin(29, Pin.IN)
vsys = ADC(29)# reads the system input voltage

charging = Pin('WL_GPIO2', Pin.IN)  # reading GP24 tells us whether or not USB power is connected
conversion_factor = 3 * 3.3 / 65535

full_battery = 4.2                  # these are our reference voltages for a full/empty battery, in volts
empty_battery = 2.4                # 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
    Pin(25, mode=Pin.OUT, pull=Pin.PULL_DOWN).high()
    voltage = vsys.read_u16() * conversion_factor
    percentage = 100 * ((voltage - 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() == 1:         # 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(voltage) + "v", 15, 10, 240, 5)
        display.text('{:.0f}%'.format(percentage), 15, 50, 240, 5)

    display.update()
    time.sleep(0.5)

I think you’re on the right lines - you don’t need to set the pin high in your while True: loop though, just doing that once at the beginning of your code where you set up the pins should be sufficient, like this:

# on a Pico W we need to pull GP25 high to be able to read vsys
# this is because this pin is shared with the wireless module's SPI interface

spi_output = Pin(25, Pin.OUT)
spi_output.value(True)

# set up the ADC that's connected to the system input voltage
vsys = ADC(29)

You also don’t need the Pin(29, Pin.IN) - we’re reading GP29 as an ADC pin, not as a standard IO pin.

I’ll try and and add a Pico W version of this example to add to Github when I get the chance - I’ve not used a Pico LiPo SHIM with a Pico W yet. They were unobtainium for a while after the Pico W launch!

very interesting…so I have updated the script now

If I start it with the usb plugged in, the charging works, including unplugging and plugging in, but the battery voltage is wrong
If I start it unplugged the battery voltage is correct for the first loop (0.5s) but is subsequently incorrect, and the charging detection also works…

my script is as follows

from machine import ADC, Pin
import time

# change to DISPLAY_PICO_DISPLAY_2 for Pico Display 2.0
from picographics import PicoGraphics, DISPLAY_PICO_DISPLAY
display = PicoGraphics(display=DISPLAY_PICO_DISPLAY, rotate=0)

display.set_backlight(0.5)

# on a Pico W we need to pull GP25 high to be able to read vsys
# this is because this pin is shared with the wireless module's SPI interface

spi_output = Pin(25, Pin.OUT)
spi_output.value(True)

vsys = ADC(29)# reads the system input voltage

charging = Pin('WL_GPIO2', Pin.IN)  # reading GP24 tells us whether or not USB power is connected
conversion_factor = 3 * 3.3 / 65535

full_battery = 4.2                  # these are our reference voltages for a full/empty battery, in volts
empty_battery = 2.4                # 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
    voltage = vsys.read_u16() * conversion_factor
    percentage = 100 * ((voltage - 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() == 1:         # 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(voltage) + "v", 15, 10, 240, 5)
        display.text('{:.0f}%'.format(percentage), 15, 50, 240, 5)
        
    display.update()
    time.sleep(0.5)

Managed to find myself a Pico LiPo SHIM, woop!

Try this - uses a vsys reading function I found online that changes the pin state of GP29 before and after a read (and does some stuff with enabling/disabling network). Vsys reading and vbus sense both seem to be working OK - the voltage reading seems to jump around more than on a standard Pico 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)

weeeeeey congrats on finding a shim :-)

that works like a charm, thank you so much, absolutely fantastic

For anyone ending up here looking for how to read pins that are shared with the wifi chip like VSYS (GPIO29) and VBUS sense (WL_GPIO2), I recently opened an issue on github after failing to do this with the C/C++ SDK.
They pointed me to CYW43_THREAD_ENTER / EXIT macro’s which helped with an implementation that allows reading the VBUS/VSYS while using wifi.
Not sure if equivalent behavior is available via micropython but the discussion cleared up much of the not-so-well documented specific usage of these pins.

basic example here: pico-examples/pico_w/wifi_vsys at issue/324 · pspeybro/pico-examples · GitHub

3 Likes

“Hello Darkness, my old friend” ;-)

Thanks for putting the finger into this wound…
This is exactly the pitfall I ran into with the current MicroPithon V0.0.4 for the Badger2040:
The 2040(non-W) board can read VSYS and detect VBUS (USB-Charging).
The 2040W V0.0.4 implementation doesn’t even know, what ‘WL_GPIO2’ is…

charging = Pin('WL_GPIO2', Pin.IN)
(...)
Traceback (most recent call last):
  File "<stdin>", line 294, in <module>
  File "<stdin>", line 269, in button
  File "<stdin>", line 225, in render
  File "<stdin>", line 127, in draw_battery_usage
ValueError: unknown named pin "WL_GPIO2"

The idea was, to have a “battery bar” for both, 2040 or 2040W
My used case / test-devices are a Badger2040 and a Badger2040W
I hope NOT, this is the reason, why this above code, which are everywhere to find, not running on that 2040 pico devices…

If someone knows, a hint would be appriciated, thank you!

The Hitchhiker

whole code
def draw_battery_usage(x):

    # Pico W voltage read function by darconeous on reddit: 
    #def draw_battery_usage(x):

    # Pico W voltage read function by darconeous on reddit: 
    # https://www.reddit.com/r/raspberrypipico/comments/xalach/comment/ipigfzu/
    # in reference of https://pico.pinout.xyz/ and https://picow.pinout.xyz/
    # the pins and ports are transfered, to make it work on a badger2040 non-W
         
    # these are our reference voltages for a full/empty battery, in volts 
    # the values could vary by battery size/manufacturer so you might need to adjust them
    # full/empty-pairs for some batteries:
    # lipo: 4.2 / 2.8
    # 2xAA/2xAAA alkaline: 3.2 / 2.4
    # 2xCR20332: 6.0 / 4.0
    # 1xCR2440:  3.0 / 2.0 experimental! 
    full_battery = 4.2 # lowered to 5.0V for CR2032, since pico draws 25mA
    empty_battery = 2.8 # lowered to 3.5V for CR2032, since pico draws 25mA
    
    con_fac_2040w = 3 * 3.3 / 2**16  # for 2040W internal 3.3V as referece
    con_fac_2040 = 60 * 3.3 / 2**16  # for 2040 its all a bit different... 

    if badger2040.is_wireless() == True:
        
        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, pull=None)

            val_vsys = ADC(29).read_u16() * con_fac_2040w
                    
        finally:
            # reading 'WL_GPIO2' on a picoW tells us whether or not USB power is connected (VBUS Sense)
            charging = Pin('WL_GPIO2', Pin.IN)
            # Why the heck this is all over the forums and no one has a problem with it???
            # since 'WL_GPIO2' seems not to work anymore on a 2040W

            # Restore the pin state and possibly reactivate WLAN
            Pin(25, Pin.OUT, value=0, pull=Pin.PULL_DOWN)
            Pin(29, Pin.ALT, pull=Pin.PULL_DOWN, alt=7)
            wlan.active(wlan_active)
           
    else:
        # reading pin24 on a pico tells us whether or not USB power is connected (VBUS Sense)
        charging = Pin(24, Pin.IN)  

        # Configure pin 29 as an input. (Read VSYS/3 through resistor divider and FET Q1)
        Pin(25, mode=Pin.OUT, pull=Pin.PULL_DOWN).high()
        Pin(29, Pin.IN, pull=None)
        val_vsys = ADC(29).read_u16() * con_fac_2040

        
    # convert the val_sys (raw ADC read) into a voltage, and then a percentage
    b_level = 100 * ( ( val_vsys - empty_battery) / (full_battery - empty_battery) )
    if b_level > 100:
        b_level = 100.00

    display.set_pen(15)
    display.image(
        bytearray(
            (
                0b110011,
                0b001100,
                0b011110,
                0b011110,
                0b010010,
                0b010010,
                0b010010,
                0b011110,
                0b000001,
            )
        ),
        6,
        9,
        x,
        3,
    )
    # assemble horizontal bar-graph beginning at position x+8 (bc. width of 6px the battery symbol)
    # outer white box
    display.rectangle(x + 8, 3, 80, 10)
    display.set_pen(0)
    # inner black box
    display.rectangle(x + 9, 4, 78, 8)
    # white bar according to percentage
    display.set_pen(15)
    #print(f"b_level: {b_level}")

    # reading 'WL_GPIO2' on a picoW tells us whether or not USB power is connected (VBUS Sense)

    # if it's not plugged into USB power...
    if charging.value() == False:
        # if charging is false, display the battery status:
        # bar starts at coordinates x+10,5 and 6px high max length 76px (accordingly to percentage)
        display.rectangle(x + 10, 5, int(76 / 100.0 * b_level), 6) 
        display.text("{:.2f}%".format(b_level), x + 91, 4, WIDTH, 1.0)
    else:
        # fake full power on USB when "charging" is true
        display.rectangle(x + 10, 5, int(76 / 100.0 * 100 ), 6) 
        display.text("USB", x + 91, 4, WIDTH, 1.0)