Skip to content

fix: M5Stack Unit C6L boot failure caused by flash_mode=qio - #3098

Merged
liamcottle merged 1 commit into
meshcore-dev:devfrom
jirogit:fix/unit-c6l-flash-mode-dio
Aug 3, 2026
Merged

fix: M5Stack Unit C6L boot failure caused by flash_mode=qio#3098
liamcottle merged 1 commit into
meshcore-dev:devfrom
jirogit:fix/unit-c6l-flash-mode-dio

Conversation

@jirogit

@jirogit jirogit commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes the M5Stack Unit C6L boot crash-loop (repeated USB-Serial-JTAG
disconnect/reconnect cycling, no serial output ever reaching the host)
that affects every merged.bin built for this board — including the
official upstream releases.

Supersedes #3088, which misattributed the crash-loop to PI4IO register
init / TCXO voltage / I2C pin routing / TX LED pin. Those four changes
were tested individually and in combination and made no difference
once the actual variable was isolated (see below); this PR reverts none
of them and touches only the flash mode.

Root cause

The esp32-c6-devkitm-1 board manifest
(platforms/espressif32/boards/esp32-c6-devkitm-1.json, all versions
checked: 6.7.0 / 6.10.0 / 6.11.0 / unversioned) sets
build.flash_mode: "qio". merge-bin.py reads this value directly
(board_config.get("build.flash_mode", "dio")), so every
*-merged.bin produced for this board — including the ones in the
official companion firmware releases — is built with a QIO header.
This module's actual flash chip does not reliably handle QIO on this
board, and the chip crash-loops on boot (repeated
enumerate/reset of the USB-Serial-JTAG peripheral) whenever the
flash is initialized in QIO mode, regardless of flash_size or
flash_freq.

How this was isolated

  1. Captured the exact esptool invocation pio run -t upload uses
    (-v build log) and reproduced it manually, byte-for-byte
    (same esptool binary/version, same --baud, --before/--after,
    --flash_mode, --flash_freq, --flash_size, and all four
    flash offsets including boot_app0.bin, which a naive manual
    erase_flash + write_flash at 0x0 had been omitting).
  2. Bisected each remaining parameter against a real device with a full
    erase_flash between each test:
    • esptool v4.8.6 (pio-bundled) vs v5.3.1 (system) — no effect
    • -z (compression) present/absent — no effect (esptool compresses
      by default regardless)
    • --flash_size 4MB vs detect (actual chip is 16MB) — no effect
    • --flash_mode dio vs qioreproduced the crash loop twice
      with qio, resolved twice with dio
      , on otherwise identical
      firmware and flashing steps
  3. Confirmed by reading back the actual on-chip flash header
    (read_flash + image_info) after flashing with no --flash_mode
    override at all (i.e. the condition a user gets from flashing a
    release merged.bin via a web flasher or a bare write_flash 0x0
    command with no flags): the chip ends up with the file's embedded
    QIO header verbatim, and crash-loops.
  4. Confirmed the currently released merged.bin for every C6L env
    (companion_radio_ble/usb, repeater, room_server) — both in this
    fork's jp-v1.8.1 release and in upstream's companion-v1.16.0
    release — has Flash mode: QIO in its header
    (esptool image_info).

Fix

One line, in the common [M5Stack_Unit_C6L] section so it applies to
all envs (companion_radio_ble/usb/repeater/room_server):

board_build.flash_mode = dio

This overrides the board manifest's qio default via PlatformIO's
board_build.* mechanism (not a build_flags -D, since this needs to
reach merge-bin.py's board_config.get("build.flash_mode", ...)
lookup, not a preprocessor define).

Test

  • pio run -e M5Stack_Unit_C6L_companion_radio_usb -t mergebin
    confirmed firmware-merged.bin header now reports Flash mode: DIO (esptool image_info)
  • Real M5Stack Unit C6L hardware, full erase_flash +
    write_flash 0x0 firmware-merged.bin with no --flash_mode
    override (matching how a user flashes a released binary) —
    boots cleanly, no crash-loop, serial output present
  • Reproduced the failure twice and the fix twice on the same
    device, to rule out a one-off observation

Board manifest for esp32-c6-devkitm-1 defaults build.flash_mode to qio,
which this module's flash chip does not support -- causes a boot
crash-loop (repeated USB-Serial-JTAG reconnects) on real hardware.
Override with board_build.flash_mode = dio in the common M5Stack_Unit_C6L
section so it applies to all envs (ble/usb/repeater/room_server).
@jirogit

jirogit commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

Prior art for this fix pattern on the same chip family:

All of these match this PR's root cause: esp32-c6-devkitm-1's board
manifest defaults build.flash_mode to qio, and merge-bin.py reads
that value directly. #2570 fixed it for one board on this manifest;
this PR fixes it for the other.

cc @recrof — thanks for all the triage you've done on this thread, it
made isolating this a lot easier. Flagging in case you have a spare
minute to take a look.

@recrof

recrof commented Aug 2, 2026

Copy link
Copy Markdown
Member

@liamcottle @ripplebiz I can't reproduce the problem on my board, however I could see how dio could help with flashing - this is safe to be merged.

@liamcottle
liamcottle merged commit d6ee3a1 into meshcore-dev:dev Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants