故事发生
最近在用一台专门用来驱动显示模组的小机器,SMART,TW设计,销售,约2W RMB一台,真是有些贵。
与电脑之间的连接是USB,用MIPI口驱动液晶模组,在上位机软件,给模组传输驱动代码,传输测试图片等。
USB口还是mini口,这个显得有些low,因为mini还是10年前用的多一些,5年前基本都是micro,最近几年都是typec,
现在设备与PC间的USB连接,要么用可靠性高的大方口,要么用typec.
机器插上PC后,在设备连接出现一个COM口,显示为一个串口,波特率为115200,这本来也没什么,模组代码以字符串传输,串口传输速率也是足够了。
但是,但是。。。。突然发现,在传输一张1080P的BMP的时候,居然也是秒传,这张图片5MB以上了,115200的COM居然秒传,显然是不合理的。
抽丝剥茧
既然存在不合理,肯定里面有些特殊的地方,按照工程的态度,我们循序渐进的来分析一下
设备信息
插上USB后,出现一个USB串行设备COM4,VID(03EB),PID(DA01),并没有需要提示安装驱动,或者显示CH340, CP2102等类似专门USB转串口芯片的信息
再查看驱动信息
系统自动为设备安装了USBSER驱动 USB 串行驱动程序 (Usbser.sys)
即:Microsoft为通信和 CDC 控制设备加载 提供的 (Usbser.sys) 驱动程序
通讯分析
设备信息看好,接下来,我们当然要看一下这个设备与PC间的通讯,这个时候,请出我们的Bus Hound
插上设备后,抓到一堆通讯,我们只要提取出有用的就行,主要就是设备描述和配置描述
下面展示一些 内联代码片
。
设备描述18字节, 类型代码02, EP0 64字节, VID 03EB PID DA01
12 01 00 02 02 00 00 40 eb 03 01 da 00 01 00 01 00 01
描述符长度9, 描述符类型-配置, 总长0x43, 接口数2, 电流100mA
09 02 43 00 02 01 00 c0 32
描述符长度9, 描述符类型-接口, CDC通讯设备
09 04 00 00 01 02 02 00 00
05 24 00 10 01
05 24 01 01 00
04 24 02 02
05 24 06 00 01
描述符长度7, 描述符类型-端点, 端点3输入, 中断传输, 64字节, 查询间隔8ms
07 05 83 03 40 00 08
描述符长度9, 描述符类型-接口, 2个端点, CDC-DATA
09 04 01 00 02 0a 00 00 00
描述符长度7, 描述符类型-端点, 端点1输出, 大容量传输, 512字节, 查询间隔0ms, 主机到设备
07 05 01 02 00 02 00
描述符长度7, 描述符类型-端点, 端点2输入, 大容量传输, 512字节, 查询间隔0ms, 设备到主机
07 05 82 02 00 02 00
可以看到这个CDC所有信息了,83是CDC控制,基本不会用到,01是PC到设备BULK传输,82是设备到PC BULK传输,packsize是512字节,是一个480Mbps的USB高速设备
秒传原理
USB-CDC,就是设备端是USB,通过PC驱动,在UI界面呈现出一个串口COM设备,但实际硬件层依旧是一个USB高速通道。COM只是比较通用,容易交互而已,但是常用速率一般115200,传送大量数据非常慢。
COM UI(115200) <-----------> WINDOWS驱动转换 <-----------> USB(480Mbps) <-----------> DEVICE
其实对于这么一个设备,COM的波特率设置多少,设备是不care的,USB带宽是固定的480Mbps,也不care什么串口的stop bit,8bit,hwcontrol…,那只是给用户看的。
设备的上位机在传图的时候,直接操作了底层USB管道,而没有通过串口,所以可以秒传数MBYTE的图片,上位机的界面看上去是C++做的,我不会。。。。。但是C#也有对应的USB库啊,那就是libusb,在C#上被封装成LibUsbDotNet
替换驱动
因为CDC连接PC时候,会被自动安装window的usbser,libusb如果需要访问这个设备的话,我们需要先替换成libusb-win32,记得以前还在圈圈USB年代的时候,写个usb驱动是多么复杂的事情,但是现在不一样了,我们现在有zadig
新设备出现
替换成功后,我们再次查看设备管理器
可以看到这个设备DAOL USB PORT已经挂在libusb-win32 devices下面了
然后我们用libusb专用的设备信息工具来看这个设备
USB的详细信息以UI的方式出现了,是不是很详细,都不用费力的解码数据了,也说明libusb可以连接到这个设备了
读写设备
LibUsbDotNet库的压缩包里面自带范例,我们拿一个出来改动一下
/*USB----------------------------------------------------------------*/
#region USB
private UsbDevice USB_Device = null;
private UsbEndpointWriter USB_EpWriter;
private UsbEndpointReader USB_EpReader;
private IDeviceNotifier USB_DeviceNotifier;
private int USB_VID, USB_PID;
private bool USB_IsConnected = false;
private long USB_WriteTotalByte = 0;
private void UsbPort_Load()
{
txtUSB_VID.Text = file_ini.Read("smart", "usb_vid");
txtUSB_PID.Text = file_ini.Read("smart", "usb_pid");
if (txtUSB_VID.Text.Length != 4) { txtUSB_VID.Text = "0000"; }
if (txtUSB_PID.Text.Length != 4) { txtUSB_PID.Text = "0000"; }
USB_VID = int.Parse(txtUSB_VID.Text, System.Globalization.NumberStyles.HexNumber);
USB_PID = int.Parse(txtUSB_PID.Text, System.Globalization.NumberStyles.HexNumber);
USB_DeviceNotifier = DeviceNotifier.OpenDeviceNotifier();
USB_DeviceNotifier.OnDeviceNotify += new EventHandler<DeviceNotifyEventArgs>(USB_DeviceNotifier_OnDeviceNotify);
}
private void UsbPort_Closed()
{
file_ini.Write("smart", "usb_vid", txtUSB_VID.Text);
file_ini.Write("smart", "usb_pid", txtUSB_PID.Text);
USB_DeviceNotifier.OnDeviceNotify -= new EventHandler<DeviceNotifyEventArgs>(USB_DeviceNotifier_OnDeviceNotify);
if (USB_Device != null)
{
if (USB_Device.IsOpen)
{
IUsbDevice wholeUsbDevice = USB_Device as IUsbDevice;
if (!ReferenceEquals(wholeUsbDevice, null))
{
wholeUsbDevice.ReleaseInterface(0);
}
USB_Device.Close();
}
USB_Device = null;
UsbDevice.Exit();
}
}
private void btnUSB_PortConnect_Click(object sender, EventArgs e)
{
USB_PortConnect();
if (USB_Device != null)
{
btnUSB_PortConnect.Enabled = false;
btnUSB_PortConnect.BackColor = Color.Green;
USB_IsConnected = true;
}
}
private void USB_DeviceNotifier_OnDeviceNotify(object sender, DeviceNotifyEventArgs e)
{
if (e.EventType == EventType.DeviceArrival)
{
if (e.Device.IdProduct == USB_PID && e.Device.IdVendor == USB_VID)
{
btnUSB_PortConnect.BackColor = Color.Green;
USB_IsConnected = true;
}
}
else if (e.EventType == EventType.DeviceRemoveComplete)
{
if (e.Device.IdProduct == USB_PID && e.Device.IdVendor == USB_VID)
{
btnUSB_PortConnect.BackColor = SystemColors.Control;
USB_IsConnected = false;
}
}
}
private void USB_PortConnect()
{
UsbDeviceFinder finder = new UsbDeviceFinder(USB_VID, USB_PID);
UsbRegDeviceList dev_list = UsbDevice.AllDevices.FindAll(finder);
if (dev_list.Count != 0)
{
for (int i = 0; i < dev_list.Count; i++)
{
UsbRegistry dev = dev_list[i];
if (dev.Open(out USB_Device))
{
if (USB_Device.Configs.Count > 0)
{
USB_EpWriter = USB_Device.OpenEndpointWriter(WriteEndpointID.Ep01, EndpointType.Bulk);
USB_EpReader = USB_Device.OpenEndpointReader(ReadEndpointID.Ep02, 4096, EndpointType.Bulk);
IUsbDevice wholeUsbDevice = USB_Device as IUsbDevice;
if (!ReferenceEquals(wholeUsbDevice, null))
{
wholeUsbDevice.SetConfiguration(1);
wholeUsbDevice.ClaimInterface(0);
}
break;
}
USB_Device.Close();
USB_Device = null;
}
}
}
}
private bool UsbPort_Send(byte[] buf, ref int wtout, ref int rtout, ref byte[] recv_data)
{
int transferredOut;
int transferredIn;
UsbTransfer usbWriteTransfer;
UsbTransfer usbReadTransfer;
byte[] bytesToSend = (byte[])buf.Clone();
byte[] readBuffer = new byte[1024];
bool rst = false;
// USB_EpReader.Flush();
// USB_EpWriter.Flush();
// USB_EpReader.Read(readBuffer, 1000, transferredIn);
if (ErrorCode.None != USB_EpReader.SubmitAsyncTransfer(readBuffer, 0, readBuffer.Length, rtout, out usbReadTransfer))
{ //建立接收
return rst;
}
// USB_EpWriter.Write(buf, wtout, out transferredOut);
if (ErrorCode.None != USB_EpWriter.SubmitAsyncTransfer(bytesToSend, 0, bytesToSend.Length, wtout, out usbWriteTransfer))
{ //建立发送
usbReadTransfer.Dispose();
return rst;
}
//等待接收和发送完成
// WaitHandle.WaitAll(new WaitHandle[] { usbReadTransfer.AsyncWaitHandle }, wtout + rtout, false);
WaitHandle.WaitAll(new WaitHandle[] { usbWriteTransfer.AsyncWaitHandle, usbReadTransfer.AsyncWaitHandle }, wtout + rtout, false);
if (!usbWriteTransfer.IsCompleted) usbWriteTransfer.Cancel();
if (!usbReadTransfer.IsCompleted) usbReadTransfer.Cancel();
//传送完成无错
if ((ErrorCode.None == usbWriteTransfer.Wait(out transferredOut)) &&
(ErrorCode.None == usbReadTransfer.Wait(out transferredIn)))
// if (ErrorCode.None == usbReadTransfer.Wait(out transferredIn))
{
wtout = transferredOut;
rtout = transferredIn;
if (readBuffer.Length > 0)
{ //返回接收到的数据
recv_data = new byte[readBuffer.Length];
for (int i = 0; i < readBuffer.Length; i++)
{
recv_data[i] = readBuffer[i];
}
rst = true;
}
}
// USB_EpReader.Flush();
// USB_EpWriter.Flush();
usbWriteTransfer.Dispose();
usbReadTransfer.Dispose();
return rst;
}
#endregion
通讯成功,设备返回了name和version,自此,我们已经可以用USB 480Mbps来通讯了,直接跳过了115200的COM
结束感想
工作期间有断断续续的分析USB协议,也自行做过一些USB上下位机,但是主要以HIDCUSTOMER为主,因为是免驱的。这次是一个USB CDC,从好奇原理到逐层分析,最后成功完成了USB底层通讯,收获还是满满,以后看到任何一个USB设备,都可以不变应万变了。。。。。。
工程的道路学无止境,不要以为这瓶水已经满了,有这种想法的,通常还在瓶底!