Interrupting soft reset (Ctrl-D) with Ctrl-C erases filesystem on Raspberry Pi Pico
Sending a Ctrl-D to the MicroPython REPL to initiate a soft reset and then immediately issuing a Ctrl-C causes the entire filesystem to be erased on the Raspberry Pi Pico board. This issue was observed on 2 separate Pico boards across the following nightly builds (the 4 latest as of 01 March 2023):
However, the bug was not observed in the latest stable version 1.19.1.
Steps to reproduce
These steps were tested on a Linux host (Ubuntu 22.04).
First, load one of the above nightly versions of MicroPython onto a Raspberry Pi Pico board.
Next, using mpremote, install a library on the Pico’s filesystem, for example hashlib:
mpremote mip install hashlib
Then check that the library exists on the Pico’s filesystem:
mpremote fs ls /lib
This should show the hashlib folder:
ls :/lib
0 hashlib/
Then, to send the required keystrokes (Ctrl-D followed by Ctrl-C), execute the following lines of Python on the Linux host machine to which the Pico is connected, replacing /dev/ttyACM0 with the serial port of the MicroPython REPL. This script requires pyserial.
import serial
s = serial.Serial('/dev/ttyACM0')
s.write(b'\x04') # Ctrl-D
s.write(b'\x03') # Ctrl-C
s.close()
Less precisely, the issue can also be reproduced by entering a REPL with mpremote and pressing Ctrl-D and Ctrl-C in rapid succession.
Then check the Pico’s filesystem once again:
mpremote fs ls
This should show that the lib folder and all files it contains have been removed:
ls :
This issue became evident because the visual studio code extension Pico-W-Go exhibits this behaviour when running a MicroPython file, issuing a Ctrl-D and Ctrl-C in rapid succession before running the given code. This issue has been reported on that project’s GitHub page here, suggesting it affects other nightly versions older than the 4 listed above.
pico - zombie pio state machines after soft reset
version:
>>> import sys
>>> sys.implementation
(name='micropython', version=(1, 19, 1), _machine='Raspberry Pi Pico with RP2040', _mpy=4102)
Problem description: pio sm instances can be unexpectedly resucitated after a soft reset
More detailed description:
- instantiate and activate many pio state machines with the same pio program
- call machine.soft_reset()
- instantiate and activate only one sm, with the same pio program
- Expected: only one sm activelly driven his output
- Observed: all sm with same pio program will be activelly driven their outputs
The demo script expects to have led + resistor in gpios 10..13
It will instantiate and activate four sm, each blinling a led, do a soft reset, instantiate and activate only one sm, do a hard reset and repeat.
Expected 5 seconds of four led blinking, followed by 5 seconds of only one led blinking, repeat
Observed always four leds blinking
See docstring for instuctions to run
demo script: pio_and_machine_reset.py
""" demoing zombie state machines after soft reset
Using a pico lean and mean, not W or other variants
HW: led + resistor on gpios 10..13
Shoul do: blink the four leds 5 seconds, then blink only the led in gpio 13
Observed: four leds blinking always
Operation:
- if not first run, ensure the file 'soft_reset_flag.txt' does not
exist in the pico
- Close Thonny or other runner / debugger to discard other sw interference
- Unplug and plug the pico to ensure a hard reset
- Use minicom or Bootterm ( bt ) to get the serial output from the pico
"""
from machine import Pin
from rp2 import PIO, StateMachine, asm_pio
import time
def report_state_machines():
for p in (0, 1):
for sm_c in range(4):
fmt = "rp2.PIO(%d).state_machine(%d).active(): %s"
print( fmt % (p, sm_c, rp2.PIO(p).state_machine(sm_c).active()))
class FSFlag:
def __init__(self, name):
self.name = name
self.fname = name + "_flag.txt"
def is_flag_set(self):
present = False
try:
f = open(self.fname, "r")
present = True
f.close()
except Exception:
present = False
return present
def set_(self):
f = open(self.fname, "w")
f.write("flag file")
f.close()
def clear(self):
import os
if self.is_flag_set():
os.remove(self.fname)
def value(self, val=None):
if val is None:
return self.is_flag_set()
elif val:
self.set_()
else:
self.clear()
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink():
set(pins, 1) [31]
nop() [31]
nop() [31]
nop() [31]
nop() [31]
set(pins, 0) [31]
nop() [31]
nop() [31]
nop() [31]
nop() [31]
def blink_1_led(dt):
print("only led on gpio 13 should blink")
print("only SM 3 should be active")
sm = StateMachine(3, blink, freq=2000, set_base=Pin(13, Pin.OUT, value=0))
sm.active(1)
report_state_machines()
time.sleep(dt)
def blink_4_leds(dt):
print("leds in gpios (10, 11, 12, 13) should blink")
print("SMs 0..4 should be active")
gpios = (10, 11, 12, 13)
py_sm_ids = (0, 1, 2, 3)
pairs = [(id_, Pin(gp, Pin.OUT, value=0)) for id_, gp in zip(py_sm_ids, gpios)]
sms = {id_: StateMachine(id_, blink, freq=2000, set_base=pin) for id_, pin in pairs}
for sm in sms.values():
sm.active(1)
report_state_machines()
time.sleep(dt)
print("Comming Alive")
report_state_machines()
soft_reset = FSFlag("soft_reset")
if soft_reset.value():
print("after soft reset")
blink_1_led(5)
soft_reset.value(0)
machine.reset()
else:
print("after hard reset")
blink_4_leds(5)
soft_reset.value(1)
machine.soft_reset()
Additional info, not demoed but can easily show with variants on the demo code:
- always after soft reset all the sm active before the reset are shown as active by
rp2.PIO(p).state_machine(sm_c).active() - if no sm is instantiated after the soft reset, no pin activity shows in the pins controlled by the old sm's
- if a pio sm with a different pio program is instantiated after the soft reset, no pin activity shows in the pins controlled by the old sm's
- if a pio sm with same pio program is activated after the soft reset, the old sm's activelly drive their pins
This issue is probably related to
https://forums.raspberrypi.com/viewtopic.php?t=340099
https://github.com/thonny/thonny/issues/2455