feat: serial CW keyer — add invert (active-LOW) polarity option
Some CW interfaces key on the opposite line level (transmit at rest / key backwards). New "Invert keying (active-LOW)" toggle flips the asserted level for both the CW and PTT lines, and the idle state drives both lines to their inactive level accordingly so an active-low rig isn't left keyed at rest. Default off = N1MM convention (line HIGH = active).
This commit is contained in:
@@ -39,6 +39,7 @@ var morseTable = map[rune]string{
|
||||
type serialKeyer struct {
|
||||
port serial.Port
|
||||
keyDTR bool // true: CW on DTR, PTT on RTS (N1MM default). false: swapped.
|
||||
invert bool // true: active-LOW — asserting a line means driving it LOW
|
||||
usePTT bool
|
||||
leadMs int
|
||||
tailMs int
|
||||
@@ -58,6 +59,7 @@ func newSerialKeyer(port serial.Port, cfg Config, onBusy func(bool)) *serialKeye
|
||||
k := &serialKeyer{
|
||||
port: port,
|
||||
keyDTR: !strings.EqualFold(strings.TrimSpace(cfg.CWKeyLine), "rts"), // default DTR=CW
|
||||
invert: cfg.CWInvert,
|
||||
usePTT: cfg.UsePTT,
|
||||
leadMs: cfg.LeadInMs,
|
||||
tailMs: cfg.TailMs,
|
||||
@@ -69,17 +71,21 @@ func newSerialKeyer(port serial.Port, cfg Config, onBusy func(bool)) *serialKeye
|
||||
stop: make(chan struct{}),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
k.allLow() // start idle: key up, PTT off
|
||||
k.idle() // start inactive: key up, PTT off
|
||||
go k.run()
|
||||
return k
|
||||
}
|
||||
|
||||
// level maps a logical "active" state to the physical line level, honouring the
|
||||
// invert (active-LOW) option: normally active = HIGH, inverted active = LOW.
|
||||
func (k *serialKeyer) level(active bool) bool { return active != k.invert }
|
||||
|
||||
// setKey raises/lowers the CW key line; setPTT the PTT line (only when enabled).
|
||||
func (k *serialKeyer) setKey(down bool) {
|
||||
if k.keyDTR {
|
||||
_ = k.port.SetDTR(down)
|
||||
_ = k.port.SetDTR(k.level(down))
|
||||
} else {
|
||||
_ = k.port.SetRTS(down)
|
||||
_ = k.port.SetRTS(k.level(down))
|
||||
}
|
||||
}
|
||||
func (k *serialKeyer) setPTT(on bool) {
|
||||
@@ -87,12 +93,18 @@ func (k *serialKeyer) setPTT(on bool) {
|
||||
return
|
||||
}
|
||||
if k.keyDTR {
|
||||
_ = k.port.SetRTS(on)
|
||||
_ = k.port.SetRTS(k.level(on))
|
||||
} else {
|
||||
_ = k.port.SetDTR(on)
|
||||
_ = k.port.SetDTR(k.level(on))
|
||||
}
|
||||
}
|
||||
func (k *serialKeyer) allLow() { _ = k.port.SetDTR(false); _ = k.port.SetRTS(false) }
|
||||
|
||||
// idle drives BOTH lines to their inactive level (key up, PTT off), respecting
|
||||
// the invert option — so an active-LOW interface isn't left keyed at rest.
|
||||
func (k *serialKeyer) idle() {
|
||||
_ = k.port.SetDTR(k.level(false))
|
||||
_ = k.port.SetRTS(k.level(false))
|
||||
}
|
||||
|
||||
// SetSpeed / Send / Stop mirror the WinKeyer manager's control surface.
|
||||
func (k *serialKeyer) SetSpeed(wpm int) {
|
||||
@@ -131,7 +143,7 @@ func (k *serialKeyer) Close() {
|
||||
|
||||
func (k *serialKeyer) run() {
|
||||
defer close(k.done)
|
||||
defer k.allLow()
|
||||
defer k.idle()
|
||||
for {
|
||||
select {
|
||||
case <-k.stop:
|
||||
@@ -149,7 +161,7 @@ func (k *serialKeyer) run() {
|
||||
for {
|
||||
select {
|
||||
case <-k.stop:
|
||||
k.allLow()
|
||||
k.idle()
|
||||
k.onBusy(false)
|
||||
return
|
||||
case s2 := <-k.in:
|
||||
|
||||
Reference in New Issue
Block a user