Skip to content

Instantly share code, notes, and snippets.

@jepler
Last active March 13, 2024 15:09
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jepler/c7676f0c5fe4eab9c584424d997a7991 to your computer and use it in GitHub Desktop.
Save jepler/c7676f0c5fe4eab9c584424d997a7991 to your computer and use it in GitHub Desktop.
Reading RP2040 BOOTSEL pin from code
import microcontroller, digitalio, time
d = digitalio.DigitalInOut(microcontroller.pin.GPIO33)
while True:
print(d.value)
time.sleep(.1)

It turns out you CAN read the pico BOOTSEL pin from software.

I became aware of this possibility after pimoroni tweeted that you could use the BOOTSEL button as a MIDI trigger on their upcoming "Tiny2040". We speculated whether there was a small HW circuit to accomplish this, or if it was SW only. Now I have the answer.

I was also heavily informed by the bootloader source, the body of the reading code is taken directly from there with adjusted timings.

The general idea:

  • Pressing the button applies a strong pull-down.
  • Normally after start-up, this pin is always being driven by the Pico's QSPI peripheral, so the button has no effect
  • We can read it by jumping to code in RAM which does all these steps:
    • disconnect the peripheral from the pin
    • turn off the output driver
    • waits a bit for the BOOTSEL pull-down to pull the voltage down if pressed
    • take some readings
    • reconnect the QSPI peripheral
    • return the result of the reading

The reading is a bit weird. Rather than enabling an internal pull (is there one?) it just disables driving and takes a "majority of 10" vote to decide the pin's input value.

The new pin also needs special handling in CircuitPython; these changes are "make it work"-quality, not pull-request quality.

Does this deserve to actually be enabled in CircuitPython, or is it too nasty a hack to consider?

From 348917565bd17f2941e3626cd944ff6e55d053cc Mon Sep 17 00:00:00 2001
From: Jeff Epler <jepler@gmail.com>
Date: Fri, 12 Feb 2021 12:07:47 -0600
Subject: [PATCH] WIP allow reading bootsel
!!! it's probably broekn !!!
---
.../common-hal/digitalio/DigitalInOut.c | 52 ++++++++++++++++++-
.../common-hal/microcontroller/Pin.c | 16 +++++-
.../common-hal/microcontroller/__init__.c | 1 +
.../common-hal/microcontroller/__init__.h | 2 +-
ports/raspberrypi/peripherals/pins.c | 1 +
ports/raspberrypi/peripherals/pins.h | 1 +
6 files changed, 70 insertions(+), 3 deletions(-)
diff --git a/ports/raspberrypi/common-hal/digitalio/DigitalInOut.c b/ports/raspberrypi/common-hal/digitalio/DigitalInOut.c
index b0bc1b96e..07d49083c 100644
--- a/ports/raspberrypi/common-hal/digitalio/DigitalInOut.c
+++ b/ports/raspberrypi/common-hal/digitalio/DigitalInOut.c
@@ -32,9 +32,12 @@
#include "common-hal/microcontroller/Pin.h"
#include "shared-bindings/digitalio/DigitalInOut.h"
+#include "shared-bindings/microcontroller/__init__.h"
#include "supervisor/shared/translate.h"
#include "src/rp2_common/hardware_gpio/include/hardware/gpio.h"
+#include "src/rp2_common/hardware_clocks/include/hardware/clocks.h"
+#include "hardware/regs/io_qspi.h"
digitalinout_result_t common_hal_digitalio_digitalinout_construct(
digitalio_digitalinout_obj_t* self, const mcu_pin_obj_t* pin) {
@@ -66,6 +69,13 @@ void common_hal_digitalio_digitalinout_deinit(digitalio_digitalinout_obj_t* self
void common_hal_digitalio_digitalinout_switch_to_input(
digitalio_digitalinout_obj_t* self, digitalio_pull_t pull) {
+ const uint8_t pin = self->pin->number;
+ if(pin == 33) {
+ if (pull != PULL_UP) {
+ mp_raise_RuntimeError(translate("Pin only usable in input mode with pull up"));
+ }
+ return;
+ }
self->output = false;
// This also sets direction to input.
common_hal_digitalio_digitalinout_set_pull(self, pull);
@@ -75,6 +85,9 @@ digitalinout_result_t common_hal_digitalio_digitalinout_switch_to_output(
digitalio_digitalinout_obj_t* self, bool value,
digitalio_drive_mode_t drive_mode) {
const uint8_t pin = self->pin->number;
+ if(pin == 33) {
+ mp_raise_RuntimeError(translate("Pin only usable in input mode"));
+ }
gpio_set_dir(pin, GPIO_OUT);
// TODO: Turn on "strong" pin driving (more current available).
@@ -92,6 +105,9 @@ digitalio_direction_t common_hal_digitalio_digitalinout_get_direction(
void common_hal_digitalio_digitalinout_set_value(
digitalio_digitalinout_obj_t* self, bool value) {
const uint8_t pin = self->pin->number;
+ if(pin == 33) {
+ mp_raise_RuntimeError(translate("Pin only usable in input mode"));
+ }
if (self->open_drain) {
gpio_set_dir(pin, value ? GPIO_IN : GPIO_OUT);
} else {
@@ -99,9 +115,43 @@ void common_hal_digitalio_digitalinout_set_value(
}
}
+static inline void inline_delay_loop(uint32_t count) {
+ asm volatile (
+ "1: \n\t"
+ "sub %0, %0, #1 \n\t"
+ "bne 1b"
+ : "+r" (count)
+ );
+}
+
+static bool __attribute__((noinline)) __not_in_flash_func(read_bootsel)(int clock_mhz) {
+ // Check CSn strap: delay for pullups to charge trace, then take a majority vote.
+
+ io_rw_32 *reg = (io_rw_32 *) (IO_QSPI_BASE + IO_QSPI_GPIO_QSPI_SS_CTRL_OFFSET);
+ io_rw_32 oreg = *reg;
+ *reg = (*reg & ~0x1f) | 5; // return pin to sio
+ sio_hw->gpio_hi_oe_clr = 1u << 1; // Output disable
+ inline_delay_loop(200 * clock_mhz / 3);
+ uint32_t sum = 0;
+ for (int i = 0; i < 9; ++i) {
+ inline_delay_loop(5 * clock_mhz / 3);
+ sum += (sio_hw->gpio_hi_in >> 1) & 1u;
+ }
+ sio_hw->gpio_hi_oe_set = 1u << 1; // Output enable
+ *reg = oreg;
+ return (sum >= 8);
+}
+
bool common_hal_digitalio_digitalinout_get_value(
digitalio_digitalinout_obj_t* self) {
- return gpio_get(self->pin->number);
+ const uint8_t pin = self->pin->number;
+ if(pin == 33) {
+ common_hal_mcu_disable_interrupts();
+ bool result = read_bootsel((clock_get_hz(clk_sys) + 999999) / 1000000);
+ common_hal_mcu_enable_interrupts();
+ return result;
+ }
+ return gpio_get(pin);
}
digitalinout_result_t common_hal_digitalio_digitalinout_set_drive_mode(
diff --git a/ports/raspberrypi/common-hal/microcontroller/Pin.c b/ports/raspberrypi/common-hal/microcontroller/Pin.c
index ca5cf5a04..1dbff37a3 100644
--- a/ports/raspberrypi/common-hal/microcontroller/Pin.c
+++ b/ports/raspberrypi/common-hal/microcontroller/Pin.c
@@ -33,6 +33,7 @@
#include "src/rp2_common/hardware_gpio/include/hardware/gpio.h"
+bool bootsel_in_use;
#ifdef MICROPY_HW_NEOPIXEL
bool neopixel_in_use;
#endif
@@ -47,8 +48,9 @@ bool speaker_enable_in_use;
STATIC uint32_t never_reset_pins;
void reset_all_pins(void) {
+ bootsel_in_use = false;
for (size_t i = 0; i < TOTAL_GPIO_COUNT; i++) {
- if ((never_reset_pins & (1 << i)) != 0) {
+ if (i < TOTAL_GPIO_COUNT && (never_reset_pins & (1 << i)) != 0) {
continue;
}
reset_pin_number(i);
@@ -64,6 +66,11 @@ void never_reset_pin_number(uint8_t pin_number) {
}
void reset_pin_number(uint8_t pin_number) {
+ if (pin_number == 33) {
+ bootsel_in_use = false;
+ return;
+ }
+
if (pin_number >= TOTAL_GPIO_COUNT) {
return;
}
@@ -113,6 +120,9 @@ void common_hal_reset_pin(const mcu_pin_obj_t* pin) {
}
void claim_pin(const mcu_pin_obj_t* pin) {
+ if (pin == &pin_GPIO33) {
+ bootsel_in_use = true;
+ }
#ifdef MICROPY_HW_NEOPIXEL
if (pin == MICROPY_HW_NEOPIXEL) {
neopixel_in_use = true;
@@ -135,6 +145,7 @@ void claim_pin(const mcu_pin_obj_t* pin) {
}
bool pin_number_is_free(uint8_t pin_number) {
+
if (pin_number >= TOTAL_GPIO_COUNT) {
return false;
}
@@ -145,6 +156,9 @@ bool pin_number_is_free(uint8_t pin_number) {
}
bool common_hal_mcu_pin_is_free(const mcu_pin_obj_t* pin) {
+ if (pin == &pin_GPIO33) {
+ return !bootsel_in_use;
+ }
#ifdef MICROPY_HW_NEOPIXEL
if (pin == MICROPY_HW_NEOPIXEL) {
return !neopixel_in_use;
diff --git a/ports/raspberrypi/common-hal/microcontroller/__init__.c b/ports/raspberrypi/common-hal/microcontroller/__init__.c
index bfea8b2d9..440d8111e 100644
--- a/ports/raspberrypi/common-hal/microcontroller/__init__.c
+++ b/ports/raspberrypi/common-hal/microcontroller/__init__.c
@@ -176,5 +176,6 @@ const mp_rom_map_elem_t mcu_pin_global_dict_table[TOTAL_GPIO_COUNT] = {
{ MP_ROM_QSTR(MP_QSTR_GPIO27), MP_ROM_PTR(&pin_GPIO27) },
{ MP_ROM_QSTR(MP_QSTR_GPIO28), MP_ROM_PTR(&pin_GPIO28) },
{ MP_ROM_QSTR(MP_QSTR_GPIO29), MP_ROM_PTR(&pin_GPIO29) },
+ { MP_ROM_QSTR(MP_QSTR_GPIO33), MP_ROM_PTR(&pin_GPIO33) },
};
MP_DEFINE_CONST_DICT(mcu_pin_globals, mcu_pin_global_dict_table);
diff --git a/ports/raspberrypi/common-hal/microcontroller/__init__.h b/ports/raspberrypi/common-hal/microcontroller/__init__.h
index cc509b6b1..6bcf9bd4f 100644
--- a/ports/raspberrypi/common-hal/microcontroller/__init__.h
+++ b/ports/raspberrypi/common-hal/microcontroller/__init__.h
@@ -29,7 +29,7 @@
#include "src/rp2040/hardware_regs/include/hardware/platform_defs.h"
-#define TOTAL_GPIO_COUNT NUM_BANK0_GPIOS
+#define TOTAL_GPIO_COUNT (1+NUM_BANK0_GPIOS)
extern const mp_rom_map_elem_t mcu_pin_global_dict_table[TOTAL_GPIO_COUNT];
diff --git a/ports/raspberrypi/peripherals/pins.c b/ports/raspberrypi/peripherals/pins.c
index a2a7b85bd..be23e6013 100644
--- a/ports/raspberrypi/peripherals/pins.c
+++ b/ports/raspberrypi/peripherals/pins.c
@@ -65,3 +65,4 @@ PIN(26);
PIN(27);
PIN(28);
PIN(29);
+PIN(33);
diff --git a/ports/raspberrypi/peripherals/pins.h b/ports/raspberrypi/peripherals/pins.h
index 99ab9cfe4..0db9b79c3 100644
--- a/ports/raspberrypi/peripherals/pins.h
+++ b/ports/raspberrypi/peripherals/pins.h
@@ -67,5 +67,6 @@ extern const mcu_pin_obj_t pin_GPIO26;
extern const mcu_pin_obj_t pin_GPIO27;
extern const mcu_pin_obj_t pin_GPIO28;
extern const mcu_pin_obj_t pin_GPIO29;
+extern const mcu_pin_obj_t pin_GPIO33;
#endif // MICROPY_INCLUDED_RASPBERRYPI_PERIPHERALS_PINS_H
--
2.20.1
@pagong
Copy link

pagong commented Mar 13, 2024

Hi Jeff,
I'd like to port my "old" Yubikey simulator from Arduino to CircuitPython on RasPi-Pico.
The HID features of CircuitPython should make this easy. But I would need to solder a button to the RasPi-Pico.
This could be avoided, if the BOOTSEL button of the RasPi-Pico could be used easily in CircuitPython.
So my question is: Has your demo code for MicroPython already been ported to CircuitPython?
Anf if yes, in which version?
Thanks, Mike

@jepler
Copy link
Author

jepler commented Mar 13, 2024

No, I did not push for inclusion of this functionality, nor do I plan to. See eg the schematic of the qtpy rp2040 for how to design a board that can use the same switch as a regular gpios (costs one gpio and one diode): https://learn.adafruit.com/adafruit-qt-py-2040/downloads

@pagong
Copy link

pagong commented Mar 13, 2024

Thanks, for the quick reply. I'll go the solder route. 😃
Adding a tiny button switch between GND and GP15 (or GND and GP12) is easy enough on the RasPi-Pico.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment