C# – COM port disappears when unplugging USB

arduinocnetserial-port

I am prototyping a sort of Arduino-based docking station for a tablet, using the USB port as connector. This means I need to support to ability to plug/unplug the USB connector while the application on the tablet is running.

The tablet runs a c# application (.net 4.5 on Win7 64 bit) in which I am connecting to the Arduino Uno. When the application is launched I loop all available COM ports using:

var ports = SerialPort.GetPortNames(); // -> [COM3,COM4,COM8]
foreach (var port in ports)
{
     var serial = new SerialPort(portname, baudRate);
     //attempt handshake and connect to right port
}

This work fine, but if I unplug and replug the USB cable and reattempt to reconnect to the Arduino (while the application is still running), the Arduino port (COM8) is no longer listed in:

SerialPort.GetPortNames(); // -> [COM3,COM4] and no COM8

Even restarting the application (with the Arduino replugged) will result in only [COM3,COM4] being listed.

The only way to get it back to work is to unplug and replug the Arduino while the application is not running.

What confuses me is the fact that when I plug in the Arduino Uno after starting the application, the SerialClass does recognize the newly added port and allows me to connect.

The problem only occurs when I unplug and replug the device when the application is running. It seems that despite the ability to reset the COM port (in code or manually in device manager), the SerialClass (and native Win32_SerialPort – I checked this too) do not recognize this, unless I restart the application

What could be the reason for this? And how can I make sure that my application can reconnect to that port? Are there any alternatives to using the SerialPort to handle the USB connector?

Best Answer

I found a solution that can handle plugging and unplugging a SerialPort.

First of all, it requires the use the SafeSerialPort, which allows you to dispose the serial port properly.

SafeSerialPort serialPort;

private void Connect()
{
    string portname = "COM8";
    serialPort = new SafeSerialPort(portname, 9600);
    serialPort.DataReceived += port_DataReceived;
    serialPort.Open(); 
}

Second, you need to use LibUsbDotNet to detect whether a USB device is connected or disconnected. This will allow you to determine whether to connect to the device or reset the COM port.

public UsbDevice MyUsbDevice;

//Find your vendor id etc by listing all available USB devices
public UsbDeviceFinder MyUsbFinder = new UsbDeviceFinder(0x2341, 0x0001);
public IDeviceNotifier UsbDeviceNotifier = DeviceNotifier.OpenDeviceNotifier();
private void OnDeviceNotifyEvent(object sender, DeviceNotifyEventArgs e)
{
    if (e.Object.ToString().Split('\n')[1].Contains("0x2341"))
    {
        if (e.EventType == EventType.DeviceArrival)
        {
            Connect();
        }
        else if(e.EventType == EventType.DeviceRemoveComplete)
        {
            ResetConnection();
        }
    }
}

Finally, disposing the SerialPort will makes sure it is registered by Windows in HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM, meaning that SerialPort.GetPortNames() can re-detect the port.

private void ResetConnection()
{
    try
    {
        //Send any data to cause an IOException
        serialPort.Write("Any value");
    }
    catch (IOException ex)
    {
        //Dispose the SafeSerialPort
        serialPort.Dispose();
        serialPort.Close();
    }
}

After this process, you can simply reconnect to the COM port when the USB device is connected without the need to restart the application.

Full code:

using LibUsbDotNet;
using LibUsbDotNet.DeviceNotify;
using LibUsbDotNet.Info;
using LibUsbDotNet.Main;    

SafeSerialPort serialPort;

            public SerialPortTest()
            {
                Connect();

                UsbDeviceNotifier.OnDeviceNotify += OnDeviceNotifyEvent;
            }

            private void Connect()
            {
                string portname = "COM8";
                serialPort = new SafeSerialPort(portname, 9600);
                serialPort.DataReceived += port_DataReceived;
                serialPort.Open(); 
            }

            private void ResetConnection()
            {
                try
                {
                    serialPort.Write("Any value");
                }
                catch (IOException ex)
                {
                    serialPort.Dispose();
                    serialPort.Close();
                }
            }


            void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
            {
                Console.WriteLine(serialPort.ReadExisting());
            }

            public UsbDevice MyUsbDevice;

            //Vendor ID etc can be found through enumerating the USB devices
            public UsbDeviceFinder MyUsbFinder = new UsbDeviceFinder(0x2341, 0x0001);
            public IDeviceNotifier UsbDeviceNotifier = DeviceNotifier.OpenDeviceNotifier();
            private void OnDeviceNotifyEvent(object sender, DeviceNotifyEventArgs e)
            {
                //if this is your usb device, in my case an Arduino
                if (e.Object.ToString().Split('\n')[1].Contains("0x2341"))
                {
                    if (e.EventType == EventType.DeviceArrival)
                    {
                        Connect();
                    }
                    else
                    {
                        ResetConnection();
                    }
                }
            }