COM порт исчезает при отключении USB
Я создаю прототип своего рода док-станции на базе Arduino для планшета, используя порт USB в качестве разъема. Это означает, что мне нужно поддерживать возможность подключать / отключать USB-разъем во время работы приложения на планшете.
На планшете работает приложение aC# (.net 4.5 на 64-битной Win7), в котором я подключаюсь к Arduino Uno. Когда приложение запускается, я зацикливаю все доступные COM-порты, используя:
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
}
Это работает нормально, но если я отключу и снова подключу USB-кабель и попытаюсь повторно подключиться к Arduino (пока приложение еще работает), порт Arduino (COM8) больше не будет указан в:
SerialPort.GetPortNames(); // -> [COM3,COM4] and no COM8
Даже перезапуск приложения (с повторным подключением Arduino) приведет к тому, что в списке будет только [COM3,COM4].
Единственный способ вернуть его к работе - отключить и снова подключить Arduino, пока приложение не запущено.
Что меня смущает, так это то, что когда я подключаю Arduino Uno после запуска приложения, SerialClass распознает только что добавленный порт и позволяет мне подключаться.
Проблема возникает, только когда я отключаю и снова подключаю устройство, когда приложение работает. Кажется, что, несмотря на возможность сброса COM-порта (в коде или вручную в диспетчере устройств), SerialClass (и собственный Win32_SerialPort - я тоже это проверял) не распознают это, пока я не перезапущу приложение
Что может быть причиной этого? И как я могу убедиться, что мое приложение может подключиться к этому порту? Есть ли альтернативы использованию SerialPort для обработки USB-разъема?
2 ответа
Я нашел решение, которое может обрабатывать подключение и отключение SerialPort.
Прежде всего, это требует использования SafeSerialPort, который позволяет вам правильно расположить последовательный порт.
SafeSerialPort serialPort;
private void Connect()
{
string portname = "COM8";
serialPort = new SafeSerialPort(portname, 9600);
serialPort.DataReceived += port_DataReceived;
serialPort.Open();
}
Во-вторых, вам нужно использовать LibUsbDotNet, чтобы определить, подключено или отключено USB-устройство. Это позволит вам определить, следует ли подключиться к устройству или сбросить COM-порт.
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();
}
}
}
Наконец, удаление SerialPort гарантирует, что Windows зарегистрирована в HKEY_LOCAL_MACHINE \ HARDWARE \ DEVICEMAP \ SERIALCOMM, что означает, что SerialPort.GetPortNames()
может повторно определить порт.
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();
}
}
После этого процесса вы можете просто повторно подключиться к COM-порту, когда устройство USB подключено, без необходимости перезапускать приложение.
Полный код:
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();
}
}
}
Поэтому я считаю, что это происходит потому, что ваша программа кэширует адрес USB при первом подключении.
Когда кто-то подключает устройство, концентратор обнаруживает напряжение на D+ или D- и сигнализирует о вставке на хост через эту конечную точку прерывания. Когда хост опрашивает эту конечную точку прерывания, он узнает, что новое устройство присутствует. Затем он инструктирует концентратор (через канал управления по умолчанию) сбросить порт, к которому было подключено новое устройство. *** Этот сброс заставляет новое устройство принимать адрес 0, и хост может затем взаимодействовать с ним напрямую; это взаимодействие приведет к тому, что хост назначит устройству новый (ненулевой) адрес.
Лучше всего исследовать, как программно очистить кэш адресов USB-устройств.