4.0.5 pre2 with unipolar motor support, improved SD speed & Wifi disconnect resilience
Pre-releaseHighlights
WebUI uploads to SD are much faster
uploadWrite() slept 1 ms on every chunk. Chunks arrive at roughly the TCP
segment size, so the sleep cost about a millisecond per kilobyte — seconds of pure
sleeping on a large file, entirely independent of how fast the card is. It now
yields every 32 chunks, which keeps the original intent of letting other tasks run.
Two further costs in the same path:
FileStreamleft its stdio stream on the default buffer, so the small writes
arriving from the network were handed to FATFS in equally small pieces. Buffering
at 4096 bytes lets whole runs of sectors reach the card at once.uploadEnd()closed the file before taking itsFluidPath, dropping the mount
count to zero and forcing a full card re-initialisation for the rehash and size
check. Under memory pressure that failed outright withESP_ERR_NO_MEM, reporting
a file that had been written correctly as a failed upload.
A separate mount race that made uploads report "No SD card" is also fixed.
A job no longer dies when WiFi does
Losing WiFi mid-job used to end the cut, and usually took the controller with it.
There were five independent causes, each fixed here:
| What happened | Cause |
|---|---|
| Wrote through a freed pointer every GCode line | The job held a raw pointer to the channel that launched it |
Heap exhausted, then abort() |
Channel input queued unboundedly while a job ran; a FreeRTOS mutex leaked per channel |
| Task watchdog reboot | WebClient::write() waited forever for an HTTP reader that had vanished |
std::terminate() |
An exception escaping a support task panicked the board |
Cache disabled but cached memory region accessed |
The step ISR read vtables out of flash |
That last one was a build-configuration bug that had switched off the project's own
protection — see below. A job now runs to completion through a WiFi drop, reporting
once that its output channel went away.
Running a job no longer depends on the network
A job started from WebUI kept a raw pointer to the channel that launched it. When
WiFi dropped, that channel was freed and the controller wrote through the dangling
pointer on every subsequent GCode line. A job now survives losing its WebUI or
WebSocket connection and runs to completion, reporting once that its output channel
went away.
Three other ways a network failure could stop a cut:
- Heap exhaustion. Input arriving on a channel was queued unboundedly while a
job held the line-processing path, and nothing drained it until the job ended.
EveryChannelalso leaked a FreeRTOS mutex on destruction — one per HTTP
command, one per WebSocket connection. - Task watchdog reboot.
WebClient::write()waited without limit for an HTTP
reader that had vanished with the WiFi, blocking the watchdog-subscribed protocol
task mid-job. std::terminate(). An exception escaping any of the polling, output or
web-command tasks panicked the board. They now contain it and take another pass.
Crash fixes: flash access from the step ISR
Jobs could die with Guru Meditation Error: Cache disabled but cached memory region accessed. Any SPI flash access disables the cache on both cores; an ISR that
touches flash in that window panics.
platformio_override.inisetextra_scriptsfor the wifi, noradio and bt
environments. That replaces the value inherited throughextendsrather than
adding to it, silently droppingvtable_in_dram.py— so the project's own
sections.ldwas never found and every vtable stayed in flash (0 in DRAM, 240
in flash). ThePinDetailandSpindlevtables thatvtable_in_dram.ld
explicitly names are read by the step ISR. Now 30 vtables are in DRAM.- The unipolar driver was reached from the step ISR by virtual dispatch, and its
vtable is not covered by that linker script.MotorDrivernow supplies plain
IRAM function pointers, cached at registration; phase pins are written with
gpio_write()instead of throughPin, whosewrite()dispatches through a
PinDetailvtable. state_is()was called into flash from the ISR, andspindle->setSpeedfromISR()
read the Spindle vtable. Both are now ISR-safe. These affect any board, not only
unipolar builds.
Verified against the linked image: nothing reachable from the step ISR references
flash.
SD and WebUI uploads
- Fixed a mount race that made uploads report "No SD card".
- Reduced per-write overhead in the file write path.
- An SD read error is no longer reported as end-of-file. Previously a transient read
failure ended a job partway through and reported it as complete; it is now a
visible error, and the partial line is discarded rather than executed.
Diagnostics
A job that stops now says why, at a level that reaches the console:
[MSG:INFO: Job started: /sd/part.gcode]
[MSG:INFO: /sd/part.gcode job sent]
[MSG:ERR: Job aborted in state Cycle by alarm 1 (Hard Limit)]
Restart reason:is logged at startup — power-on, panic, task watchdog or
brownout were previously indistinguishable.- The low-memory warning is re-enabled and no longer allocates to report itself.
- New
$Console/ReportInterval(ms, default 0) gives grbl status reports on the
serial console, including theSD:progress field — the one connection that
survives a WiFi failure.
Assets
FluidNC-v4.0.5-pre2-wifi-esp32-firmware.bin — classic ESP32, wifi
environment. The firmware reports itself as v4.0.5-pre2-wifi.
This is the application image, which is what the Web Installer's
firmware-update path uploads and what WebUI's OTA firmware update accepts. It
preserves the filesystem, so your config and WebUI files on SPIFFS are left alone.
esptool.py --chip esp32 write_flash 0x10000 \
FluidNC-v4.0.5-pre2-wifi-esp32-firmware.bin
For a fresh install on a blank board this file is not enough on its own — the
Web Installer's fresh-install path also needs the bootloader (0x1000), partition
table (0x8000), boot_app0 (0xe000) and a filesystem image, none of which are
published here. Use a full release bundle from build-release.py for that.