Button, Button, Who’s Got The (Pico) Button?

There is an episode of Ren and Stimpy with a big red “history eraser’ button that must not be pressed. Of course, who can resist the temptation of pressing the unpressable button? The same goes for development boards. If there is a button on there, you want to read it in your code, right? The Raspberry Pi Pico is a bit strange in that regard. The standard one lacks a reset button, but there is a big tantalizing button to reset in bootloader mode. You only use it when you power up, so why not read it in your code? Why not, indeed?

Turns out, that button isn’t what you think it is. It isn’t connected to a normal CPU pin at all. Instead, it connects to the flash memory chip. So does that mean you can’t read it at all? Not exactly. There’s good news, and then there’s bad news.

The Good News

The official Raspberry Pi examples show how to read the button (you have read all the examples, right?). You can convert the flash’s chip-select into an input temporarily and try to figure out if the pin is low, meaning that the button is pushed. Sounds easy, right?

The Bad News

The bad news is really bad. When you switch the flash chip-select to an input, you will lose access to the flash memory. But we are running from the flash memory! So, the first thing to think about is that the code will need to run from RAM.

But that’s not all. The Pico has interrupts and two CPU cores. So even if you are out of the flash memory, there’s no reason to assume someone else won’t want to use it simultaneously. So, to make this work, you need to disable interrupts and shut down the other CPU core while you read the pin.

The Example

Looking at the example, they do everything but disable the second core. I recently had to put this code in an Arduino-style program; there is excellent Arduino support for the Pico. Putting the function in RAM is pretty easy:

bool __no_inline_not_in_flash_func(_get_bootsel_button)() {

The rest is just manipulating the I/O pins and turning interrupts on and off:

uint32_t flags = save_and_disable_interrupts();

// Set chip select to Hi-Z
hw_write_masked(&ioqspi_hw->io[CS_PIN_INDEX].ctrl,
GPIO_OVERRIDE_LOW << IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_LSB,
IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_BITS);

// Note we can't call into any sleep functions in flash right now
for (volatile int i = 0; i< 1000; ++i);

// The HI GPIO registers in SIO can observe and control the 6 QSPI pins.
// Note the button pulls the pin *low* when pressed.
bool button_state = !(sio_hw->gpio_hi_in & (1u << CS_PIN_INDEX));

// Need to restore the state of chip select, else we are going to have a
// bad time when we return to code in flash!
hw_write_masked(&ioqspi_hw->io[CS_PIN_INDEX].ctrl,
GPIO_OVERRIDE_NORMAL << IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_LSB,
IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_BITS);

restore_interrupts(flags);

That leaves the core. I put a wrapper around this function to avoid any possible problems with it being called from RAM (though it would probably work):

bool get_bootsel_button(void)
{
   bool rv;
// freeze
   rp2040.idleOtherCore();
   rv = _get_bootsel_button();
// unfreeze
   rp2040.resumeOtherCore();
   return rv;
}

It works. But I do worry about how inefficient it must be. You usually want to poll a button often. Turning off the other core, disabling interrupts, and the idle loop to let the pin settle — all that will take time. In practice, it seems to work OK, but it must be slowing things down some.

In Retrospect…

So, can you read the Pico button? Yes. Should you? Maybe. For some applications, it is probably just fine. But if you are worried about performance, it probably isn’t the best idea.

With two 133 MHz cores, a ton of memory, easy debugging, and those cool peripheral processors, there’s a lot to love about the Pico. Just maybe not the BOOTSEL button.

26 thoughts on “Button, Button, Who’s Got The (Pico) Button?

  1. Just feel i should mention because its a little bit burred in the documentation. if you’re using the usb interface for stdio, set your baudrate to 1200 on the host machine and the pico will reset in to bootloader mode. way easier than trying to reset it while holding that button.

    1. neat! i came here to report my different hack to avoid pushing the bootsel button.

      i just put my code in SRAM. i made a relatively bespoke toolchain because i have a kneejerk reaction against embedded SDKs https://galexander.org/rp2040/

      but the stock SDK also offers a ‘program to SRAM’ option. you flash it, it boots up your code, and then when you reset, there’s nothing in flash so it goes into the bootloader by default. i use a watchdog timer so my experiment reboots on its own after a few seconds. when i did experiment with writing to flash, i had to write a garbage block to flash when i was done so it wouldn’t keep seeing that

      obviously, some limitations. but for me, the RAM on this thing is spacious beyond comprehension and the games i’ve played with it so far all are amenable to automatic reset after a few seconds.

  2. Well, it is labeled “BOOTSEL” right on the silk screening after all. I’ll admit my ways are not for everyone, but if I need a general purpose button I just add one to a GPIO pin. For me, the real issue is the lack of a RESET button. C’mon Raspberry Pi, it wouldn’t have been that hard to add one, just about every third party RP2040 board does!

  3. I’m not sure I quite understand the appeal of this chip. Unless you’re using the more advanced hardware peripherals, I’d much rather use an ESP32. And that has some pretty cool peripherals itself.

      1. And the SDK doesn’t beg you to disable virus scanning, plenty of pins, and 1.8v operation for those weird times, USB, plus you could buy them during pandemic

        ESP is a powerful chip, but the RP2040 is really growing on me.

      2. PIO begs for usb 2.0 high speed mode so you could easily push data in/out, sadly it is not there. without usb (= and power attached) you may also notice it has quite poor power management so unlike almost any other microcontroller it draws around milliamp in lowest deep sleep mode.

        But still it is great little thing, I am looking forward to next version in this family.

    1. what i like about the pico is that it’s almost as inexpensive as the most disreputable vendors for esp32 and stm32 sort of boards (blue pill, etc). but for the pico, that’s the official price from reputable vendors! and unlike esp32, there’s a little support and documentation.

      i really think it’s the apex of this sort of embedded ARM dev kit.

      but if i was already well-oriented towards some other board i don’t know if i’d switch. but i had no experience with esp32 and the stm32 boards i have were expensive and/or full of downsides so the pico is a really nice upgrade for me

      1. Hmm, not sure why you thought the esp32 doesn’t have documentation.. And wifi/bluetooth are nice to have at the same (or cheaper) price point..
        So I think it is more of a 328P replacement.

    2. Does everything I want a board like this to do and more. Reliable. Easy to work with. From a reputable vendor. Cheap even with Wifi added. Very good documentation, tutorials, etc. microPython, Circuit Python, C support . Plus if you the Pico doesn’t meet your need maybe many of the other RP2040 based boards will. For whatever reason, I’ve collect enough boards (Picos and RP2040 based boards) to automate a nuclear plant probably … Like potato chips, never can have just one. Vendors have several screw down terminal block boards built around the Pico. So what’s not to like? I’ve replaced a couple of RPI Zero Ws with Pico W boards for reliability purposes. I’ll leave the Linux based boards for more compute intensive tasks. Of course not the only boards I use. I like the adafruit Mega series of boards. Like the Mega Grand Central board which worked out great when I needed lots of DO connections.

  4. There is already an Arduino class built in that handles reading BOOTSEL… Namely, “BOOTSEL”. :)

    https://github.com/earlephilhower/arduino-pico/blob/master/cores/rp2040/Bootsel.h

    Use it as an “operator bool” and you’re all set: if (BOOTSEL) Serial.println(“BEEP”)

    That said, it is a little slow to read so not the best choice for high performance. On the other hand, it’s already there so the price is right.

  5. If the whole thing is slow, why the loop to let the button settle? If you poll often enough to press a button (a short press should last >200ms, but you can also
    accept “long press” and poll every 5s) and then you only need to detect “if” the button is pressed. It’s only interrupt-based or really fast processes that need debouncing.

    I do a similar thing with some of my projects and just poll every 50ms (some switches might debounce that long, the ones I use from an old VCR won’t), and do an action only when they are released and count how long they have been pressed, to differentiate between short press, longer press (I think 1s) and “set-up” (5s), see http://iivq.net/controlling-the-card/

  6. And who can forget the Looney Toons episode of the Insurance Salesman where Daffy Duck sells Porky Pig a full home insurance policy and installs a red, “Press in case of Hurricane!” button. Porky presses the button and the whole house is raised up 200 feet. Then Daffy shows up in a helicopter and says, “For a small fee, I can install this BLUE button to get you down!” Classic!

  7. A little confused when reading this article, because it mentions having to use this in an arduino style program. I was pretty sure I didn’t have any issue with reading button presses on BOOTSEL. Sure enough, looking at my old code, I just use “if (BOOTSEL) {“. So, support for reading the button is likely already baked into the board support in arduino.

  8. I’m surprised this is still not widely adopted, I covered this with a Hello World demo sketch almost 3 years ago and this has been used by plenty of people: https://github.com/jasongaunt/rp2040-bootsel-reboot-example

    Key usage:

    1. Use the demo or include its code in your own project – you can safely strip the comments
    2. When it’s running, press BOOTSEL once to just reboot the RP2040
    3. Alternatively, press and hold BOOTSEL to jump into upload mode

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.