Zombie BBC Micro:bit serial ports created when using pyocd-gdbserver –persist

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')

Leave a Reply

Your email address will not be published. Required fields are marked *