adafruit / circuitpython

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

Metro ESP32-S3 `watchdog timer expired` safe mode when various demos #8288

Closed kattni closed 1 year ago

kattni commented 1 year ago

CircuitPython version

Adafruit CircuitPython 8.2.0-75-gc75640eb0-dirty on 2023-08-15; Adafruit Metro ESP32S3 with ESP32S3 (built from main with SD_CS pin added)

Adafruit CircuitPython 8.1.0-beta.0-255-g7567f7170-dirty on 2023-08-15; Adafruit Metro ESP32S3 with ESP32S3 (built from 8.2.x with SD_CS pin added)

Code/REPL

import busio
import digitalio
import board

spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
cs = digitalio.DigitalInOut(board.SD_CS)

Behavior

On save, the board immediately reboots into safe mode. The code above is the minimal necessary to guarantee safe mode. Safe mode occurs if the code is saved in code.py or run from the REPL.

Auto-reload is off.
Running in safe mode! Not running saved code.

You are in safe mode because:
Internal watchdog timer expired.
Press reset to exit safe mode.

Press any key to enter the REPL. Use CTRL-D to reload.

Description

No response

Additional information

This is the original demo:


import os
import busio
import digitalio
import board
import storage
import adafruit_sdcard

# The SD_CS pin is the chip select line.

SD_CS = board.SD_CS  # setup for microSD BFF

# Connect to the card and mount the filesystem.
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
cs = digitalio.DigitalInOut(SD_CS)
sdcard = adafruit_sdcard.SDCard(spi, cs)
vfs = storage.VfsFat(sdcard)
storage.mount(vfs, "/sd")

# Use the filesystem as normal! Our files are under /sd

# This helper function will print the contents of the SD
def print_directory(path, tabs=0):
    for file in os.listdir(path):
        stats = os.stat(path + "/" + file)
        filesize = stats[6]
        isdir = stats[0] & 0x4000

        if filesize < 1000:
            sizestr = str(filesize) + " bytes"
        elif filesize < 1000000:
            sizestr = "%0.1f KB" % (filesize / 1000)
        else:
            sizestr = "%0.1f MB" % (filesize / 1000000)

        prettyprintname = ""
        for _ in range(tabs):
            prettyprintname += "   "
        prettyprintname += file
        if isdir:
            prettyprintname += "/"
        print("{0:<40} Size: {1:>10}".format(prettyprintname, sizestr))

        # recursively print directory contents
        if isdir:
            print_directory(path + "/" + file, tabs + 1)

print("Files on filesystem:")
print("====================")
print_directory("/sd")
kattni commented 1 year ago

@ladyada FYI.

ladyada commented 1 year ago

what if you use board.SPI()?

kattni commented 1 year ago

I tried board.SPI() as well yesterday, and still ended in safe mode. I verified again today that the following results in the same safe mode reason.

import digitalio
import board
import adafruit_sdcard

cs = digitalio.DigitalInOut(board.SD_CS)
sdcard = adafruit_sdcard.SDCard(board.SPI(), cs)
kattni commented 1 year ago

I tried running an I2S pin identifying script and ended up in the same safe mode. Running the I2S tone demo does not result in safe mode. I can get the pin script to print 8.5 lines of pin combinations before it resets into safe mode.

I don't know enough about what's going on here to know if it's related, but I'm including it here anyway.

Script below.

import board
import audiobusio
from microcontroller import Pin

def is_hardware_i2s(bit_clock, word_select, data):
    try:
        p = audiobusio.I2SOut(bit_clock, word_select, data)
        p.deinit()
        return True
    except ValueError:
        return False

def get_unique_pins():
    exclude = [
        getattr(board, p)
        for p in [
            # This is not an exhaustive list of unexposed pins. Your results
            # may include other pins that you cannot easily connect to.
            "NEOPIXEL",
            "DOTSTAR_CLOCK",
            "DOTSTAR_DATA",
            "APA102_SCK",
            "APA102_MOSI",
            "LED",
            "SWITCH",
            "BUTTON",
        ]
        if p in dir(board)
    ]
    pins = [
        pin
        for pin in [getattr(board, p) for p in dir(board)]
        if isinstance(pin, Pin) and pin not in exclude
    ]
    unique = []
    for p in pins:
        if p not in unique:
            unique.append(p)
    return unique

for bit_clock_pin in get_unique_pins():
    for word_select_pin in get_unique_pins():
        for data_pin in get_unique_pins():
            if bit_clock_pin is word_select_pin or bit_clock_pin is data_pin or word_select_pin \
                    is data_pin:
                continue
            if is_hardware_i2s(bit_clock_pin, word_select_pin, data_pin):
                print("Bit clock pin:", bit_clock_pin, "\t Word select pin:", word_select_pin,
                      "\t Data pin:", data_pin)
            else:
                pass
jepler commented 1 year ago

[synthesizing @tannewt's remarks in the meeting as well as some info from the internal meeting]

The 8MB PSRAM uses additional pins because it supports "octal mode", in which 8 bits are transmitted every clock cycle. CircuitPython doesn't correctly protect these pins, which causes the "pin in use check" not to work.

If the check did work, it would not be possible to construct an SPI object using the pre-defined SPI pins because they overlap with the pins used by the PSRAM. (you'd get a "pin in use" exception). We're checking whether it's possible to work around this by using the flash in quad mode rather than octal mode, so making it possible to use the SD card but this slows down RAM access.

As it is, the code switches the pin over to being GPIO, which stops SPI RAM from working anymore. This leads to the watchdog timeout, because CircuitPython stops working properly.

The board has been pulled from the store for the time being, and is likely to undergo a revision before more are sold, so that the Octal mode can be used, by choosing different SD card pins. So if you have one, it's now become a collector's item :) Because the new revision would have a change in the pinout it'd probably become a separate CircuitPython board definition.

What's interesting is how this passed the factory test, which covers all GPIO pins and also the SPI RAM chip: The tester program sets up the SPI RAM first, checks that it works, then does the GPIO checks. But after the GPIO checks it never uses SPI RAM again, so there's no misbehavior noted.