周五我第一次开始尝试设置USB接口。我正在尝试使用 Winusb.sys 和 Windows.Devices.Usb 命名空间在 C# 中编写通用 USB 通信类。我通常会关注 Microsoft 关于该主题的教程。
我的目标是编写一个与 USBTMC 设备通信的通用 USB 通信接口。我希望创建类似 C# 的 SerialPort 类,但针对 USB 设备。我知道 NI-VISA。我用过。坦白说,我讨厌它。它需要大量的软件依赖性。我的目标是创建一个便携式解决方案,而不需要在计算机上安装千兆字节的 NI 驱动程序和软件。
到目前为止,我已经创建并安装了自定义 INF,以将 USBTMC 设备与 WinUsb 相关联。我制作了一个简单的 Windows 窗体应用程序来枚举、显示和连接到与 USBTMC 类标识符匹配的 USB 设备。我还可以读取设备描述符。我复制了应该写入和读取的代码。我编写了一个批量传输类来处理创建批量传输标头并生成对齐字节。我还下载了一个名为 Usblyzer 的随机 USB 分析器/嗅探器。
我正在尝试发送一个简单的 SCPI“*IDN?”要求。附上。在我看来,一切都是对的,然而,当我尝试回读时,我却从未得到任何回应。据我所知,我已经生成了格式正确的 Bulk-Out USBTMC 传输。我已通读 USBTMC 规范和 USB488 子类规范的部分内容。我还没有完整地读过这两本书,所以如果我错过了其中一些简单的内容,我深表歉意。
下面是控制USB交互的两段代码。 UsbComm 旨在成为通用通信接口。 BulkOutMessage 是一个帮助器类,可以更轻松地创建批量传输标头。
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices.Enumeration;
using Windows.Devices.Usb;
using Windows.Storage.Streams;
namespace USB_Testing
{
public class UsbComm : IDisposable
{
private UsbDevice usbDevice;
private DataWriter writer;
private DataReader reader;
/// <summary>
/// Used to track the number of bulk transfers initiated which in turn is used to generate the bTag for subsequent bulk transfers.
/// This must be incremented for each transfer to comply with USB protocols.
/// </summary>
private byte messageCounter = 0;
public static async Task<UsbComm> CreateAsync(DeviceInformation deviceInformation)
{
return await CreateAsync(deviceInformation.Id);
}
public static async Task<UsbComm> CreateAsync(string deviceId)
{
var newClass = new UsbComm();
return await newClass.InitializeAsync(deviceId);
}
private async Task<UsbComm> InitializeAsync(string deviceId)
{
this.usbDevice = await UsbDevice.FromIdAsync(deviceId);
this.writer = InitializeWriter();
this.reader = InitializeReader();
return this;
}
private DataWriter InitializeWriter()
{
UsbBulkOutPipe writePipe = usbDevice.DefaultInterface.BulkOutPipes[0];
writePipe.WriteOptions |= UsbWriteOptions.ShortPacketTerminate | UsbWriteOptions.AutoClearStall;
return new DataWriter(writePipe.OutputStream);
}
private DataReader InitializeReader()
{
UsbBulkInPipe readPipe = usbDevice.DefaultInterface.BulkInPipes[0];
readPipe.ReadOptions |= UsbReadOptions.IgnoreShortPacket | UsbReadOptions.AutoClearStall;
return new DataReader(readPipe.InputStream);
}
private UsbComm()
{
}
public static async Task<DeviceInformationCollection> EnumerateDevices(UInt32 vid = 0x00, UInt32 pid = 0x00)
{
// VID and PID can be found by opening Device Manage > Right click device of interest > Properties > Details > Select Hardware Ids from the Property dropdown > Copy the values
// VID and PID should not be used unless we wish to enumerate only specific devices from specific manufacturers. Otherwise, we should enumerate all in the test and measurement class devices.
// A selector can be made from the VID and PID alone
//UsbDevice.GetDeviceSelector(vid, pid);
string selector = "";
if (vid != 0x00 || pid != 0x00)
{
// If the caller specified and VID and/or PID we should enumerate using this.
selector = UsbDevice.GetDeviceSelector(vid, pid);
}
else
{
// Otherwise, we should enumerate all USBTMC devices and let the caller filter the result as they see fit.
selector = UsbDevice.GetDeviceClassSelector(UsbDeviceClasses.Measurement);
}
// We !MUST! use the selector to search for USB devices. If the selector created from VID / PID or Device Class does not return the device of interest, then it is not configured properly.
// If we search for our device with a more broad selector, we may find the device, but when we try to connect to it "UsbDevice.FromIdAsync" will return null.
return await DeviceInformation.FindAllAsync(selector);
}
public async Task<uint> SendMessage(string message)
{
return await sendMessage(new BulkOutMessage(message, this.messageCounter, false));
}
private async Task<uint> sendMessage(BulkOutMessage bulkOutMessage)
{
this.messageCounter++;
writer.WriteBytes(bulkOutMessage.ToBytes());
return await writer.StoreAsync();
}
public async Task<string> ReadMessage()
{
await reader.LoadAsync(512);
IBuffer buffer = reader.ReadBuffer(reader.UnconsumedBufferLength);
return buffer.ToString();
}
public void Dispose()
{
if (this.usbDevice != null)
{
this.usbDevice.Dispose();
}
}
#region GetDescriptorStrings
public string GetDeviceDescriptorAsString()
{
string content = null;
var deviceDescriptor = this.usbDevice.DeviceDescriptor;
content = "Device Descriptor\n"
+ "\nUsb Spec Number : 0x" + deviceDescriptor.BcdUsb.ToString("X4", NumberFormatInfo.InvariantInfo)
+ "\nMax Packet Size (Endpoint 0) : " + deviceDescriptor.MaxPacketSize0.ToString("D", NumberFormatInfo.InvariantInfo)
+ "\nVendor ID : 0x" + deviceDescriptor.VendorId.ToString("X4", NumberFormatInfo.InvariantInfo)
+ "\nProduct ID : 0x" + deviceDescriptor.ProductId.ToString("X4", NumberFormatInfo.InvariantInfo)
+ "\nDevice Revision : 0x" + deviceDescriptor.BcdDeviceRevision.ToString("X4", NumberFormatInfo.InvariantInfo)
+ "\nNumber of Configurations : " + deviceDescriptor.NumberOfConfigurations.ToString("D", NumberFormatInfo.InvariantInfo);
return content;
}
public string GetConfigurationDescriptorAsString()
{
string content = null;
var usbConfiguration = this.usbDevice.Configuration;
var configurationDescriptor = usbConfiguration.ConfigurationDescriptor;
content = "Configuration Descriptor\n"
+ "\nNumber of Interfaces : " + usbConfiguration.UsbInterfaces.Count.ToString("D", NumberFormatInfo.InvariantInfo)
+ "\nConfiguration Value : 0x" + configurationDescriptor.ConfigurationValue.ToString("X2", NumberFormatInfo.InvariantInfo)
+ "\nSelf Powered : " + configurationDescriptor.SelfPowered.ToString()
+ "\nRemote Wakeup : " + configurationDescriptor.RemoteWakeup.ToString()
+ "\nMax Power (milliAmps) : " + configurationDescriptor.MaxPowerMilliamps.ToString("D", NumberFormatInfo.InvariantInfo);
return content;
}
public string GetInterfaceDescriptorsAsString()
{
string content = null;
var interfaces = this.usbDevice.Configuration.UsbInterfaces;
content = "Interface Descriptors";
foreach (UsbInterface usbInterface in interfaces)
{
// Class/subclass/protocol values from the first interface setting.
UsbInterfaceDescriptor usbInterfaceDescriptor = usbInterface.InterfaceSettings[0].InterfaceDescriptor;
content += "\n\nInterface Number: 0x" + usbInterface.InterfaceNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
+ "\nClass Code: 0x" + usbInterfaceDescriptor.ClassCode.ToString("X2", NumberFormatInfo.InvariantInfo)
+ "\nSubclass Code: 0x" + usbInterfaceDescriptor.SubclassCode.ToString("X2", NumberFormatInfo.InvariantInfo)
+ "\nProtocol Code: 0x" + usbInterfaceDescriptor.ProtocolCode.ToString("X2", NumberFormatInfo.InvariantInfo)
+ "\nNumber of Interface Settings: " + usbInterface.InterfaceSettings.Count.ToString("D", NumberFormatInfo.InvariantInfo)
+ "\nNumber of open Bulk In pipes: " + usbInterface.BulkInPipes.Count.ToString("D", NumberFormatInfo.InvariantInfo)
+ "\nNumber of open Bulk Out pipes: " + usbInterface.BulkOutPipes.Count.ToString("D", NumberFormatInfo.InvariantInfo)
+ "\nNumber of open Interrupt In pipes: " + usbInterface.InterruptInPipes.Count.ToString("D", NumberFormatInfo.InvariantInfo)
+ "\nNumber of open Interrupt Out pipes: " + usbInterface.InterruptOutPipes.Count.ToString("D", NumberFormatInfo.InvariantInfo);
}
return content;
}
public string GetEndpointDescriptorsAsString()
{
string content = null;
var usbInterface = this.usbDevice.DefaultInterface;
var bulkInPipes = usbInterface.BulkInPipes;
var bulkOutPipes = usbInterface.BulkOutPipes;
var interruptInPipes = usbInterface.InterruptInPipes;
var interruptOutPipes = usbInterface.InterruptOutPipes;
content = "Endpoint Descriptors for open pipes";
// Print Bulk In Endpoint descriptors
foreach (UsbBulkInPipe bulkInPipe in bulkInPipes)
{
var endpointDescriptor = bulkInPipe.EndpointDescriptor;
content += "\n\nBulk In Endpoint Descriptor"
+ "\nEndpoint Number : 0x" + endpointDescriptor.EndpointNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
+ "\nMax Packet Size : " + endpointDescriptor.MaxPacketSize.ToString("D", NumberFormatInfo.InvariantInfo);
}
// Print Bulk Out Endpoint descriptors
foreach (UsbBulkOutPipe bulkOutPipe in bulkOutPipes)
{
var endpointDescriptor = bulkOutPipe.EndpointDescriptor;
content += "\n\nBulk Out Endpoint Descriptor"
+ "\nEndpoint Number : 0x" + endpointDescriptor.EndpointNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
+ "\nMax Packet Size : " + endpointDescriptor.MaxPacketSize.ToString("D", NumberFormatInfo.InvariantInfo);
}
// Print Interrupt In Endpoint descriptors
foreach (UsbInterruptInPipe interruptInPipe in interruptInPipes)
{
var endpointDescriptor = interruptInPipe.EndpointDescriptor;
content += "\n\nInterrupt In Endpoint Descriptor"
+ "\nEndpoint Number : 0x" + endpointDescriptor.EndpointNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
+ "\nMax Packet Size : " + endpointDescriptor.MaxPacketSize.ToString("D", NumberFormatInfo.InvariantInfo)
+ "\nInterval : " + endpointDescriptor.Interval.Duration().ToString();
}
// Print Interrupt Out Endpoint descriptors
foreach (UsbInterruptOutPipe interruptOutPipe in interruptOutPipes)
{
var endpointDescriptor = interruptOutPipe.EndpointDescriptor;
content += "\n\nInterrupt Out Endpoint Descriptor"
+ "\nEndpoint Number : 0x" + endpointDescriptor.EndpointNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
+ "\nMax Packet Size : " + endpointDescriptor.MaxPacketSize.ToString("D", NumberFormatInfo.InvariantInfo)
+ "\nInterval : " + endpointDescriptor.Interval.Duration().ToString();
}
return content;
}
public string GetCustomDescriptorsAsString()
{
string content = null;
// Descriptor information will be appended to this string and then printed to UI
content = "Raw Descriptors";
var configuration = this.usbDevice.Configuration;
var allRawDescriptors = configuration.Descriptors;
// Print first 2 bytes of all descriptors within the configuration descriptor
// because the first 2 bytes are always length and descriptor type
// the UsbDescriptor's DescriptorType and Length properties, but we will not use these properties
// in order to demonstrate ReadDescriptorBuffer() and how to parse it.
foreach (UsbDescriptor descriptor in allRawDescriptors)
{
var descriptorBuffer = new Windows.Storage.Streams.Buffer(descriptor.Length);
descriptor.ReadDescriptorBuffer(descriptorBuffer);
DataReader reader = DataReader.FromBuffer(descriptorBuffer);
// USB data is Little Endian according to the USB spec.
reader.ByteOrder = ByteOrder.LittleEndian;
// ReadByte has a side effect where it consumes the current byte, so the next ReadByte will read the next character.
// Putting multiple ReadByte() on the same line (same variable assignment) may cause the bytes to be read out of order.
var length = reader.ReadByte().ToString("D", NumberFormatInfo.InvariantInfo);
var type = "0x" + reader.ReadByte().ToString("X2", NumberFormatInfo.InvariantInfo);
content += "\n\nDescriptor"
+ "\nLength : " + length
+ "\nDescriptorType : " + type;
}
return content;
}
#endregion GetDescriptorStrings
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace USB_Testing
{
public class BulkOutMessage
{
private byte MsgId;
private byte bTag;
private byte bTagInverse;
private UInt32 TransferSize;
/// <summary>
/// Digit 7 through Digit 2 Reserved. All bits must be 0.
///
/// D1 =
/// 1 – All of the following are true:
/// • The USBTMC interface supports TermChar
/// • The bmTransferAttributes. TermCharEnabled bit was set in the REQUEST_DEV_DEP_MSG_IN.
/// • The last USBTMC message data byte in this transfer matches the TermChar in the REQUEST_DEV_DEP_MSG_IN.
/// 0 – One or more of the above conditions is not met.
/// D0 EOM (End Of Message)
/// 1 - The last USBTMC message data byte in the transfer is the last byte of the USBTMC message.
/// 0 – The last USBTMC message data byte in the transfer is not the last byte of the USBTMC message.
/// </summary>
private byte bmTransferAttributes;
private string Message;
public BulkOutMessage(string messageData, byte bTagValue, bool isLastMessageInTransfer)
{
this.Message = messageData;
this.MsgId = 2;
this.bTag = bTagValue;
// Take the ones complement using '~'
this.bTagInverse = (byte)~this.bTag;
this.TransferSize = (uint)messageData.Length;
/// D1 =
/// 1 – All of the following are true:
/// • The USBTMC interface supports TermChar
/// • The bmTransferAttributes. TermCharEnabled bit was set in the REQUEST_DEV_DEP_MSG_IN.
/// • The last USBTMC message data byte in this transfer matches the TermChar in the REQUEST_DEV_DEP_MSG_IN.
/// 0 – One or more of the above conditions is not met.
byte TermCharSetBit = (byte)0x00;
/// D0 EOM (End Of Message)
/// 1 - The last USBTMC message data byte in the transfer is the last byte of the USBTMC message.
/// 0 – The last USBTMC message data byte in the transfer is not the last byte of the USBTMC message.
byte EomBit = isLastMessageInTransfer ? (byte)0x01 : (byte)0x01;
// Top 6 bits are 0 - bit 1 is TermCharSetBit - bit 0 is EomBit
this.bmTransferAttributes = (byte)(0x00 | (TermCharSetBit << 1) | EomBit);
}
public byte[] ToBytes()
{
List<byte> data = new List<byte>()
{
MsgId,
bTag,
bTagInverse,
0x00, // Reserved
((byte)(TransferSize >> 0)),
((byte)(TransferSize >> 8)),
((byte)(TransferSize >> 16)),
((byte)(TransferSize >> 24)),
bmTransferAttributes,
0x00, // Reserved
0x00, // Reserved
0x00, // Reserved
};
// Add all data to transfer
data.AddRange(Message.Select(x => (byte)x));
data.AddRange(Enumerable.Repeat(0x00, 4 - data.Count % 4).Select(x => (byte)x));
return data.ToArray();
}
}
}
我没有时间分析你的代码,但你可以尝试使用我创建的ScpiNet库。