Skip to content

No-button-boot problems (ugh), Adafruit RP2040 board, Linux host #3240

@egnor

Description

@egnor

I have an Adafruit Feather RP2040 RFM95 connected by USB to a Linux PC running Ubuntu 25.10.

If I hold BOOT (grounding QSPI_CS) and press RESET on the RP2040 board, it boots into the mass-storage bootloader, I can load programs and it's fine.

However, attempting a "zero button bootload" to reset and reprogram the board does not work, even though the firmware is trivial and running fine and using the serial port.

  • the boot attempt DOES interrupt the running firmware -- LED stops blinking, etc
  • the host does NOT see the USB-serial port going away
  • the host does NOT see a USB mass storage device
  • loading fails with "No drive to deploy".

Detailed info follows. (Thank you for all your work @earlephilhower and I am sure this is your least favorite recurring bug so I am trying to do my homework here.)

System info

The Feather board is bare (nothing connected to it) and connected to the host PC with a USB-C cable.

Here is the setup on the host PC:

% uname -a
Linux skully 6.17.0-6-generic #6-Ubuntu SMP PREEMPT_DYNAMIC Tue Oct  7 13:34:17 UTC 2025 x86_64 GNU/Linux

% arduino-cli version
arduino-cli  Version: 1.3.1 Commit: 08ff7e2b Date: 2025-08-28T13:51:45Z

Test setup

% cat sketch.yaml
default_profile: default

profiles:
  default:
    fqbn: rp2040:rp2040:adafruit_feather_rfm:dbgport=Serial,dbglvl=All
    platforms:
      - platform: rp2040:rp2040 (5.4.2)
        platform_index_url: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json

% cat pico-bug.ino
#include <Arduino.h>

void setup() {
  Serial.begin(115200);
  Serial.println("SETUP");
  pinMode(D13, OUTPUT);
}

void loop() {
  Serial.println("LOOP");
  digitalWrite(D13, HIGH);
  delay(500);
  digitalWrite(D13, LOW);
  delay(500);
}

Successful boot

After holding BOOT and pressing RESET, a successful boot looks like this:

% python -m serial
---  1: /dev/ttyACM0         'Meeting Owl Pro - CDC Abstract Control Model (ACM)'
---  2: /dev/ttyACM1         'Meeting Owl Pro - CDC Abstract Control Model (ACM)'
---  3: /dev/ttyACM2         'Meeting Owl Pro - CDC Abstract Control Model (ACM)'
---  4: /dev/ttyS0           'n/a'
---  5: /dev/ttyS1           'n/a'
... (other stuff redacted) ...

% lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
... (other stuff redacted) ...
Bus 003 Device 054: ID 2e8a:0003 Raspberry Pi RP2 Boot

% journalctl -k -n 10
Nov 13 12:29:58 skully kernel: scsi host2: usb-storage 3-1.1.4.4:1.0
Nov 13 12:29:59 skully kernel: scsi 2:0:0:0: Direct-Access     RPI      RP2    >
Nov 13 12:29:59 skully kernel: sd 2:0:0:0: Attached scsi generic sg1 type 0
Nov 13 12:29:59 skully kernel: sd 2:0:0:0: [sdb] 262144 512-byte logical blocks>
Nov 13 12:29:59 skully kernel: sd 2:0:0:0: [sdb] Write Protect is off
Nov 13 12:29:59 skully kernel: sd 2:0:0:0: [sdb] Mode Sense: 03 00 00 00
Nov 13 12:29:59 skully kernel: sd 2:0:0:0: [sdb] No Caching mode page found
Nov 13 12:29:59 skully kernel: sd 2:0:0:0: [sdb] Assuming drive cache: write th>
Nov 13 12:29:59 skully kernel:  sdb: sdb1
Nov 13 12:29:59 skully kernel: sd 2:0:0:0: [sdb] Attached SCSI removable disk

% arduino-cli compile && arduino-cli upload --port /dev/ttyACM3 && arduino-cli monitor --port /dev/ttyACM3
Sketch uses 67256 bytes (0%) of program storage space. Maximum is 8384512 bytes.
Global variables use 9792 bytes (3%) of dynamic memory, leaving 252352 bytes for local variables. Maximum is 262144 bytes.
Resetting /dev/ttyACM3
Converting to uf2, output size: 167936, start address: 0x2000
Scanning for RP2040 devices
Flashing /media/egnor/RPI-RP2 (RPI-RP2)
Wrote 167936 bytes to /media/egnor/RPI-RP2/NEW.UF2
New upload port: /dev/ttyACM3 (serial)
Using default monitor configuration for board: rp2040:rp2040:adafruit_feather_rfm:dbgport=Serial,dbglvl=All
Monitor port settings:
  baudrate=9600
  bits=8
  dtr=on
  parity=none
  rts=on
  stop_bits=1

Connecting to /dev/ttyACM3. Press CTRL-C to exit.
LOOP
LOOP
LOOP
LOOP
... ^C

% journalctl -k -n 10
Nov 13 12:29:59 skully kernel: sd 2:0:0:0: [sdb] Attached SCSI removable disk
Nov 13 12:32:32 skully kernel: usb 3-1.1.4.4: USB disconnect, device number 54
Nov 13 12:32:33 skully kernel: usb 3-1.1.4.4: new full-speed USB device number >
Nov 13 12:32:33 skully kernel: usb 3-1.1.4.4: New USB device found, idVendor=23>
Nov 13 12:32:33 skully kernel: usb 3-1.1.4.4: New USB device strings: Mfr=2, Pr>
Nov 13 12:32:33 skully kernel: usb 3-1.1.4.4: Product: Feather RP2040 RFM
Nov 13 12:32:33 skully kernel: usb 3-1.1.4.4: Manufacturer: Adafruit
Nov 13 12:32:33 skully kernel: usb 3-1.1.4.4: SerialNumber: DF62585783553434
Nov 13 12:32:33 skully kernel: cdc_acm 3-1.1.4.4:1.0: ttyACM3: USB ACM device
Nov 13 12:32:33 skully kernel: FAT-fs (sdb1): unable to read boot sector to mar>
☝️ [the device switching from mass storage to serial]

% lsusb
...
Bus 003 Device 055: ID 239a:812d Adafruit Feather RP2040 RFM
...

% python -m serial
...
---  4: /dev/ttyACM3         'Feather RP2040 RFM - Pico Serial'
...

(And of course the light is happily blinking on the device.)

Failed attempt at zero button boot

% arduino-cli compile && arduino-cli upload --port /dev/ttyACM3 && arduino-cli monitor --port /dev/ttyACM3
Sketch uses 67256 bytes (0%) of program storage space. Maximum is 8384512 bytes.
Global variables use 9792 bytes (3%) of dynamic memory, leaving 252352 bytes for local variables. Maximum is 262144 bytes.
Resetting /dev/ttyACM3
Converting to uf2, output size: 167936, start address: 0x2000
Scanning for RP2040 devices
No drive to deploy.
Failed uploading: uploading error: exit status 1

% journalctl -k -n 10
Nov 13 12:29:59 skully kernel: sd 2:0:0:0: [sdb] Attached SCSI removable disk
Nov 13 12:32:32 skully kernel: usb 3-1.1.4.4: USB disconnect, device number 54
Nov 13 12:32:33 skully kernel: usb 3-1.1.4.4: new full-speed USB device number >
Nov 13 12:32:33 skully kernel: usb 3-1.1.4.4: New USB device found, idVendor=23>
Nov 13 12:32:33 skully kernel: usb 3-1.1.4.4: New USB device strings: Mfr=2, Pr>
Nov 13 12:32:33 skully kernel: usb 3-1.1.4.4: Product: Feather RP2040 RFM
Nov 13 12:32:33 skully kernel: usb 3-1.1.4.4: Manufacturer: Adafruit
Nov 13 12:32:33 skully kernel: usb 3-1.1.4.4: SerialNumber: DF62585783553434
Nov 13 12:32:33 skully kernel: cdc_acm 3-1.1.4.4:1.0: ttyACM3: USB ACM device
Nov 13 12:32:33 skully kernel: FAT-fs (sdb1): unable to read boot sector to mar>
☝️ [the same logs from before, nothing new shows up]

% lsusb
...
Bus 003 Device 055: ID 239a:812d Adafruit Feather RP2040 RFM
...

% python -m serial
...
---  4: /dev/ttyACM3         'Feather RP2040 RFM - Pico Serial'
...

The light stops blinking the moment it says Resetting /dev/ttyACM3, and even though that serial port shows up in the list, attempting to read or write it does nothing (or, sometimes, gives "Input/output error").

Discussion and hypothesis

It looks like the baud rate tickle that happens in uf2conv.py

ser = serial.Serial()

does happen and does reset the board, as we can tell from the loss of blinky. Exactly what state the board is in is unclear-- the host OS still shows the app serial port as connected, but can't send any data to/from it.

(Also, executing the baud rate tickle by hand (in a python REPL) has the same effect as letting uf2conv.py do it.)

Importantly, once in this state, doing the "two-button" salute (BOOT / RESET) does NOT work, the USB remains in that state. Doing a simple RESET starts the light blinking again, but serial port output doesn't show up, and there's no USB state change on the host. After enough time (unclear how long) elapses, the host sometimes resets its USB state, and re-enumerates the device as either a UF2 mass storage device or a serial port, as appropriate.

(However, if we do NOT attempt a zero button boot and go straight to the two-button salute, then it works fine and the host re-enumerates the device as UF2 mass storage immediately.)

[edit, new discovery:] If I unplug and replug the connection from the PC to a hub that's in front of the device, then it re-enumerates in the correct mode. However, plugging the device directly into the PC (eliminating the hub) doesn't solve the problem, nor does unplugging and replugging the device. So it feels like something about the upstream USB port (either in the host or in a hub) getting put in an odd state by the device.

Hypothesis: Something about the way the baud-rate-tickle-reset causes the device to interact on USB (interrupting its previous activity, showing up as a new device) doesn't sit well with this (very vanilla) Linux kernel and system hardware. I'm not 100% clear on what exactly happens on the device side after that tickle arrives. Is it possible that it needs to make a more complete reset of the USB peripheral somehow, or maybe even delay a little bit to give the bus time to recover? I suppose the next step would be to attempt additional debugging of USB traffic, either on the kernel side or with a hardware USB analyzer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions