├── .gitignore ├── bootsel-reboot.hpp ├── CMakeLists.txt ├── rp2040-bootsel-reboot-example.cpp ├── LICENSE ├── bootsel-reboot.cpp └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore build folder and its contents 2 | build/**/* 3 | 4 | # Ignore pico_sdk_import.cmake 5 | pico_sdk_import.cmake 6 | -------------------------------------------------------------------------------- /bootsel-reboot.hpp: -------------------------------------------------------------------------------- 1 | #ifndef _BOOTSEL_REBOOT_HPP 2 | #define _BOOTSEL_REBOOT_HPP 3 | 4 | #define WATCHDOG_TIMEOUT 1000 // Milliseconds 5 | 6 | bool __no_inline_not_in_flash_func(get_bootsel_button)(); 7 | void arm_watchdog(); 8 | void update_watchdog(); 9 | void check_bootsel_button(); 10 | 11 | #endif // _BOOTSEL_REBOOT_HPP 12 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Import SDK 2 | cmake_minimum_required(VERSION 3.13) 3 | include(pico_sdk_import.cmake) 4 | project(rp2040-bootsel-reboot-example) 5 | pico_sdk_init() 6 | 7 | # Build application 8 | add_executable( 9 | rp2040-bootsel-reboot-example 10 | rp2040-bootsel-reboot-example.cpp 11 | bootsel-reboot.cpp 12 | ) 13 | 14 | # Debug output - Enable for USB CDC and disable for on-board UART 15 | # You can change these around (or even have both active) by changing the 1s and 0s 16 | pico_enable_stdio_usb(rp2040-bootsel-reboot-example 1) 17 | pico_enable_stdio_uart(rp2040-bootsel-reboot-example 0) 18 | 19 | # Build dependencies and link application 20 | pico_add_extra_outputs(rp2040-bootsel-reboot-example) 21 | target_link_libraries( 22 | rp2040-bootsel-reboot-example 23 | pico_stdlib 24 | ) -------------------------------------------------------------------------------- /rp2040-bootsel-reboot-example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "pico/stdlib.h" 3 | #include "hardware/gpio.h" 4 | #include "bootsel-reboot.hpp" 5 | 6 | const uint LED_PIN = 25; 7 | 8 | struct repeating_timer timer; 9 | volatile bool timer_fired = false; 10 | bool led_state = false; 11 | 12 | bool flip_leds(struct repeating_timer *t) { timer_fired = true; return true; } 13 | 14 | int main() { 15 | stdio_init_all(); 16 | arm_watchdog(); 17 | 18 | gpio_init(LED_PIN); 19 | gpio_set_dir(LED_PIN, GPIO_OUT); 20 | add_repeating_timer_ms(500, flip_leds, NULL, &timer); 21 | 22 | puts("Hello World\n"); 23 | 24 | while (1) { 25 | check_bootsel_button(); 26 | 27 | if (timer_fired) { 28 | led_state = !led_state; 29 | timer_fired = false; 30 | gpio_put(LED_PIN, led_state); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jason Gaunt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bootsel-reboot.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "pico/stdlib.h" 3 | #include "hardware/gpio.h" 4 | #include "hardware/sync.h" 5 | #include "hardware/structs/ioqspi.h" 6 | #include "hardware/structs/sio.h" 7 | 8 | extern "C" { 9 | #include "hardware/watchdog.h" 10 | } 11 | 12 | #include "bootsel-reboot.hpp" 13 | 14 | bool __no_inline_not_in_flash_func(get_bootsel_button)() { 15 | const uint CS_PIN_INDEX = 1; 16 | 17 | // Must disable interrupts, as interrupt handlers may be in flash, and we 18 | // are about to temporarily disable flash access! 19 | uint32_t flags = save_and_disable_interrupts(); 20 | 21 | // Set chip select to Hi-Z 22 | hw_write_masked(&ioqspi_hw->io[CS_PIN_INDEX].ctrl, 23 | GPIO_OVERRIDE_LOW << IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_LSB, 24 | IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_BITS); 25 | 26 | // Note we can't call into any sleep functions in flash right now 27 | for (volatile int i = 0; i < 1000; ++i); 28 | 29 | // The HI GPIO registers in SIO can observe and control the 6 QSPI pins. 30 | // Note the button pulls the pin *low* when pressed. 31 | bool button_state = !(sio_hw->gpio_hi_in & (1u << CS_PIN_INDEX)); 32 | 33 | // Need to restore the state of chip select, else we are going to have a 34 | // bad time when we return to code in flash! 35 | hw_write_masked(&ioqspi_hw->io[CS_PIN_INDEX].ctrl, 36 | GPIO_OVERRIDE_NORMAL << IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_LSB, 37 | IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_BITS); 38 | 39 | restore_interrupts(flags); 40 | 41 | return button_state; 42 | } 43 | 44 | void arm_watchdog() { 45 | watchdog_enable(WATCHDOG_TIMEOUT, 1); 46 | } 47 | 48 | void update_watchdog() { 49 | watchdog_update(); 50 | } 51 | 52 | void check_bootsel_button() { 53 | if (get_bootsel_button() == 1) { 54 | puts("Rebooting...\n"); 55 | while(1); 56 | } 57 | update_watchdog(); 58 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Raspberry Pi Pico RP2040 BOOTSEL reboot example 2 | 3 | ## Introduction 4 | 5 | This github repo is a sample program to use the BOOTSEL button as a two-stage reboot button for the Pico RP2040. 6 | 7 | Pressing the button and releasing it will reboot the Pico RP2040 and start running your application again. 8 | 9 | Pressing and holding the button will reboot the Pico RP2040 and go into USB mass storage mode so you can upload a new application. 10 | 11 | I have written this sample to help others from damaging the fragile Micro USB connector on the Pico RP2040 board. 12 | 13 | ## Analysis 14 | 15 | This sample application is a hello-world style application, it will print "Hello World" to the USB-CDC UART and flash the LED at 1 Hertz in a non-blocking fashion by making use of the on-board timer. 16 | 17 | This application utilises the RP2040 watchdog to perform the actual reset so that we can benefit from two-stage rebooting. 18 | 19 | ## Warning 20 | 21 | This code utilises low-level functions to tap into the BOOTSEL button that can stall (and potentially disrupt) code that utilises the on-board flash storage (ie. datalogging). This code also briefly disables interrupts whilst it checks the state of the BOOTSEL button. 22 | 23 | Although this code executes quickly there is always the possibility your application may miss an interrupt or not read or write to flash. 24 | 25 | **Use with caution and test thoroughly!** 26 | 27 | ## Installation 28 | 29 | This code was written with the intention of being compiled on a Linux system (in my case, Ubuntu 20.04). 30 | 31 | It expects that the `pico-sdk` folder is located in the parent folder above this (or the parent folder above that). Examples of this are as follows... 32 | 33 | #### SDK located in parent folder 34 | ``` 35 | ├── rp2040-bootsel-reboot-example 36 | │ ├── .gitignore 37 | │   ├── CMakeLists.txt 38 | │   ├── README.md 39 | │   ├── bootsel-reboot.cpp 40 | │   ├── bootsel-reboot.hpp 41 | │   ├── build.sh 42 | │   └── rp2040-bootsel-reboot-example.cpp 43 | └── pico-sdk 44 | ├── CMakeLists.txt 45 | ├── LICENSE.TXT 46 | ├── README.md 47 | └── ... more files 48 | ``` 49 | 50 | #### Your code in a projects folder 51 | ``` 52 | ├── my-projects 53 | │   └── rp2040-bootsel-reboot-example 54 | │ ├── .gitignore 55 | │   ├── CMakeLists.txt 56 | │   ├── README.md 57 | │   ├── bootsel-reboot.cpp 58 | │   ├── bootsel-reboot.hpp 59 | │   ├── build.sh 60 | │   └── rp2040-bootsel-reboot-example.cpp 61 | └── pico-sdk 62 | ├── CMakeLists.txt 63 | ├── LICENSE.TXT 64 | ├── README.md 65 | └── ... more files 66 | ``` 67 | 68 | To compile, check out the code and run the build shell script... 69 | 70 | ```bash 71 | git clone git@github.com:jasongaunt/rp2040-bootsel-reboot-example.git 72 | cd rp2040-bootsel-reboot-example/ 73 | ./build.sh 74 | ``` 75 | 76 | The code should then build (usually in under 30 seconds, [YMMV](https://dictionary.cambridge.org/dictionary/english/ymmv)). 77 | 78 | 79 | Once built, in the `build/` folder you should find `rp2040-bootsel-reboot-example.uf2` - copy this to your Pico RP2040 and enjoy 80 | 81 | ## Sample Usage 82 | 83 | When this sample application is uploaded to your Pico RP2040 it will print "Hello World" to the USB-CDC Serial device and then start flashing the on-board LED at a rate of 1 Hertz. 84 | 85 | Press the BOOTSEL button and release immediately and the Pico RP2040 will reboot and start running the application again. 86 | 87 | Press and hold the BOOTSEL button and it will reboot into USB mass storage mode so you can upload a new application. 88 | 89 | ## Using in your own application 90 | 91 | 1. In your `CMakeLists.txt` make sure to add `bootsel-reboot.cpp` to the `add_executable()` block. 92 | 93 | 2. At the top of your application add `#include "bootsel-reboot.hpp"` 94 | 95 | 3. At the start of your application entry point (usually the `int main()` function) add `arm_watchdog();` after `stdio_init_all();` 96 | 97 | 4. In your application main loop call `check_bootsel_button();` frequently (ideally every 100 ms or less) 98 | 99 | **Warning:** If `check_bootsel_button();` isn't called every 1000 ms or less the watchdog will reboot the Pico RP2040! 100 | 101 | ## Contributing 102 | 103 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 104 | 105 | ## Contributors 106 | * Jason Gaunt - Initial Application 107 | * Raspberry Pi team - SDK and code samples that made this possible 108 | 109 | ## License 110 | [MIT](https://choosealicense.com/licenses/mit/) --------------------------------------------------------------------------------