Skip to content

Commit d5446e2

Browse files
committed
fix: use EscapeCommFunction for USB reset on Windows
The go.bug.st/serial library uses SetCommState to toggle RTS/DTR, which does not reliably send USB control messages on Windows for USB CDC devices (e.g. ESP32-S3 USB-JTAG/Serial). Add a Windows-specific hardResetUSB that closes the library port, opens a raw handle via CreateFile, and uses EscapeCommFunction to directly toggle the reset signals. Non-Windows platforms delegate to the existing hardReset via the serial library, which works correctly on Linux/macOS.
1 parent 8cc15f9 commit d5446e2

File tree

3 files changed

+79
-1
lines changed

3 files changed

+79
-1
lines changed

pkg/espflasher/flasher.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ func New(portName string, opts *FlasherOptions) (*Flasher, error) {
175175

176176
// Close releases the serial port and associated resources.
177177
func (f *Flasher) Close() error {
178+
if f.port == nil {
179+
return nil
180+
}
178181
return f.port.Close()
179182
}
180183

@@ -844,7 +847,13 @@ func (f *Flasher) Reset() {
844847
// For ROM bootloaders, skip flash_begin/flash_end — sending
845848
// CMD_FLASH_BEGIN after a compressed download may interfere with
846849
// the flash controller state at offset 0.
847-
hardReset(f.port, false)
850+
if f.usesUSB {
851+
if hardResetUSB(f.portStr, f.port) {
852+
f.port = nil
853+
}
854+
} else {
855+
hardReset(f.port, false)
856+
}
848857
f.logf("Device reset.")
849858
}
850859

pkg/espflasher/reset_other.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//go:build !windows
2+
3+
package espflasher
4+
5+
import "go.bug.st/serial"
6+
7+
// hardResetUSB performs a hardware reset for USB-JTAG/Serial devices.
8+
// On non-Windows platforms, this delegates to the standard hardReset
9+
// which uses SetRTS/SetDTR through the serial library.
10+
// Returns false because the port remains open.
11+
func hardResetUSB(portName string, port serial.Port) bool {
12+
hardReset(port, true)
13+
return false
14+
}

pkg/espflasher/reset_windows.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//go:build windows
2+
3+
package espflasher
4+
5+
import (
6+
"syscall"
7+
"time"
8+
9+
"go.bug.st/serial"
10+
)
11+
12+
var procEscapeCommFunction = syscall.NewLazyDLL("kernel32.dll").NewProc("EscapeCommFunction")
13+
14+
const (
15+
escSETRTS = 3
16+
escCLRRTS = 4
17+
escSETDTR = 5
18+
escCLRDTR = 6
19+
)
20+
21+
// hardResetUSB performs a hardware reset for USB-JTAG/Serial devices on Windows.
22+
//
23+
// The go.bug.st/serial library uses SetCommState to toggle RTS/DTR, but this
24+
// does not reliably send USB control messages on Windows for USB CDC devices.
25+
// This function closes the library port, opens the COM port with a raw Windows
26+
// handle, and uses EscapeCommFunction to directly toggle the signals.
27+
//
28+
// Returns true because the port is consumed (closed) and must not be reused.
29+
func hardResetUSB(portName string, port serial.Port) bool {
30+
port.Close() //nolint:errcheck
31+
32+
name, err := syscall.UTF16PtrFromString(`\\.\` + portName)
33+
if err != nil {
34+
return true
35+
}
36+
37+
handle, err := syscall.CreateFile(name,
38+
syscall.GENERIC_READ|syscall.GENERIC_WRITE,
39+
0, nil, syscall.OPEN_EXISTING, 0, 0)
40+
if err != nil {
41+
return true
42+
}
43+
defer syscall.CloseHandle(handle) //nolint:errcheck
44+
45+
// DTR=false (GPIO0 HIGH → normal boot mode, not bootloader)
46+
procEscapeCommFunction.Call(uintptr(handle), escCLRDTR) //nolint:errcheck
47+
// RTS=true (EN LOW → hold chip in reset)
48+
procEscapeCommFunction.Call(uintptr(handle), escSETRTS) //nolint:errcheck
49+
time.Sleep(200 * time.Millisecond)
50+
// RTS=false (EN HIGH → chip boots)
51+
procEscapeCommFunction.Call(uintptr(handle), escCLRRTS) //nolint:errcheck
52+
time.Sleep(200 * time.Millisecond)
53+
54+
return true
55+
}

0 commit comments

Comments
 (0)