Merge pull request #200 from cmaglie/CH340_hack

Hack to workaround misbehaving CH340 drivers on Windows
This commit is contained in:
Cristian Maglie
2025-03-17 17:24:34 +01:00
committed by GitHub

View File

@@ -28,8 +28,9 @@ import (
) )
type windowsPort struct { type windowsPort struct {
mu sync.Mutex mu sync.Mutex
handle windows.Handle handle windows.Handle
hasTimeout bool
} }
func nativeGetPortsList() ([]string, error) { func nativeGetPortsList() ([]string, error) {
@@ -72,26 +73,33 @@ func (port *windowsPort) Read(p []byte) (int, error) {
} }
defer windows.CloseHandle(ev.HEvent) defer windows.CloseHandle(ev.HEvent)
err = windows.ReadFile(port.handle, p, &readed, ev) for {
if err == windows.ERROR_IO_PENDING { err = windows.ReadFile(port.handle, p, &readed, ev)
err = windows.GetOverlappedResult(port.handle, ev, &readed, true) if err == windows.ERROR_IO_PENDING {
} err = windows.GetOverlappedResult(port.handle, ev, &readed, true)
switch err { }
case nil: switch err {
// operation completed successfully case nil:
case windows.ERROR_OPERATION_ABORTED: // operation completed successfully
// port may have been closed case windows.ERROR_OPERATION_ABORTED:
return int(readed), &PortError{code: PortClosed, causedBy: err} // port may have been closed
default: return int(readed), &PortError{code: PortClosed, causedBy: err}
// error happened default:
return int(readed), err // error happened
} return int(readed), err
if readed > 0 { }
return int(readed), nil if readed > 0 {
} return int(readed), nil
}
// Timeout // Timeout
return 0, nil port.mu.Lock()
hasTimeout := port.hasTimeout
port.mu.Unlock()
if hasTimeout {
return 0, nil
}
}
} }
func (port *windowsPort) Write(p []byte) (int, error) { func (port *windowsPort) Write(p []byte) (int, error) {
@@ -275,10 +283,19 @@ func (port *windowsPort) GetModemStatusBits() (*ModemStatusBits, error) {
} }
func (port *windowsPort) SetReadTimeout(timeout time.Duration) error { func (port *windowsPort) SetReadTimeout(timeout time.Duration) error {
// This is a brutal hack to make the CH340 chipset work properly.
// Normally this value should be 0xFFFFFFFE but, after a lot of
// tinkering, I discovered that any value with the highest
// bit set will make the CH340 driver behave like the timeout is 0,
// in the best cases leading to a spinning loop...
// (could this be a wrong signed vs unsigned conversion in the driver?)
// https://github.com/arduino/serial-monitor/issues/112
const MaxReadTotalTimeoutConstant = 0x7FFFFFFE
commTimeouts := &windows.CommTimeouts{ commTimeouts := &windows.CommTimeouts{
ReadIntervalTimeout: 0xFFFFFFFF, ReadIntervalTimeout: 0xFFFFFFFF,
ReadTotalTimeoutMultiplier: 0xFFFFFFFF, ReadTotalTimeoutMultiplier: 0xFFFFFFFF,
ReadTotalTimeoutConstant: 0xFFFFFFFE, ReadTotalTimeoutConstant: MaxReadTotalTimeoutConstant,
WriteTotalTimeoutConstant: 0, WriteTotalTimeoutConstant: 0,
WriteTotalTimeoutMultiplier: 0, WriteTotalTimeoutMultiplier: 0,
} }
@@ -287,12 +304,20 @@ func (port *windowsPort) SetReadTimeout(timeout time.Duration) error {
if ms > 0xFFFFFFFE || ms < 0 { if ms > 0xFFFFFFFE || ms < 0 {
return &PortError{code: InvalidTimeoutValue} return &PortError{code: InvalidTimeoutValue}
} }
if ms > MaxReadTotalTimeoutConstant {
ms = MaxReadTotalTimeoutConstant
}
commTimeouts.ReadTotalTimeoutConstant = uint32(ms) commTimeouts.ReadTotalTimeoutConstant = uint32(ms)
} }
port.mu.Lock()
defer port.mu.Unlock()
if err := windows.SetCommTimeouts(port.handle, commTimeouts); err != nil { if err := windows.SetCommTimeouts(port.handle, commTimeouts); err != nil {
return &PortError{code: InvalidTimeoutValue, causedBy: err} return &PortError{code: InvalidTimeoutValue, causedBy: err}
} }
port.hasTimeout = (timeout != NoTimeout)
return nil return nil
} }