How to brick your Pico in one easy step?

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
martincho
Posts: 96
Joined: Mon May 16, 2022 9:59 pm

How to brick your Pico in one easy step?

Post by martincho » Mon Jul 04, 2022 9:32 pm

This is a continuation of a prior post. I just wanted to make it stand on its own with a clear description of the problem for anyone to reproduce.

MicroPython v1.18 on Pico

Code: Select all

from time import sleep, ticks_ms, ticks_diff

import micropython
import machine

def main():
    led0 = machine.Pin(25, machine.Pin.OUT)
    
    last_update = ticks_ms()
    while True:
        led0.toggle()
        if ticks_diff(ticks_ms(), last_update) > 1000:
            last_update = ticks_ms()
            print(".", end="")

main()
Install using Thonny. Get a terminal program ready to go, for example PuTTY. Close Thonny. Reset the Pico.

The LED should light up (it's actually blinking very fast).

Use PuTTY to connect. You'll see a period printed once per second. All is well.

Disconnect and fire-up Thonny to edit the file. Add a single line just above main():

Code: Select all

from time import sleep, ticks_ms, ticks_diff

import micropython
import machine

@micropython.native
def main():
    led0 = machine.Pin(25, machine.Pin.OUT)
    
    last_update = ticks_ms()
    while True:
        led0.toggle()
        if ticks_diff(ticks_ms(), last_update) > 1000:
            last_update = ticks_ms()
            print(".", end="")

main()
Save it. Close Thonny. Get PuTTY ready. Reboot Pico. Try to connect.

On Windows you get an error message from PuTTY saying it cannot connect. On Linux (using minicom or something else) it just doesn't connect.

Launch Thonny again. Can't connect.

You can reset the board all you want, you will not be able to connect to it in any way, Windows or Linux. It is, for all intents and purposes no different from bricked.

The only way to get it back is to use the flash_nuke.uf2 utility to clear out flash (thanks jimmo for the link):

https://learn.adafruit.com/intro-to-rp2 ... f2-3083182

One way to partially fix it is to add a delay. I used sleep(15) before main runs. With this you can connect to the USB serial port during that period and all is well. If you wait until the loop starts running you will not be able to connect. Remove @micropython.native and everything works as expected.

BTW, I ran the same tests under v1.19.1. Same problem.

My hypothesis is that 'native is causing some of the interrupts to not configure correctly. On my application the UARTs don't seem to be working reliably as well. Again, adding a delay allows for connection during that delay and everything works, not after.

I'd like some feedback on this before raising the issue. One question being: Does it happen on boards other than Pico? I don't have any, so I can't test.

Thanks.

-Martin

User avatar
jimmo
Posts: 2754
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia
Contact:

Re: How to brick your Pico in one easy step?

Post by jimmo » Tue Jul 05, 2022 12:34 am

martincho wrote:
Mon Jul 04, 2022 9:32 pm
It is, for all intents and purposes no different from bricked.
Just to be clear, this isn't "bricked" in the way the term is conventionally used as it can be trivially recovered via the bootloader. What you have done is flash the equivalent of a "while(1) {}" loop if you were using the Pico-SDK directly.

"Bricked" would mean, functionally equivalent to, well, a brick. And permanently so.

This is one, I guess, unfortunate feature of the Pico that it doesn't have any buttons other than the "boot" button (not even a reset button!). On the STM32 port for example, we provide a mechanism at boot to activate MicroPython's "safe mode" which lets you bypass running main.py by holding the user button at startup. (You can also activate the bootloader, or force a filesystem reset via this mechanism too).
martincho wrote:
Mon Jul 04, 2022 9:32 pm
Remove @micropython.native and everything works as expected.
Yes this is the key. I realise this could be better documented but the native emitter does not do pending (asynchronous) exception handling or scheduled task execution. These things only happen while the bytecode VM is running. (I am currently working on documentation so will try and improve things in this area)

The way any of the IDEs or tools (Thonny, mpremote, serial console, etc) interrupt the program is by sending Ctrl-C.

On the rp2 we have a hook in the USB serial driver (see ports/rp2/mphalport.c tud_cdc_rx_cb() ) that detects this and then schedules a pending exception for the main thread (similar how it works on other ports, but see below). Because your entire program is running via the native emitter, there is never a chance to detect the pending exception. (Except if you call sleep, which does do this while it's sleeping).

The idea here is that native code is performance sensitive -- you don't want to be interrupting it for scheduled tasks or the burden of constantly checking the pending exception (also it's tricky to do this because there isn't an equivalent "jump/loop hook" like there is in the VM).

On the STM32 port we have an additional mechanism where a _second_ Ctrl-C can do a more agressive interrupt of the main thread. See ports/stm32/pendsv.c pendsv_kbd_intr(). We should implement this for rp2.

martincho
Posts: 96
Joined: Mon May 16, 2022 9:59 pm

Re: How to brick your Pico in one easy step?

Post by martincho » Tue Jul 05, 2022 1:59 am

jimmo wrote:
Tue Jul 05, 2022 12:34 am
Just to be clear, this isn't "bricked" in the way the term is conventionally used as it can be trivially recovered via the bootloader. What you have done is flash the equivalent of a "while(1) {}" loop if you were using the Pico-SDK directly.

"Bricked" would mean, functionally equivalent to, well, a brick. And permanently so.
Yes, of course. That said. This isn't about Thonny. This is about this code running on a custom board inside a device. In that case, the reset button or a multiple Ctrl-C scenario isn't relevant because there is no such opportunity at all. The device would be bricked as far as the end user would be concerned. They would have to send it back to us to unbrick it.

Also, unless someone is reading this thread and discovers the flash nuke UF2, their device is as good as a brick. You can't do a thing with it.
This is one, I guess, unfortunate feature of the Pico that it doesn't have any buttons other than the "boot" button (not even a reset button!).
We put a reset button on our board and have a bunch of carrier boards for the Pico that add a reset button. I can't imagine why RPi thought not adding one was a good idea. Their new models don't have them either.

That said, that's immaterial, as the use case would be one where there would be no access to a reset button or even the connection of a terminal to the USB port.
the native emitter does not do pending (asynchronous) exception handling or scheduled task execution. These things only happen while the bytecode VM is running. (I am currently working on documentation so will try and improve things in this area)
OK, now I understand. Thanks.
Because your entire program is running via the native emitter, there is never a chance to detect the pending exception. (Except if you call sleep, which does do this while it's sleeping).
This makes me concerned about the use of the native emitter to speed things up. I've had to remove the decorator from a few functions that got too long. Now I am wondering if there's potential danger lurking elsewhere. I mean, pretty much every function and method in this application has the decorator. The only exception to this rule are inline assembler routines.

Everything runs well and has gone through tons of testing. Adding one more level of indirection by having main.py launch app.py's run() function suddenly "bricked" the device. Before that, launching run() by hand surfaced no issues at all. Of course, now I understand that there was a natural delay in that process that allowed pending exceptions to be handled.

I am adding a 1 second delay after configuring all peripherals. It seems a good idea to create a few opportunities for scheduled tasks to run, particularly on startup.
The idea here is that native code is performance sensitive -- you don't want to be interrupting it for scheduled tasks or the burden of constantly checking the pending exception (also it's tricky to do this because there isn't an equivalent "jump/loop hook" like there is in the VM).
Looking for informed rules on where NOT to use the decorator. For example, run() executes a tight loop that, among other things, monitors both serial ports, does packet and command processing and relays messages from one to the other (bidirectionally). It works fine, no real issues. Every packet is CRC checked and we've run millions of packets through with error rates too low to worry about. And this was with the native decorator in place. I have removed it since because of this issue.

I just wonder how 'native relates to servicing serial ports at a rapid rate. In this case I am not using asycio or select (tried both, I get better --more predicable-- performance with what I call a "brute force scheduler"). Like I said, wondering where one should not use 'native at all as a general rule.

User avatar
scruss
Posts: 360
Joined: Sat Aug 12, 2017 2:27 pm
Location: Toronto, Canada
Contact:

Re: How to brick your Pico in one easy step?

Post by scruss » Tue Jul 05, 2022 3:00 am

Looks like a bug, perhaps.

Rather than using the full-on flash_nuke.uf2, there's one which renames main.py here: Bricked MicroPython rescue firmware - Raspberry Pi Forums. At least that way you can recover the contents of the board

martincho
Posts: 96
Joined: Mon May 16, 2022 9:59 pm

Re: How to brick your Pico in one easy step?

Post by martincho » Tue Jul 05, 2022 5:59 am

scruss wrote:
Tue Jul 05, 2022 3:00 am
Looks like a bug, perhaps.

Rather than using the full-on flash_nuke.uf2, there's one which renames main.py here: Bricked MicroPython rescue firmware - Raspberry Pi Forums. At least that way you can recover the contents of the board
That can be very useful too. Thanks for posting the info.


martincho
Posts: 96
Joined: Mon May 16, 2022 9:59 pm

Re: How to brick your Pico in one easy step?

Post by martincho » Tue Jul 05, 2022 6:21 am

Thanks for opening these issues. While nobody like losing pins, this might be necessary. There might be a potential to get clever with existing pins. GPIO 23 and 24 come to mind.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

A universal get-out

Post by pythoncoder » Tue Jul 05, 2022 9:05 am

To add a simple practical fix. I've encountered various scenarios with assorted MicroPython targets where application code makes it difficult to break in. The fix I use is to have main.py look like this:

Code: Select all

import time
time.sleep(4)
import my_app
That ensures I have a few seconds between power up and the board locking up where I can access the board with rshell - I can then edit main.py.
Peter Hinch
Index to my micropython libraries.

Post Reply