From e107efa63f5369ded1134466abf2631e4fd579f6 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 17 Jun 2021 17:12:05 +0200 Subject: [PATCH] main: detect specific serial port IDs based on USB vid/pid This makes it possible to flash a board even when there are multiple different kinds of boards attached, e.g. an Arduino Uno and a Circuit Playground Express. You can find the VID/PID pair in several ways: 1. By running `lsusb` before and after attaching the board and looking at the new USB device. 2. By grepping for `usb_PID` and `usb_VID` in the TinyGo source code. 3. By checking the Arduino IDE boards.txt from the vendor. Note that one board may have multiple VID/PID pairs: * The bootloader and main program may have a different PID, so far I've seen that the main program generally has the bootloader PID with 0x8000 added. * The software running on the board may have an erroneous PID, for example from a different board. I've seen this happen a few times. * A single board may have had some revisions which changed the PID. This is particularly true for the Arduino Uno. As a fallback, if the given VID/PID pair isn't found, the whole set of serial ports will be used. There are many boards which I haven't included yet simply because I couldn't test them. --- compileopts/target.go | 1 + main.go | 71 +++++++++++++++++++++++++++--- targets/arduino-nano33.json | 1 + targets/arduino.json | 1 + targets/circuitplay-bluefruit.json | 1 + targets/circuitplay-express.json | 1 + targets/itsybitsy-m4.json | 1 + targets/nano-33-ble.json | 1 + targets/pybadge.json | 1 + targets/pyportal.json | 1 + 10 files changed, 74 insertions(+), 6 deletions(-) diff --git a/compileopts/target.go b/compileopts/target.go index 24cf4f39..4fb62fce 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -45,6 +45,7 @@ type TargetSpec struct { FlashCommand string `json:"flash-command"` GDB []string `json:"gdb"` PortReset string `json:"flash-1200-bps-reset"` + SerialPort []string `json:"serial-port"` // serial port IDs in the form "acm:vid:pid" or "usb:vid:pid" FlashMethod string `json:"flash-method"` FlashVolume string `json:"msd-volume-name"` FlashFilename string `json:"msd-firmware-name"` diff --git a/main.go b/main.go index 992affa5..24d94362 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "path/filepath" "regexp" "runtime" + "strconv" "strings" "sync/atomic" "time" @@ -296,7 +297,7 @@ func Flash(pkgName, port string, options *compileopts.Options) error { return builder.Build(pkgName, fileExt, config, func(result builder.BuildResult) error { // do we need port reset to put MCU into bootloader mode? if config.Target.PortReset == "true" && flashMethod != "openocd" { - port, err := getDefaultPort(strings.FieldsFunc(port, func(c rune) bool { return c == ',' })) + port, err := getDefaultPort(port, config.Target.SerialPort) if err != nil { return err } @@ -321,7 +322,7 @@ func Flash(pkgName, port string, options *compileopts.Options) error { if strings.Contains(flashCmd, "{port}") { var err error - port, err = getDefaultPort(strings.FieldsFunc(port, func(c rune) bool { return c == ',' })) + port, err = getDefaultPort(port, config.Target.SerialPort) if err != nil { return err } @@ -718,7 +719,8 @@ func windowsFindUSBDrive(volume string, options *compileopts.Options) (string, e } // getDefaultPort returns the default serial port depending on the operating system. -func getDefaultPort(portCandidates []string) (port string, err error) { +func getDefaultPort(portFlag string, usbInterfaces []string) (port string, err error) { + portCandidates := strings.FieldsFunc(portFlag, func(c rune) bool { return c == ',' }) if len(portCandidates) == 1 { return portCandidates[0], nil } @@ -734,13 +736,70 @@ func getDefaultPort(portCandidates []string) (port string, err error) { return "", err } + var preferredPortIDs [][2]uint16 + for _, s := range usbInterfaces { + parts := strings.Split(s, ":") + if len(parts) != 3 || (parts[0] != "acm" && parts[0] == "usb") { + // acm and usb are the two types of serial ports recognized + // under Linux (ttyACM*, ttyUSB*). Other operating systems don't + // generally make this distinction. If this is not one of the + // given USB devices, don't try to parse the USB IDs. + continue + } + vid, err := strconv.ParseUint(parts[1], 16, 16) + if err != nil { + return "", fmt.Errorf("could not parse USB vendor ID %q: %w", parts[1], err) + } + pid, err := strconv.ParseUint(parts[2], 16, 16) + if err != nil { + return "", fmt.Errorf("could not parse USB product ID %q: %w", parts[1], err) + } + preferredPortIDs = append(preferredPortIDs, [2]uint16{uint16(vid), uint16(pid)}) + } + + var primaryPorts []string // ports picked from preferred USB VID/PID + var secondaryPorts []string // other ports (as a fallback) for _, p := range portsList { - if p.IsUSB { - ports = append(ports, p.Name) + if !p.IsUSB { + continue } + if p.VID != "" && p.PID != "" { + foundPort := false + vid, vidErr := strconv.ParseUint(p.VID, 16, 16) + pid, pidErr := strconv.ParseUint(p.PID, 16, 16) + if vidErr == nil && pidErr == nil { + for _, id := range preferredPortIDs { + if uint16(vid) == id[0] && uint16(pid) == id[1] { + primaryPorts = append(primaryPorts, p.Name) + foundPort = true + continue + } + } + } + if foundPort { + continue + } + } + + secondaryPorts = append(secondaryPorts, p.Name) + } + if len(primaryPorts) == 1 { + // There is exactly one match in the set of preferred ports. Use + // this port, even if there may be others available. This allows + // flashing a specific board even if there are multiple available. + return primaryPorts[0], nil + } else if len(primaryPorts) > 1 { + // There are multiple preferred ports, probably because more than + // one device of the same type are connected (e.g. two Arduino + // Unos). + ports = primaryPorts + } else { + // No preferred ports found. Fall back to other serial ports + // available in the system. + ports = secondaryPorts } - if ports == nil || len(ports) == 0 { + if len(ports) == 0 { // fallback switch runtime.GOOS { case "darwin": diff --git a/targets/arduino-nano33.json b/targets/arduino-nano33.json index 45ca81e8..f1797b79 100644 --- a/targets/arduino-nano33.json +++ b/targets/arduino-nano33.json @@ -2,5 +2,6 @@ "inherits": ["atsamd21g18a"], "build-tags": ["arduino_nano33"], "flash-command": "bossac -i -e -w -v -R -U --port={port} --offset=0x2000 {bin}", + "serial-port": ["acm:2341:8057", "acm:2341:0057"], "flash-1200-bps-reset": "true" } diff --git a/targets/arduino.json b/targets/arduino.json index ebe8af51..91dc49b8 100644 --- a/targets/arduino.json +++ b/targets/arduino.json @@ -6,5 +6,6 @@ "-Wl,--defsym=_stack_size=512" ], "flash-command": "avrdude -c arduino -p atmega328p -P {port} -U flash:w:{hex}:i", + "serial-port": ["acm:2341:0043", "acm:2341:0001", "acm:2a03:0043", "acm:2341:0243"], "emulator": ["simavr", "-m", "atmega328p", "-f", "16000000"] } diff --git a/targets/circuitplay-bluefruit.json b/targets/circuitplay-bluefruit.json index 6a57e1da..24bf5577 100644 --- a/targets/circuitplay-bluefruit.json +++ b/targets/circuitplay-bluefruit.json @@ -3,6 +3,7 @@ "build-tags": ["circuitplay_bluefruit","nrf52840_reset_uf2", "softdevice", "s140v6"], "flash-1200-bps-reset": "true", "flash-method": "msd", + "serial-port": ["acm:239a:8045", "acm:239a:45"], "msd-volume-name": "CPLAYBTBOOT", "msd-firmware-name": "firmware.uf2", "uf2-family-id": "0xADA52840", diff --git a/targets/circuitplay-express.json b/targets/circuitplay-express.json index a2b4143c..e70dfb93 100644 --- a/targets/circuitplay-express.json +++ b/targets/circuitplay-express.json @@ -3,6 +3,7 @@ "build-tags": ["circuitplay_express"], "flash-1200-bps-reset": "true", "flash-method": "msd", + "serial-port": ["acm:239a:8018", "acm:239a:18"], "msd-volume-name": "CPLAYBOOT", "msd-firmware-name": "firmware.uf2" } diff --git a/targets/itsybitsy-m4.json b/targets/itsybitsy-m4.json index 2d6a88ed..9c7a634b 100644 --- a/targets/itsybitsy-m4.json +++ b/targets/itsybitsy-m4.json @@ -3,6 +3,7 @@ "build-tags": ["itsybitsy_m4"], "flash-1200-bps-reset": "true", "flash-method": "msd", + "serial-port": ["acm:239a:802b", "acm:239a:002b"], "msd-volume-name": "ITSYM4BOOT", "msd-firmware-name": "firmware.uf2" } diff --git a/targets/nano-33-ble.json b/targets/nano-33-ble.json index df22221f..c5381e9f 100644 --- a/targets/nano-33-ble.json +++ b/targets/nano-33-ble.json @@ -2,6 +2,7 @@ "inherits": ["nrf52840"], "build-tags": ["nano_33_ble", "nrf52840_reset_bossa"], "flash-command": "bossac_arduino2 -d -i -e -w -v -R --port={port} {bin}", + "serial-port": ["acm:2341:805a", "acm:2341:005a"], "flash-1200-bps-reset": "true", "linkerscript": "targets/nano-33-ble.ld" } diff --git a/targets/pybadge.json b/targets/pybadge.json index 0514f537..b76b3246 100644 --- a/targets/pybadge.json +++ b/targets/pybadge.json @@ -3,6 +3,7 @@ "build-tags": ["pybadge"], "flash-1200-bps-reset": "true", "flash-method": "msd", + "serial-port": ["acm:239a:8033", "acm:239a:33"], "msd-volume-name": "PYBADGEBOOT", "msd-firmware-name": "arcade.uf2" } diff --git a/targets/pyportal.json b/targets/pyportal.json index e03efcb6..a26d82f0 100644 --- a/targets/pyportal.json +++ b/targets/pyportal.json @@ -3,6 +3,7 @@ "build-tags": ["pyportal"], "flash-1200-bps-reset": "true", "flash-method": "msd", + "serial-port": ["acm:239a:8035", "acm:239a:35", "acm:239a:8036"], "msd-volume-name": "PORTALBOOT", "msd-firmware-name": "firmware.uf2" }