Skip to content

fix: M5Stack Unit C6L crash loop on boot - #3088

Closed
jirogit wants to merge 1 commit into
meshcore-dev:devfrom
jirogit:fix/unit-c6l-pi4io-crash-loop
Closed

fix: M5Stack Unit C6L crash loop on boot#3088
jirogit wants to merge 1 commit into
meshcore-dev:devfrom
jirogit:fix/unit-c6l-pi4io-crash-loop

Conversation

@jirogit

@jirogit jirogit commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes a repeatable crash loop on the M5Stack Unit C6L (ESP32-C6 + SX1262) where the USB serial port cycles connect/disconnect continuously on boot (/dev/ttyACM0 present/absent, several times per minute). ROM download mode was stable, confirming the hardware/USB wiring was fine and the crash was in application firmware.

Four changes, all required together:

  • SX126X_DIO3_TCXO_VOLTAGE: 1.83.0
  • I2C pins (PIN_BOARD_SDA/PIN_BOARD_SCL): 16/1710/8
  • Add a PI4IO I/O-expander init sequence in UnitC6LBoard::begin(), including a write to the OUT_H_IM (output high-impedance) register. Upstream's board file never initializes the PI4IO expander at all, so P_LORA_RESET=-1 (SX1262 NRST wired through PI4IO P7) was left floating. radio_init() would then hang waiting on the SX1262 BUSY pin, and the watchdog would reset the MCU — the crash loop.
  • Remove P_LORA_TX_LED=15 (GPIO15 is an ESP32-C6 strapping pin)

Root cause for the PI4IO piece specifically: on this I/O expander, IO_DIR (pin direction) and OUT_H_IM (output driver enable) are independent registers. Writing IO_DIR=output and OUT_SET alone has no effect on the physical pin until the corresponding OUT_H_IM bit is also cleared — otherwise the pin stays in its power-on Hi-Z state. That's why NRST was never actually released.

Credit

The TCXO voltage, I2C pin assignment, and PI4IO register sequence are adapted from TheRealHaoLiu/MeshCore (main-m5stack-unit-c6l branch), a working M5Burner distribution build for this board.

Testing

All testing on real M5Stack Unit C6L hardware, companion_radio_usb build.

  • Stacked the 4 changes incrementally (PI4IO fix alone, then + I2C pins, then + TCXO, then + TX_LED removal), reflashing between steps. The crash loop persisted at every intermediate step and was only resolved once all 4 were combined. This confirms the combination is sufficient; it does not rule out that some subset smaller than 4 might also work, since not every subset was isolated separately.
  • Verified with a full chip erase (esptool erase_flash) + a from-scratch clean build (.pio/build removed, no incremental cache) + reflash, to rule out stale build artifacts or leftover NVS state as a factor in the earlier failed steps.
  • Confirmed stable: zero USB disconnect/reconnect events over multiple sustained monitoring windows (dmesg-level USB re-enumeration tracking, 10+ minutes total with zero drops after the fix), versus a 2-3 second disconnect/reconnect cycle beforehand.
  • Confirmed a USB client (companion app protocol) can connect and exchange data over the stabilized port.

contribute to #2229.

Update: Confirmed working via pio run -t upload (USB env,
companion_radio_usb). I haven't been able to get it working via the
official web flasher (flasher.meshcore.io) with the merged .bin yet.
Also haven't tested the BLE companion env specifically — the fix
targets the crash-loop-on-boot issue, not connectivity beyond that.

If anyone tries this via the web flasher, would be curious whether you
hit the same wall or if it's something specific to my setup.

- SX126X_DIO3_TCXO_VOLTAGE 1.8 -> 3.0
- I2C pins (PIN_BOARD_SDA/SCL) 16/17 -> 10/8
- Add PI4IO OUT_H_IM register write to release Hi-Z on the
  LoRa control pins (P5/P6/P7), which upstream left unset
- Remove P_LORA_TX_LED=15 (ESP32-C6 strapping pin conflict)

The TCXO voltage, I2C pins, and PI4IO initialization sequence
are adapted from TheRealHaoLiu/MeshCore (main-m5stack-unit-c6l
branch), a working M5Burner distribution for this board.
Upstream's PI4IO handling in particular never releases the
Hi-Z state on these pins, so writes to OUT_SET had no effect
on the physical pins.

This combination of changes resolves a repeatable crash where
the USB serial port cycled connect/disconnect continuously on
boot.

Fixes present/absent USB port cycling reported in meshcore-dev#2229.
jirogit added a commit to jirogit/MeshCore that referenced this pull request Aug 1, 2026
Removes 3 pin definitions from the M5Stack Unit C6L build config:

* GPS_RX=4 / GPS_TX=5: Unit C6L has no GPS hardware, so these are dead definitions.
* SX126X_RXEN=5: shares GPIO5 with GPS_TX=5 above. Note this was not itself dead —
  RadioLib's setRfSwitchPins()/setRfSwitchState() actively drives GPIO5 on every TX/RX
  transition, independent of SX126X_DIO2_AS_RF_SWITCH=true (which switches the antenna via
  the SX1262's internal DIO2, not an MCU GPIO). Removing this line leaves DIO2-based switching
  as the sole antenna switch mechanism on this board.

This is independent of the crash-loop fix in meshcore-dev#3088 (TCXO/I2C/PI4IO/TX_LED). During bisection
of that issue, these 3 lines were tested both present and removed with no observed effect on
the crash loop either way, so they're split out here as a separate, unrelated cleanup rather
than bundled into that fix.

TX/RX functionality after removing SX126X_RXEN has not yet been verified on hardware —
pending before merge. GPS_RX/GPS_TX removal has no functional impact (confirmed unused).

Related to meshcore-dev#2229 (split out from meshcore-dev#3088 during investigation).
@jirogit

jirogit commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

Closing this — the four changes here turned out not to be the actual
fix. I re-tested with a cleaner methodology (full erase, clean build,
consistent flashing method) and found none of them were necessary;
stock upstream/dev boots fine under the same conditions.

The real cause was flash_mode: qio in the board manifest
(esp32-c6-devkitm-1.json), which this chip doesn't handle reliably —
confirmed by bisecting flash_mode specifically (reproduced the crash
loop twice with qio, resolved it with dio, on otherwise identical
firmware).

Opening a new PR with the actual fix: a one-line
board_build.flash_mode = dio override. Sorry for the churn — details
in the new PR and in #2229.

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.

1 participant