adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
Other
4.07k stars 1.2k forks source link

RP2040 Prop Maker becomes non-writable #9473

Closed dolson56 closed 1 month ago

dolson56 commented 2 months ago

CircuitPython version

CP9.1.1
Boot_out.txt:
Adafruit CircuitPython 9.1.1 on 2024-07-22; Adafruit Feather RP2040 Prop-Maker with rp2040
Board ID:adafruit_feather_rp2040_prop_maker
UID:DF625857C7736034

Code/REPL

any code. the issue is in saving to the micro
# SPDX-FileCopyrightText: 2020 Jerry Needell for Adafruit Industries
# SPDX-License-Identifier: MIT

# Example to send a packet periodically between addressed nodes

import adafruit_ds3231
from time import mktime

import time
import board
import busio
import digitalio
import adafruit_rfm69
import displayio
import rotaryio
import terminalio
from adafruit_display_text import label
import adafruit_displayio_ssd1306
from i2cdisplaybus import I2CDisplayBus
import microcontroller
from math import atan2, degrees
import adafruit_lis3mdl
from audiocore import WaveFile

# for I2S audio with external I2S DAC board
import audiobusio

# I2S audio on PropMaker Feather RP2040
power = digitalio.DigitalInOut(board.EXTERNAL_POWER)
power.switch_to_output(value=True)
audio = audiobusio.I2SOut(board.I2S_BIT_CLOCK, board.I2S_WORD_SELECT, board.I2S_DATA)

wave_file = open("LeFreak.wav", "rb")
wave = WaveFile(wave_file)
playing = False

oled_reset = board.D4
i2c = board.I2C()
ds3231 = adafruit_ds3231.DS3231(i2c)
months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec")
currentTime  = ds3231.datetime

def which_day(t):
    # Lookup table for names of days (nicer printing).
    days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
    this_day = days[int(t.tm_wday)]
#    print("function which_day thinks the weekday is {}".format(this_day))
    count = "first"
    if int(t.tm_mday)<8:
        count = "first"
    elif int(t.tm_mday) < 15:
        count = "second"
    elif int(t.tm_mday) < 22:
        count = "third"
    elif int(t.tm_mday) < 29:
        count = "fourth"
    elif int(t.tm_mday) < 32:
        count = "fifth"
    else:
        count = "error"

    return count, this_day

i2c = board.I2C()  # uses board.SCL and board.SDA
displayio.release_displays()
display_bus = I2CDisplayBus(i2c, device_address=0x3C)
WIDTH = 128
HEIGHT = 64  # This is 32 in smaller displays
BORDER = 5
display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=WIDTH, height=HEIGHT)

# Make the display context
group = displayio.Group()
display.root_group = group

# Draw a labels
labelDirection = label.Label(terminalio.FONT, text="Direction", color=0xFFFFFF, x=10, y=15)
group.append(labelDirection)
sigLabel = label.Label(terminalio.FONT, text = "Sig Strength", color=0xFFFFFF, x=10, y=30)
group.append(sigLabel)
labelLastRx = label.Label(terminalio.FONT, text = "Last Rx", color=0xFFFFFF, x=10, y=45)
group.append(labelLastRx)

RADIO_FREQ_MHZ = 915.0  # Frequency of the radio in Mhz. Must match your
# Define pins connected to the radio chip.
CS = digitalio.DigitalInOut(board.D1)
RESET = digitalio.DigitalInOut(board.D0)

# Initialize SPI bus.
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
# Initialze RFM radio
rfm69 = adafruit_rfm69.RFM69(spi, CS, RESET, RADIO_FREQ_MHZ)

value = microcontroller.nvm[0]
if value == 0:
    unitName = "marco"
#    rfm69.node = 1
#    rfm69.destination = 2
else:
    unitName = "polo"
#    rfm69.node = 2
#    rfm69.destination = 1
print("NVM value = {}".format(value))
print("node = {}".format(rfm69.node))
print("destination = {}".format(rfm69.destination))
# send a broadcast message
#rfm69.send(bytes("S/U from {}".format( rfm69.node), "UTF-8"))
rxTimestamp = 0
#iter = 0

if unitName == "polo":
    sensor = adafruit_lis3mdl.LIS3MDL(i2c)
def vector_2_degrees(x, y):
    x=x-6.8
    x=x/51.78
    y=y+43.9
    y=y/45.97

    angle = degrees(atan2(y, x))
    if angle < 0:
        angle += 360
    return angle

def get_heading(_sensor):
    magnet_x, magnet_y, _ = _sensor.magnetic
    return vector_2_degrees(magnet_x, magnet_y)

def Get_Compass_Rounded(Deg):
    if (Deg>305 or Deg<46):
        Dir = "South"
    elif (Deg>45 and Deg <136):
        Dir = "West"
    elif (Deg>135 and Deg<226):
        Dir = "North"
    elif (Deg>225 and Deg <315):
        Dir = "East"

    else:
        Dir = "Error"
    return Dir

#We are not using the rotary encoder or it's button
RotaryButton = digitalio.DigitalInOut(board.D9)
RotaryButton.direction = digitalio.Direction.INPUT
RotaryButton.pull = digitalio.Pull.UP

encoder = rotaryio.IncrementalEncoder(board.D11, board.D10)
last_position = None

# the button on the outdoor unit is on D12
# on the indoor unit, it is D13
if unitName == "polo":
    AlarmButton = digitalio.DigitalInOut(board.D12)
    ButtonLight = digitalio.DigitalInOut(board.D13)
else:
    AlarmButton = digitalio.DigitalInOut(board.D13)
    ButtonLight = digitalio.DigitalInOut(board.D12)

AlarmButton.direction = digitalio.Direction.INPUT
AlarmButton.pull = digitalio.Pull.UP

ButtonLight.direction = digitalio.Direction.OUTPUT
discoBall = digitalio.DigitalInOut(board.D6)
discoBall.direction = digitalio.Direction.OUTPUT

def checkNorthAlarm(todayStruct):
    #North days are 2nd and 4th Thursdays
    count, day = which_day(todayStruct)
    #print("checkNorthAlarm says day={}, count={}".format(day, count))
    if((day == "Thursday") and (count == "second" or count == "fourth")):
        todayIsAlarmDay = True
    else:
        todayIsAlarmDay = False
    #print("checkNorthAlarm says todayIsAlarmDay = {}".format(str(todayIsAlarmDay)))
    #print("checkNorthAlarm says alarmDay = {}".format(str(todayIsAlarmDay )))
    #print("checkNorthAlarm says hour = {}".format(todayStruct.tm_hour))
    if (todayStruct.tm_hour<12 and todayIsAlarmDay):
       return True

    todayInt  = time.mktime(currentTime)
    tomorrowInt = todayInt + 86400
    tomorrowStruct = time.localtime(tomorrowInt)
    count, day = which_day(tomorrowStruct)
    if((day == "Thursday") and (count == "second" or count == "fourth")):
        tomorrowIsAlarmDay = True
    else:
        tomorrowIsAlarmDay = False
    if (todayStruct.tm_hour>12 and tomorrowIsAlarmDay):
       return True
    else:
        return False
def checkSouthAlarm(todayStruct):
    #South days are 2nd and 4th Wednesdays
    count, day = which_day(todayStruct)
    #print("checkSouthAlarm says day={}, count={}".format(day, count))
    if((day == "Wednesday") and (count == "second" or count == "fourth")):
        todayIsAlarmDay = True
    else:
        todayIsAlarmDay = False
    #print("checkSouthAlarm says alarmDay = {}".format(str(todayIsAlarmDay )))
    #print("checkSouthAlarm says hour = {}".format(todayStruct.tm_hour))
    if (todayStruct.tm_hour<12 and todayIsAlarmDay):

       return True

    todayInt  = time.mktime(currentTime)
    tomorrowInt = todayInt + 86400
    tomorrowStruct = time.localtime(tomorrowInt)
    count,day = which_day(tomorrowStruct)
    if((day == "Wednesday") and (count == "second" or count == "fourth")):
        tomorrowIsAlarmDay = True
    else:
        tomorrowIsAlarmDay = False
    if (todayStruct.tm_hour>12 and tomorrowIsAlarmDay):
       return True
    else:
        return False

def checkDirection():
    print("Update Direction")
    #Only the outside unit, "polo" has a magnetometer (compass) module
    if unitName == "polo":
        Dir = get_heading(sensor)
        #print("heading: {:.2f} degrees".format(Dir))
        direction = Get_Compass_Rounded(Dir)
        #print("Direction: {}".format(direction))+-
    else:
        direction = "none"
    #print(direction)
    return direction

def updateTime():
    #print("Update Time")
    currentTime  = ds3231.datetime
#    currentTime  = time.struct_time((2024, 7, 24, 10, 30, 0, 0, -1, -1))
    return currentTime
def updateDisplay():
    print("Update Display")
    labelDirection.text  = "Dir {}".format(direction)
    #sigLabel = "Sig={.2f}".format(lastRSSI)
    sigLabel.text = str(lastRSSI)
    timeStruct = time.localtime(timeSinceLastRadioContact)
    #labelLastRx.text = "hello"
    #labelLastRx.text = "Last={:02}:{:02}:{:02}".format(timeStruct.tm_hour, timeStruct.tm_min, timeStruct.tm_sec)
def radioRxTx():
    print("Update Radio")
    # when it is inside sending to outside (marco to polo, we send "ping"
    #when it is outside sending to inside(polo to marco), we send the
    # compass direction
    if unitName == "marco":
        rfm69.send("ping")
        print("sending")
    if unitName == "polo":
        global direction
        rfm69.send(direction)
        print("direction = {}".format(direction))
        #direction
    packet = rfm69.receive()
    # If no packet was received during the timeout then None is returned.
    #we don't assign "none" to the direction here#we assume that some receives will
    #not be successful, but that is OK. Too soon to say that we are disconnected
    #we handle the choice to say we're disconnected elsewhere
    if packet is not None:
        print("RSSI: {0}".format(rfm69.last_rssi))
        lastRSSI = rfm69.last_rssi
        packet_text = str(packet, "ascii")
        rxTimestamp = time.mktime(ds3231.datetime)
        print(packet_text)
        if unitName == "marco":
            direction = packet_text
        return rfm69.last_rssi

    else:
        return 0
connected = False
direction = "x"
northAlarm = False
southAlarm = False
State = "Idle"
lastRSSI = 0.0
radioTimestamp = 0
clockTimestamp = 0
displayTimestamp = 0
directionTimestamp = 0
directionTimeout = 1
radioTimeout = 2
#clockTimeout = 10
displayTimeout = 3
radioTimeoutRemaining = -1
#clockTimeoutRemaining = -1
displayTimeoutRemaining = -1
directionTimeoutRemaining = -1
radioConnected = False
maxSecondsconnected = 30
timeSinceLastRadioContact = 0
while True:
    #check timeouts
    currentTime  = ds3231.datetime
    radioTimeoutRemaining = radioTimeout- (time.mktime(currentTime) - radioTimestamp)
    directionTimeoutRemaining = directionTimeout - (time.mktime(currentTime) - directionTimestamp)
    #print("radio Timeout remaining = {}".format(radioTimeoutRemaining))
    #print("radio Time Stamp = {}".format(radioTimestamp))
    #print("Current time = {}".format(time.mktime(currentTime)))
    #time.sleep(1)
    #clockTimeoutRemaining = clockTimeout - (time.mktime(currentTime) - clockTimestamp)
    displayTimeoutRemaining = displayTimeout - (time.mktime(currentTime) - displayTimestamp)
    #print("display Timeout remaining = {}".format(displayTimeoutRemaining))
    #print("display Time Stamp = {}".format(displayTimestamp))
    timeSinceLastRadioContact = time.mktime(currentTime) - rxTimestamp
    if timeSinceLastRadioContact < maxSecondsconnected:
        radioConnected = True
    else:
        radioConnected = False

    if displayTimeoutRemaining < 0:
        labelLastRx.text = "hello"
        updateDisplay()
        displayTimestamp = time.mktime(currentTime)
 #   if clockTimeoutRemaining < 0:
        # call handler(s)
 #       currentTime = updateTime()
 #       clockTimestamp = time.mktime(currentTime)
    northAlarm = checkNorthAlarm(currentTime)
    southAlarm = checkSouthAlarm(currentTime)
    if directionTimeoutRemaining <0:
        direction = checkDirection()
        #print("direction ={}".format(direction))
        directionTimestamp = time.mktime(currentTime)

    if radioTimeoutRemaining < 0:
        resultRSSI = radioRxTx()
        if resultRSSI > 0:
            lastRSSI = resultRSSI
        radioTimestamp = time.mktime(currentTime)

    if State == "Alarming":
        #restart sound if needed
        if audio.playing == False:
            audio.play(wave)
        #if alarm state clears, we go to idle
        if connected and ((southAlarm and direction == "South") or (northAlarm and direction == "North")):
            #still alarming
            pass
        else:
            State = "Idle"
            AlarmButtonLight.value = False
            audio.stop()
        #if user presses button, we go to Dismissed
        if AlarmButton.value == False:
            State == "Dismissed"
            audio.stop()
    elif State == "Dismissed":
        #if alarm state clears, we go to idle
        if connected and ((southAlarm and direction == "South") or (northAlarm and direction == "North")):
            #still alarming
            pass
        else:
            State = "Idle"
            AlarmButtonLight.value = False

    else:
        if connected and southAlarm and direction == "South":
            State = "Alarming"
            print("South Alarming")
            #start disco ball
            discoBall = True
            AlarmButtonLight.value = True
            audio.play(wave)

        if connected and northAlarm and direction == "North":
            State = "Alarming"
            print("North Alarming")
            #start disco ball
            discoBall = True
            AlarmButtonLight.value = True
            audio.play(wave)

        if AlarmButton.value == False:
            discoBall = True
            AlarmButtonLight.value = True
            audio.play(wave)
            time.sleep(5)
            AlarmButtonLight.value = False
            audio.stop()
    time.sleep(0.01)

Behavior

Any code can be used to reproduce. The issue is the micro becomes un-writable. Furious, random unplugs and restarts of Mu can sometimes briefly recover the write-permission. No discernable pattern yet. It also helps if you curse out loud while doing this.

Description

Over time, the micro becomes more and more likly to appear as non-writable. You can't save to it. You see this when developing with Mu, however, you can verify the issue with windows file explorer. Also shows up as non-writable. I have tried to reinstall CP as recommended on CP.org(link) and this has fixed the issue for a time, but the issue re-appears. I have also tried storage.erase_filesystem(), this had no better success than the re-install.
This project involves two systems linked by RFM69 radios. So all troubleshooting includes 2 RP2040's. I have learned late in this troubleshooting that a USB device 'eject' is a good idea for each unplug. I don't have extra RP2040's to try this out with - that is, doing eject each time from time zero to prevent this issue from coming back in the first place.

Additional information

Windows 10 PC. I have a really big doc to describe the project, however I don't think the issue is related to anything but the USB plug and un-plugs. I'll attach the write all the same. Anti Parking Ticket System Description.docx

tannewt commented 2 months ago

Does this still occur if you eject the device before unplugging?

There isn't much we can do from the CircuitPython side if you unplug without eject. Windows caches writes to the file system and unplugging prevents those from completing. Eject will write out those changes.

dhalbert commented 1 month ago

Closing for now since we haven't heard back.