So I was happily using pyocd-gdbserver to program and enter debugging mode on a BBC Micro:bit attached to one of my laptop's USB port, as described here. Then I stopped being able to read data through the USB port... Long story short, multiple 'zombie' ports were created and my Python script was connecting to a zombie instead of the live one.
setserial -g /dev/ttyACM*
output:
/dev/ttyACM0, UART: unknown, Port: 0x0000, IRQ: 0, Flags: low_latency /dev/ttyACM1, UART: unknown, Port: 0x0000, IRQ: 0, Flags: low_latency
Sometimes for fun, I would also see a ttyACM2. Why would two ports have the same Port number? The answer is they don't. They are the same port. Connecting to /dev/ttyACM1 got me nothing. Connecting to /dev/ttyACM0 got me connected to the BBC Micro:bit. I had set the pyocd-gdb utility running using:
sudo ~/.local/bin/pyocd-gdbserver -t nrf51 -bh -r --persist
I think that the --persist flag does the damage. Run the script without this and I think we are good to go. I altered my serial port script to flag up when more than one Micro:bit is found. For good measure, I sort the ports into reverse order and connect to the first one with the PID and VID for the Micro:bit, which will be the lowest numbered ttyACM port. This is a work around when zombies appear.
Please find my Python 3 serial_port.py script for finding and returning a serial port connection to a BBC Micro:bit below.
import logging import serial import serial.tools.list_ports as list_ports from time import sleep BAUD = 115200 PID_MICROBIT = 516 VID_MICROBIT = 3368 TIMEOUT = 0.1 logging.basicConfig(level=logging.DEBUG, format='%(message)s') class SerialPort(): def __init__(self, pid=PID_MICROBIT, vid=VID_MICROBIT, baud=BAUD, timeout=TIMEOUT): self.serial_port = self.open_serial_port(pid, vid, baud, timeout) def count_same_ports(self, ports, pid, vid): ''' Count how many ports with pid and vid are in <ports>. ''' return len([p for p in ports if p.pid==pid and p.vid==vid]) def get_serial_data(self, serial_port): ''' get serial port data ''' inWaiting = serial_port.inWaiting() read_bytes = serial_port.readline(inWaiting) if not read_bytes: return return read_bytes.decode() def get_serial_port(self): ''' Return the serial port. ''' return self.serial_port def open_serial_port(self, pid=PID_MICROBIT, vid=VID_MICROBIT, baud=BAUD, timeout=TIMEOUT): ''' open a serial connection ''' print('looking for attached microbit on a serial port') # serial = find_comport(pid, vid, baud) serial_port = serial.Serial(timeout=timeout) serial_port.baudrate = baud ports = list(list_ports.comports()) print('scanning ports') num_mb = self.count_same_ports(ports, pid, vid) logging.info('{} microbits found'.format(num_mb)) if num_mb>1: logging.info('**** check for false connections ****') ports.sort(reverse=True) for p in ports: print('pid: {} vid: {}'.format(p.pid, p.vid)) if (p.pid == pid) and (p.vid == vid): print('found target device pid: {} vid: {} port: {}'.format( p.pid, p.vid, p.device)) serial_port.port = str(p.device) if not serial: print('no serial port found') return None try: serial_port.open() serial_port.flush() print('opened serial port: {}'.format(serial)) # except (AttributeError, SerialException) as e: except Exception as e: print('cannot open serial port: {}'.format(e)) return None # 100ms delay sleep(0.1) return serial_port if __name__ == '__main__': print('instatiating SerialPort()') serial_port = SerialPort() print('finished')