初始化USB调制解调器的COM端口

时间:2016-02-03 17:01:40

标签: c# serial-port usb modem

我正在使用 GsmComm 连接到USB调制解调器。当我最初将调制解调器连接到计算机时,我正在使用的调制解调器的COM端口不会显示在设备管理器中。计算机将其显示为可移动驱动器。但是,当我运行随调制解调器提供的应用程序时,com端口将显示在设备管理器中。

因此,每次我想将设备与我的应用程序一起使用时,我必须首先将其连接到PC,运行其软件以初始化COM端口,然后运行我的应用程序。

但有没有办法用我的应用程序C#初始化com端口?

我已经阅读了有关创建虚拟COM端口以连接到USB设备的内容,但我不知道该怎么做。任何帮助或指示将受到高度赞赏。

2016年2月14日更新

我按照antiduh的回答发现第一次连接时设备被识别为cdrom。

enter image description here

运行应用程序后,链接将更改为harddiskvolume -

enter image description here

创建了三个新的com链接。

enter image description here

3 个答案:

答案 0 :(得分:6)

虚拟串行端口由设备随附的设备驱动程序模拟。如果在运行软件之前它们不会显示在设备管理器中,那么它会动态安装设备驱动程序,或者向驱动程序发送秘密握手,告诉它开始模拟端口。

前者需要UAC提升和.sys文件,如果您在运行软件时没有看到提示并且没有看到可能执行此操作的已安装服务,也没有.sys文件那么您可以抓住这种可能性。后者通常通过DeviceIoControl()调用来完成,这是您可以使用过滤器驱动程序监视的类型。与IoSpy类似,是WDK附带的实用程序。在实用程序上使用Dumpbin.exe / imports可以提供有用的实现细节,SysInternals'进程监视器。

几乎不能保证成功,最好向制造商询问详情。但是,他们通常不会回电话,也不会在手册中包含此类详细信息。他们当然更喜欢任何人使用他们的铲子。请记住,你看到一只猪的卷曲尾巴,最好通过退回设备并从另一家制造商处购买另一种设备来减少损失。

答案 1 :(得分:3)

我有一个假设。

你见过Windows Object Manager吗?它是Windows中一个简洁的小命名空间,用于连接和暴露所有类型的疯狂小对象,包括设备作为文件。把它想象成Window的'/ dev'的可怕版本。

有趣的是,用户空间程序可以通过使用特殊前缀调用CreateFile来访问它。

例如,在Windows中打开串行端口的一种方法是调用CreateFile(@"\\.\COM3")。这是映射到对象管理器路径\GLOBAL??\COM3的路径。

以下是使用WinObj的路径: Screenshot of the Windows Object Manager program showing OM paths

在我的情况下,您可以看到\GLOBAL??\COM3实际上已连接到\Device\QTUSBSerial0

如果您在此特殊软件运行之前和之后观看WinObj,您可能会发现哪些目标设备符号链接到COMX,然后您可能能够找出该真实设备是否实际上始终存在。

天真地,我认为可以将任意对象管理器路径路径到CreateFile来访问对象,而不必依赖\\.\\GLOBAL??\映射。但是,似乎有一个挂断 - 根据this answerCreateFile只接受以对象管理器的\GLOBAL??\部分为目标的参数 - 它只接受\\.\作为路径前缀,并且不接受,例如\\.\Device\QTUSBSerial0或类似的字符串。

还有一种可能:创建一个非常小的设备驱动程序/内核模块,使用IoCreateSymbolicLink自己创建符号链接。编写一个创建对象管理器符号链接的驱动程序\GLOBAL??\CrazyDevice - > \Device\CrazyDevice,然后使用黑客攻击的SerialPortNet代码来调用CreateFile(@"\\.\CrazyDevice")

这有点紧张,但也许它会解决你的问题。

免责声明:我从未编写Windows设备驱动程序或操纵对象管理器。我几乎不知道我在这里做什么。

答案 2 :(得分:2)

看看自从我使用Windows以来已经过了很长时间,但是,这个概念仍然适用。首先,似乎这个USB调制解调器具有模式切换,这意味着它首先将其自身识别为CD-ROM以使您能够获取驱动程序,然后在安装完成后模式切换到包含创建的脚本的大容量存储设备3个COM端口的符号链接。 要在应用程序中使用它,您需要一些东西,首先是usb PID和VID,以便能够枚举计算机集线器上的设备。其次,您需要一个触发符号链接创建的脚本副本,并且一旦检测到设备(通过枚举VID和PID),您需要从应用程序调用该脚本,一旦脚本执行,将自动出现三个COM端口,你应该像往常一样访问它们。另外你可能想检查一下CD-ROM应用程序是否安装了dll(几乎可以肯定它确实是第二个脚本在创建COM端口链接之前检查dll,所以要确保dll保持在它们应该的位置至)。你需要将它们与你的应用程序链接起来以获得那些提供的任何额外功能(但是这会为本机界面打开pandoras框,如果你不熟悉那就不要这样做......我可以做/显示示例Java,但不是C#),否则,如果只是使用com端口是你想要的,你实际上知道如何与设备通信(AT命令)然后忘记它并打开com端口并消失。最后一点,您必须找出C#的本机接口(不是真正的本机接口,只是执行bash命令来运行脚本/ exe的系统调用,这就是全部),所以寻找内置的系统调用函数.NET框架。

如果您需要进一步澄清步骤,请与我们联系。

<强>更新

对于usb枚举,你可以使用类似于javas usb4java的库,并实现类似于以下的函数

public Device findDevice(short vendorId, short productId)
{
// Read the USB device list
DeviceList list = new DeviceList();// ---> Here is empty device list 
int result = LibUsb.getDeviceList(null, list);// ---> Now list is populated
if (result < 0) throw new LibUsbException("Unable to get device list", result);

try
{
    // Iterate over all devices and scan for the right one
    for (Device device: list)
    {
        DeviceDescriptor descriptor = new DeviceDescriptor();
        result = LibUsb.getDeviceDescriptor(device, descriptor);
        if (result != LibUsb.SUCCESS) throw new LibUsbException("Unable to read device descriptor", result);
        //Match the VID and PID (function inputs)---> if you find a match, then return success/or the device
        // you can find the VID and PID from the device manager
        if (descriptor.idVendor() == vendorId && descriptor.idProduct() == productId) 
        return device;
    }
}
finally
{
    // Ensure the allocated device list is freed
    LibUsb.freeDeviceList(list, true);
}

// Device not found
return null;
}

此功能允许您直接从应用程序访问USB设备。然后你可以开始初始化序列和批量传输(很可能适用于虚拟COM端口设备,例如ftdi芯片的典型情况,但由于这是一个未知的Chines芯片,这是我们可以做到的低水平,我们必须建立在提供的什么,并让Windows做驱动程序肮脏的工作)...

此时你的程序知道插入usb设备的事实,如果fucntion返回null,则休眠一秒钟并继续轮询直到设备被插入。

从这一点来说,你需要运行将创建符号链接的脚本,我将假设它是一个.exe文件。以下是其他成员发布的C#代码的副本和过去

using System.Diagnostics;

// Prepare the process to run
ProcessStartInfo start = new ProcessStartInfo();
// Enter in the command line arguments, everything you would enter after  the executable name itself
start.Arguments = arguments; 
// Enter the executable to run, including the complete path
start.FileName = "C:/path/to/your/.exe";
// Do you want to show a console window?
start.WindowStyle = ProcessWindowStyle.Hidden;
start.CreateNoWindow = true;
int exitCode;


// Run the external process & wait for it to finish
using (Process proc = Process.Start(start))
{
 proc.WaitForExit();

 // Retrieve the app's exit code
 exitCode = proc.ExitCode;
}

使用退出代码非常有用,可以指示脚本是否成功创建了符号链接(希望Chines的人员遵循正确的编码实践)。

修改

脚本可能会失败。原因很简单:它不知道枚举和执行初始化的ProductID和VendorID。 (99.99999%他们没有重新编译每个单元的简单初始化脚本只是为了硬编码pid和vid)所以它可能接收pid和vid作为args(最好的情况)或从usb大容量存储隐藏扇区读取(此时你如果从非root位置运行脚本,可能会出现路径问题... ...如果.exe没有向stderr输出任何内容,你可能需要gdb来查明是否有一些args丢失

最后,您可以使用以下标准开始查找使用标准C#库的COM端口列表:

Code Source

var portNames = SerialPort.GetPortNames();

foreach(var port in portNames) {
    //Try for every portName and break on the first working
}

当您找到所需的端口时,可以使用

打开它

Code Source

public static void Main()
{
string name;
string message;
StringComparer stringComparer = StringComparer.OrdinalIgnoreCase;
Thread readThread = new Thread(Read);

// Create a new SerialPort object with default settings.
_serialPort = new SerialPort();

// Allow the user to set the appropriate properties.
_serialPort.PortName = SetPortName(_serialPort.PortName);
_serialPort.BaudRate = SetPortBaudRate(_serialPort.BaudRate);
_serialPort.Parity = SetPortParity(_serialPort.Parity);
_serialPort.DataBits = SetPortDataBits(_serialPort.DataBits);
_serialPort.StopBits = SetPortStopBits(_serialPort.StopBits);
_serialPort.Handshake = SetPortHandshake(_serialPort.Handshake);

// Set the read/write timeouts
_serialPort.ReadTimeout = 500;
_serialPort.WriteTimeout = 500;

_serialPort.Open();
_continue = true;
readThread.Start();

Console.Write("Name: ");
name = Console.ReadLine();

Console.WriteLine("Type QUIT to exit");

while (_continue)
{
    message = Console.ReadLine();

    if (stringComparer.Equals("quit", message))
    {
        _continue = false;
    }
    else
    {
        _serialPort.WriteLine(
            String.Format("<{0}>: {1}", name, message));
    }
}

readThread.Join();
_serialPort.Close();
}

public static void Read()
{
while (_continue)
{
    try
    {
        string message = _serialPort.ReadLine();
        Console.WriteLine(message);
    }
    catch (TimeoutException) { }
}
}

希望有助于您入门!