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.
My approach was just to add little solder jumpers to the boot and reset pin that connects them (optionally!) to the IO header!
https://hackaday.io/project/192207-crapi2040
Fun reminder that analog methods are still valid in an increasingly digital world.
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.
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.
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!
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.
The PIO subsystem is quite incredible. It lets you speak protocols you could never bitbang. And the SDK is quite refined.
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.
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.
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
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.
i’m so old school i remember when the esp32 was spelled “esp8266” and all the english documentation was aftermarket :)
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.
should have said ” Like the Mega Grand Central board which worked out great when I needed lots of DI and DO connections.”
Ok. Wish I could edit. Board is actually Adafruit’s “Metro M4 Grand Central” :rolleyes: Not Mega … Metro series .
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.
How did I miss that? Is it here somewhere? https://arduino-pico.readthedocs.io/en/latest/index.html
Not yet, thanks for looking! Looks like I did the BOOTSEL.ino example but did not add anything to the docs. That’s fixed here: https://github.com/earlephilhower/arduino-pico/pull/1722
As there is a R & S reference,
some people will be wondering if I will comment,
but I won’t.
Good job
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/
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!
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.
well, if you can’t put an interrupt on it it isn’t that useful for a button…
For those that are too young,
“Button, button, who’s got the button” is a line from Willy Wonka and the Chocolate Factory.
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