Hard fault with combination of ESP32SPI and wifi monitor

オープン
#11,710 コメント 6 件 リアクション 0 件 担当者 1 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@dhalbert がすでに取り組んでいます。

2026年11月2日 から。

評価

この issue はまだ評価されていません。

説明

bug network
CircuitPython version and board name
Adafruit CircuitPython 11.0.0-alpha.2-7-ge6bbfee76e on 2026-10-23; FeatherS3 with ESP32S3
(with SPI Airlift FeatherWing)

Adafruit CircuitPython 11.0.0-alpha.2-1-gf2acd4502a on 2026-10-22; Adafruit QT Py ESP32-S3 4MB Flash 2MB PSRAM with ESP32S3
(with SPI WIZnet WIZ850io W5500)

Adafruit CircuitPython 11.0.0-alpha.2-1-gf2acd4502a on 2026-10-22; ESP32-C5-DevKitC-1-N8R8 with ESP32C5
(with SPI WIZnet WIZ850io W5500)
Code/REPL
import time
import os
import random
import traceback
import json
import board
import busio
import digitalio
import supervisor
import microcontroller
from microcontroller import watchdog as w
from watchdog import WatchDogMode
import wifi
import ssl
import adafruit_connection_manager
import adafruit_requests


# set one active configured interface...
active = "eth"  
UART = active == "uart"
ETH = active == "eth"
ESP = active == "esp"

# if multiple WIZnet devices, set lowest octet to avoid DHCP conflicts
OCTET = "24"  # "ed", "cc", "d0", "24"


def uart_config():
    uart = busio.UART(board.TX, board.RX, baudrate=1_000_000, timeout=0.1, receiver_buffer_size=65535)
    return uart

def eth_config():
    if "ESP32-S3" in os.uname().machine:
        spi = board.SPI()
        cs = digitalio.DigitalInOut(board.A0)
        rst = digitalio.DigitalInOut(board.A1)
    elif "ESP32-C5" in os.uname().machine:
        spi = busio.SPI(board.IO8, MOSI=board.IO10, MISO=board.IO9)
        cs = digitalio.DigitalInOut(board.IO1)
        rst = digitalio.DigitalInOut(board.IO0)
    return WIZNET5K(spi, cs, reset=rst, mac=f'de:ad:be:ef:fe:{OCTET}', debug=False)

def esp_config():
    spi = board.SPI()
    esp32_cs = digitalio.DigitalInOut(board.D13)
    esp32_reset = digitalio.DigitalInOut(board.D12)
    esp32_ready = digitalio.DigitalInOut(board.D11)
    while True:
        try:
            radio = ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset, debug=False)
            break
        except Exception as ex:
            traceback.print_exception(ex, ex, ex.__traceback__)
    return radio

def connect(radio):
    if radio.__class__.__name__ == "WIZNET5K":
        ipv4_str = radio.pretty_ip(radio.ip_address)
    elif radio.__class__.__name__ == "ESP_SPIcontrol":
        while not radio.is_connected:
            try:
                radio.connect_AP(os.getenv("WIFI_SSID"), os.getenv("WIFI_PASSWORD"))
            except Exception as ex:
                traceback.print_exception(ex, ex, ex.__traceback__)
                time.sleep(1)
        ipv4_str = radio.ipv4_address
    return ipv4_str

def do_uart():
    print(f'{time.monotonic():.1f} UART writing...', end=' ')
    start = time.monotonic()
    written = uart.write(outbuf)
    print(f'{written} bytes {(time.monotonic() - start):.3f}s')

    print(f'{time.monotonic():.1f} UART reading...', end=' ')
    start = time.monotonic()
    read = uart.readinto(inbuf)
    print(f'{read} bytes {(time.monotonic() - start):.3f}s')

def do_radio():
    # choose your own URL adventure...
    url = f'http://192.168.5.48/hello?{config}'
    # url = f'https://httpbin.org/post'
    print(f'{time.monotonic():.1f} {config} POSTing {len(outbuf)} bytes')
    start = time.monotonic()
    try:
        with requests.post(url, json={'data': outbuf.decode()}, headers={'user-agent': 'Minimal'}) as r:
            inbuf = r.content
            end = time.monotonic()
            print(f'{time.monotonic():.1f} {(end - start):.3f}s {r.status_code} {r.reason.decode()}')
    except TimeoutError as ex:  # TimeoutError: ESP32 not responding
        end_sec = time.monotonic()
        print(f'{time.monotonic():.1f} {(end - start):.3f}s Exception, resetting network and continuing...')
        radio.reset()
        time.sleep(1)
    except Exception as ex:
        end_sec = time.monotonic()
        print(f'{time.monotonic():.1f} {(end - start):.3f}s Exception, continuing...')
        traceback.print_exception(ex, ex, ex.__traceback__)

def restart_notify():
    # choose your own URL adventure...
    url = f'http://192.168.5.48/hello?{config}&reset={str(microcontroller.cpu.reset_reason).split(".")[-1]}&run={str(supervisor.runtime.run_reason).split(".")[-1]}'
    start = time.monotonic()
    try:
        with requests.get(url, headers={'user-agent': 'Minimal'}) as r:
            end = time.monotonic()
            print(f'{time.monotonic():.1f} {(end - start):.3f}s {r.status_code} {r.reason.decode()}')
    except TimeoutError as ex:  # TimeoutError: ESP32 not responding
        end_sec = time.monotonic()
        print(f'{time.monotonic():.1f} {(end - start):.3f}s Exception, resetting network and continuing...')
        radio.reset()
        time.sleep(1)
    except Exception as ex:
        end_sec = time.monotonic()
        print(f'{time.monotonic():.1f} {(end - start):.3f}s Exception, continuing...')
        traceback.print_exception(ex, ex, ex.__traceback__)


time.sleep(3)  # wait for serial
print("=" * 25)

uart = radio = config = None
if UART:
    print(f'Initializing UART...')
    uart = uart_config()
elif ETH:
    from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K

    print(f'Initializing Ethernet...')
    radio = eth_config()
    config = "+".join((os.uname().sysname.split("ESP32")[1], radio.__class__.__name__))
    print(f'{config} {connect(radio)}')
elif ESP:
    from adafruit_esp32spi.adafruit_esp32spi import ESP_SPIcontrol

    print(f'Initializing ESP32SPI...')
    radio = esp_config()
    print(f'Connecting to AP with ESP32SPI...')
    esp_ipv4 = connect(radio)
    config = "+".join((os.uname().sysname.split("ESP32")[1], "ESP32SPI"))
    print(f'{config} {connect(radio)}')

if ETH or ESP:
    print(f'Initializing requests...')
    pool = adafruit_connection_manager.get_radio_socketpool(radio)
    context = adafruit_connection_manager.get_radio_ssl_context(radio)
    requests = adafruit_requests.Session(pool, context)

print(f'Initializing watchdog...')
w.timeout = 90
w.mode = WatchDogMode.RESET

print(f'Notifying server of restart...')
restart_notify()

# use for UART, ETH, or ESP
outbuf = bytearray(b'\x5A') * 1024
inbuf = bytearray(1024)

packets = 0
m = wifi.Monitor(queue=1024)  # default channel=1
while True:
    w.feed()
    print("-" * 25)
    time.sleep(1)
    print(f'{time.monotonic():.1f} {m.queued()=} {m.lost()=}')
    p = m.packet()
    while p:  # and (m.queued() > 50):
        packets += 1
        p = m.packet()
        if random.randint(0, 25) == 0:
            if uart:
                do_uart()
            else:
                do_radio()
            print(f'{time.monotonic():.1f} {m.queued()=} {m.lost()=}')
    print(f'{time.monotonic():.1f} {m.queued()=} {m.lost()=}')
Behavior

CircuitPython firmware used is post- #11651 (includes the fix for wifi monitor re-init hard faults). ESP32SPI, WIZnet, or wifi monitor alone also don't trigger hard faults or watchdog... something about the combination (SPI-related, perhaps; the ESP32SPI and WIZnet libraries don't really do much heavy core stuff other than busio SPI).

Reproducer code is larger than I'd like, but I tried many simpler versions that didn't trigger hard faults or watchdog. Seemingly unrelated code changes can change the behavior, even make hard faults or watchdog timeouts go away, so it's been challenging to come up with a single code.py that will trigger on all three configs that contained what I think are the salient features. It is also possible that there is more than one issue across S3 vs C5, and ESP32SPI vs WIZnet.

Before putting in the watchdog code, the behavior was a permanent "freeze"...serial output stopped, no control-c possible, but MSC (S3, not C5) was still available. Hard reset was required. I may not have watchdog tuned quite right, still testing.

Code changes may also cause hard fault to become watchdog, or vice versa. It seems to be easier to trigger hard faults with longer, more complex code. Likewise, it seems harder to trigger hard faults with shorter, simpler code. Hard fault or watchdog seem to occur during an HTTP POST from the client point of view. The server will occasionally give a 400, 408, or 500 response with an exception indicating an incomplete request.

With the code above:

  • S3 + ESP32SPI will hard fault in about 1 minute
  • S3 + WIZnet will watchdog in 5-15 minutes
  • C5 + WIZnet will watchdog in under 30 seconds (longer, given the addendum in Additional Information below)

The hard fault on the S3+ESP32SPI is the easiest to replicate with the above code.

I also tested UART (thinking it might be related to interrupts or some general busio issue), but did not see any hard faults or watchdog timeouts.

To test on hardware: there are a couple of the constants at the top of the code.py that may need to be tweaked, depending on testing config. Also the URL(s) used can (I think) be any HTTP server you can POST to. I mainly used a local server to capture the server log (and intervals between crashes), but also verified behavior with httpbin.org/post (commented out due to frequency of POSTs).

Here is almost 45 minutes of server log showing the hard faults and watchdogs:

access.log
192.168.9.208 - - [01/Nov/2026:17:43:52 -0600] "GET /hello?S3+WIZNET5K&reset=POWER_ON&run=STARTUP HTTP/1.1" 200 242 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:17:43:54 -0600] "GET /hello?S3+ESP32SPI&reset=POWER_ON&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:17:43:56 -0600] "GET /hello?C5+WIZNET5K&reset=POWER_ON&run=STARTUP HTTP/1.1" 200 243 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:17:44:51 -0600] "GET /hello?S3+ESP32SPI&reset=SOFTWARE&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:17:45:40 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:17:46:53 -0600] "GET /hello?S3+ESP32SPI&reset=SOFTWARE&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:17:47:27 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:17:48:47 -0600] "GET /hello?S3+ESP32SPI&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:17:49:11 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.208 - - [01/Nov/2026:17:49:48 -0600] "GET /hello?S3+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 245 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:17:50:46 -0600] "GET /hello?S3+ESP32SPI&reset=SOFTWARE&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:17:50:54 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:17:52:41 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:17:53:14 -0600] "GET /hello?S3+ESP32SPI&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:17:54:24 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:17:55:21 -0600] "GET /hello?S3+ESP32SPI&reset=SOFTWARE&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:17:55:56 -0600] "GET /hello?S3+ESP32SPI&reset=SOFTWARE&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:17:56:08 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.208 - - [01/Nov/2026:17:56:49 -0600] "GET /hello?S3+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 245 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:17:57:51 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:17:58:11 -0600] "GET /hello?S3+ESP32SPI&reset=SOFTWARE&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:17:59:34 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:18:00:11 -0600] "GET /hello?S3+ESP32SPI&reset=SOFTWARE&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:18:01:18 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:18:02:28 -0600] "GET /hello?S3+ESP32SPI&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:18:03:01 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:18:03:37 -0600] "GET /hello?S3+ESP32SPI&reset=SOFTWARE&run=STARTUP HTTP/1.1" 200 242 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:18:04:24 -0600] "GET /hello?S3+ESP32SPI&reset=SOFTWARE&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:18:04:45 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 243 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:18:06:06 -0600] "GET /hello?S3+ESP32SPI&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:18:06:28 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:18:07:16 -0600] "GET /hello?S3+ESP32SPI&reset=SOFTWARE&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:18:08:15 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.208 - - [01/Nov/2026:18:08:51 -0600] "GET /hello?S3+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 245 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:18:09:44 -0600] "GET /hello?S3+ESP32SPI&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:18:09:59 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:18:10:48 -0600] "GET /hello?S3+ESP32SPI&reset=SOFTWARE&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:18:11:42 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:18:13:09 -0600] "GET /hello?S3+ESP32SPI&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:18:13:25 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:18:15:00 -0600] "GET /hello?S3+ESP32SPI&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 242 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:18:15:11 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.208 - - [01/Nov/2026:18:15:54 -0600] "GET /hello?S3+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 245 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:18:16:54 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:18:17:04 -0600] "GET /hello?S3+ESP32SPI&reset=SOFTWARE&run=STARTUP HTTP/1.1" 200 243 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:18:17:53 -0600] "GET /hello?S3+ESP32SPI&reset=SOFTWARE&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:18:18:39 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:18:19:54 -0600] "GET /hello?S3+ESP32SPI&reset=SOFTWARE&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:18:20:22 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:18:20:39 -0600] "GET /hello?S3+ESP32SPI&reset=SOFTWARE&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:18:22:16 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 243 "-" "Minimal"
192.168.9.208 - - [01/Nov/2026:18:22:27 -0600] "GET /hello?S3+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 245 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:18:22:42 -0600] "GET /hello?S3+ESP32SPI&reset=SOFTWARE&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"
192.168.9.36 - - [01/Nov/2026:18:23:59 -0600] "GET /hello?C5+WIZNET5K&reset=WATCHDOG&run=STARTUP HTTP/1.1" 200 243 "-" "Minimal"
192.168.6.43 - - [01/Nov/2026:18:24:52 -0600] "GET /hello?S3+ESP32SPI&reset=SOFTWARE&run=STARTUP HTTP/1.1" 200 244 "-" "Minimal"

"reset=WATCHDOG" in the log is clear enough. "reset=SOFTWARE" indicates a hard fault in these cases. It takes more code to capture the safemode reason (it must be done in safemode.py and passed to code.py using a file, nvm, or alarm memory), and I stripped that out of this version.

Description

No response

Additional information

addendum: I have a slight error in the posted code (different from many prior iterations) where the watchdog should be fed within the while p: loop, but I don't think this fully explains the watchdogs, especially since there were freezes before the watchdog code. Responses are typically about a half second, pretty consistently, and the monitor queue drains in 5-10 seconds on S3+ESP32SPI (allowing the watchdog to be fed). I put the watchdog inside the loop now, and adjusted the random.randint() upwards (to 50 on the C5) to keep the monitor queue from growing too fast, and the watchdog timeouts still occur during POST. I'll continue to test variations and add anything that may help.

(Some previous WIZnet-specific comments on this issue were in #11472)

主要言語
C
スター
4.6k
フォーク
1.4k
平均マージ
1日 9時間
マージ済み PR(30日)
173

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

adafruit/circuitpython のほかの issue

adafruit/circuitpython の issue をすべて見る

似ている issue

C の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。