Skip to content

Commit 3449d2e

Browse files
authored
Merge pull request #200 from cmaglie/CH340_hack
Hack to workaround misbehaving CH340 drivers on Windows
2 parents 7dc6297 + 1ff9b6f commit 3449d2e

File tree

1 file changed

+47
-22
lines changed

1 file changed

+47
-22
lines changed

serial_windows.go

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ import (
2828
)
2929

3030
type windowsPort struct {
31-
mu sync.Mutex
32-
handle windows.Handle
31+
mu sync.Mutex
32+
handle windows.Handle
33+
hasTimeout bool
3334
}
3435

3536
func nativeGetPortsList() ([]string, error) {
@@ -72,26 +73,33 @@ func (port *windowsPort) Read(p []byte) (int, error) {
7273
}
7374
defer windows.CloseHandle(ev.HEvent)
7475

75-
err = windows.ReadFile(port.handle, p, &readed, ev)
76-
if err == windows.ERROR_IO_PENDING {
77-
err = windows.GetOverlappedResult(port.handle, ev, &readed, true)
78-
}
79-
switch err {
80-
case nil:
81-
// operation completed successfully
82-
case windows.ERROR_OPERATION_ABORTED:
83-
// port may have been closed
84-
return int(readed), &PortError{code: PortClosed, causedBy: err}
85-
default:
86-
// error happened
87-
return int(readed), err
88-
}
89-
if readed > 0 {
90-
return int(readed), nil
91-
}
76+
for {
77+
err = windows.ReadFile(port.handle, p, &readed, ev)
78+
if err == windows.ERROR_IO_PENDING {
79+
err = windows.GetOverlappedResult(port.handle, ev, &readed, true)
80+
}
81+
switch err {
82+
case nil:
83+
// operation completed successfully
84+
case windows.ERROR_OPERATION_ABORTED:
85+
// port may have been closed
86+
return int(readed), &PortError{code: PortClosed, causedBy: err}
87+
default:
88+
// error happened
89+
return int(readed), err
90+
}
91+
if readed > 0 {
92+
return int(readed), nil
93+
}
9294

93-
// Timeout
94-
return 0, nil
95+
// Timeout
96+
port.mu.Lock()
97+
hasTimeout := port.hasTimeout
98+
port.mu.Unlock()
99+
if hasTimeout {
100+
return 0, nil
101+
}
102+
}
95103
}
96104

97105
func (port *windowsPort) Write(p []byte) (int, error) {
@@ -275,10 +283,19 @@ func (port *windowsPort) GetModemStatusBits() (*ModemStatusBits, error) {
275283
}
276284

277285
func (port *windowsPort) SetReadTimeout(timeout time.Duration) error {
286+
// This is a brutal hack to make the CH340 chipset work properly.
287+
// Normally this value should be 0xFFFFFFFE but, after a lot of
288+
// tinkering, I discovered that any value with the highest
289+
// bit set will make the CH340 driver behave like the timeout is 0,
290+
// in the best cases leading to a spinning loop...
291+
// (could this be a wrong signed vs unsigned conversion in the driver?)
292+
// https://github.com/arduino/serial-monitor/issues/112
293+
const MaxReadTotalTimeoutConstant = 0x7FFFFFFE
294+
278295
commTimeouts := &windows.CommTimeouts{
279296
ReadIntervalTimeout: 0xFFFFFFFF,
280297
ReadTotalTimeoutMultiplier: 0xFFFFFFFF,
281-
ReadTotalTimeoutConstant: 0xFFFFFFFE,
298+
ReadTotalTimeoutConstant: MaxReadTotalTimeoutConstant,
282299
WriteTotalTimeoutConstant: 0,
283300
WriteTotalTimeoutMultiplier: 0,
284301
}
@@ -287,12 +304,20 @@ func (port *windowsPort) SetReadTimeout(timeout time.Duration) error {
287304
if ms > 0xFFFFFFFE || ms < 0 {
288305
return &PortError{code: InvalidTimeoutValue}
289306
}
307+
308+
if ms > MaxReadTotalTimeoutConstant {
309+
ms = MaxReadTotalTimeoutConstant
310+
}
311+
290312
commTimeouts.ReadTotalTimeoutConstant = uint32(ms)
291313
}
292314

315+
port.mu.Lock()
316+
defer port.mu.Unlock()
293317
if err := windows.SetCommTimeouts(port.handle, commTimeouts); err != nil {
294318
return &PortError{code: InvalidTimeoutValue, causedBy: err}
295319
}
320+
port.hasTimeout = (timeout != NoTimeout)
296321

297322
return nil
298323
}

0 commit comments

Comments
 (0)