2
votes

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?

2
Just a second here, are you using a serial port as a USB port, or a USB port as a serial port? AFAIK, every time you reconnect something to a COM port you need to reboot it.Shark
serial port, I connect things all the time to serail ports with out rebooting. The real serial ports (rs-232) are very robust.(I thought)John b
have you tried disposing of that instance of SerialPort* and creating a new one when you lose a connection?Anthony Russell
It is the same problem. Every USB driver misbehaves in its own way. Your specific problem is that it doesn't update the registry correctly, the keys that SerialPort uses when it calls GetPortNames(). The message ought to be clear, don't disconnect the cable while the port is in use, there's just no point to it. You don't jerk a flash drive out of a USB port either while Windows is writing to it, treat USB emulators the same way. If this feature is essential to you for some mysterious reason then you need to go shopping for another one with a better driver.Hans Passant
I gave very specific advice in the linked question. Writing your own driver was certainly not part of that.Hans Passant

2 Answers

2
votes

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();
                    }
                }
            }
1
votes

So I believe this is happening because your program is caching the address of the USB the first time it is plugged in.

When someone plugs in a device, the hub detects voltage on either D+ or D- and signals the insertion to the host via this interrupt endpoint. When the host polls this interrupt endpoint, it learns that the new device is present. It then instructs the hub (via the default control pipe) to reset the port where the new device was plugged in. ***This reset makes the new device assume address 0, and the host can then interact with it directly; this interaction will result in the host assigning a new (non-zero) address to the device.

Your best bet is to research how to programically flush the address cache of USB devices.

Reference:http://en.wikipedia.org/wiki/USB_hub