随着USB设备的普及,摆在开发人员面前的驱动开发任务也是越来越繁重了,特别是对于一些嵌入式开发厂商来讲,由于设备所采用的操作系统不同,相应的硬件接口也是不一样的,开发相关的USB 驱动程序更是难上加难。Windows CE.NET 是微软推出的功能强大的嵌入式操作系统,国内采用此操作系统的厂商已经很多了,本文就以windows ce.net为例,简单介绍一下如何开发windows ce.net下的USB驱动程序。
首先要熟悉一些USB的基本概念,当然最好把USB 1.1的协议看一遍,(当然现在2。0的协议都已经有了)http://www.usb.org上可以下载,我记得好像有个中文版的,翻译的还可以,http://www.driverdevolep.com上有的,具体位置记不太清楚了,中文版的协议可以快速翻一边,了解一些基本的概念,但是设计到一些关键性的东西最好还是看英文版的心里比较清楚些。
这里我就不介绍USB的基本协议了,假设用户已经熟悉了USB设备的一些基本的概念,并且对Winows CE.NET的开发有一定的了解。
下面简略介绍一下Windows CE.NET中USB设备驱动开发的一些基础知识。
Windows CE.NET 的USB系统软件分为两层: USB Client设备驱动程序和底层的Windows CE实现的函数层。USB设备驱动程序主要负责利用系统提供的底层接口配置设备,和设备进行通讯。底层的函数提本身又由两部分组成,通用串行总线驱动程序(USBD)模块和较低的主控制器驱动程序(HCD)模块。HCD负责最最底层的处理,USBD模块实现较高的USBD函数接口。USB设备驱动主要利用 USBD接口函数和他们的外围设备打交道。
USB设备驱动程序主要和USBD打交道,所以我们必须详细的了解USBD提供的函数。
主要的传输函数有:
AbourtTransfer IssueControlTransferCloseTransfer IssueInterrupTransferGetIsochResult IssueIsochTransferGetTransferStatus IstransferCompleteIssueBulkTransfer IssueVendorTransfer主要的用于打开和关闭USBD和USB设备之间的通信通道的函数有:
AbortPipeTransfers ClosePipeIsDefaultPipeHalted IsPipeHaltedOpenPipe ResetDefaultPipeResetPipe相应的打包函数接口有:
GetFrameLength GetFrameNumber ReleaseFrameLengthControlSetFrameLength TakeFrameLengthControl取得设置设备配置函数:
ClearFeature SetDescriptorGetDescriptor SetFeatureGetInterface SetInterfaceGetStatus SyncFrame与USB进行交互的实现方法相关的多任务函数:
FindInterface RegisterClientDeviceIdGetDeviceInfo RegisterClientSettingsGetUSBDVersion RegisterNotificationRoutineLoadGenericInterfaceDriver TranslateStringDescrOpenClientRegisterKey UnRegisterNotificationRoutine常见的Windows CE.NET下USB的设备驱动程序的编写有以下几种方法:
● 流式接口函数这种驱动程序主要呈现流式函数接口,主要输出XXX_Init,XXX_Deinit,XXX_Open,XXX_Close,XXX_Open,XXX_Close,XXX_Read,XXX_Write,XXX_Seek, XXX_IOControl,XXX_PowerUp,XXX_PowerDown等流式接口,注意上述的几个接口一定都要输出,另外XXX必须为三个字符,否则会出错。但是此类的驱动程序不是通过设备管理接口来加载的,所以必须手工的调用RegisterDevice()和 DeregisterDevice()函数来加载和卸载驱动程序。用户可以将此类的设备作为标准的文件来操作,只要调用相应的文件操作就可以和驱动程序打交道。
● 使用现有的Window CE.NET的应用程序接口此类设备主要是利用Windows CE.NET中已经有了现成的函数接口,例如USB Mass Storage Disk,它主要利用现有的Windows CE.Net中已经有的可安装文件系统接口,呈现给系统可用的文件系统,对于用户来讲,它是透明的,用户仅仅感觉在操作一个文件夹。
● 创建指定到特定的USBD的用户指定的API这种方法在USBD呈现设备时不需要任何限制,主要是特制的提供API给用户,一般不太常见。
USB设备驱动程序必须输出的函数有:
● USBDeviecAttach当USB设备连接到计算机上时,USBD模块就会调用此函数,这个函数主要用于初始化USB设备,取得USB设备信息,配置USB设备,并且申请必需的资源。
● USBInstallDriver主要用于创建一个驱动程序加载所需的注册表信息,例如读写超时,设备名称等。
● USBUninstallDriver主要用于释放驱动程序所占用的资源,以及删除USBInstallDriver函数创建的注册表等。
上述的三个函数接口是所有的USB驱动程序必须提供的,缺一不可。
另外比较重要的是USB设备驱动程序的注册表配置,一般的USB设备驱动程序的注册表配置在HKEY_LOCAL_MACHINEDriversUSB LoadClients下,每个驱动程序的子键都有Group1_IDGroup2_IDGroup3_IDDriverName格式,如果注册表信息与USB设备信息符合,USBD就会加载此驱动程序。否则设备的子键应该由供应商,设备类和协议信息通过下划线组成。
具体的配置举个例子:
例如你有个PDA设备,它具有一个USB接口,它的供应厂商ID假设为0x0888,设备ID为0x0999,没有使用特殊的协议,那么它的加载注册表应该写为:
[HKEY_LOCAL_MACHINEDriversUSBLoadClients2184_2457DefaultDefaultPDA] "DLL"="pdausb.dll"需要注意的是注册表构成都是十进制数值来标识的,注意一下十进制和十六进制的转换。
再举个USB鼠标的例子,USB鼠标是标准的HID设备,它的协议为:InterfaceClassCode为3(HID类), InterfaceSubclassCode为1(引导接口类),InterfaceProtocolCode为2(鼠标协议类),所以它的注册如下:
[HKEY_LOCAL_MACHINEDriversUSBLoadClientsDefaultDefault3_1_2USBMouse] "DLL"="usbmouse.dll"到此为止,我们可以看出,其实驱动开发无非做两件事情,一件是和硬件打交道,另外一件是和操作系统打交道。举个简单的例子,例如:我们需要开发一个USB鼠标驱动程序,我们就需要了解USB鼠标硬件上是怎么发送数据的?操作系统怎么才能得到鼠标的控制事件?其实USB鼠标是有一个中断PIPE的,用于传送鼠标产生的数据,Windwos CE.NET中有个接口函数叫做mouse_event(),专门用于产生鼠标事件,但是它是不关心具体什么硬件的,甚至我们自己在应用程序中调用这个函数都可以实现模拟鼠标,对应的有个keybd_event(),用于产生键盘事件,知道了这个就好办多了,只要将相应的数据转换一下,调用一下 mouse_event()即可例如我们有个USB Mouse设备,设备信息描述如下:
Device Descriptor:
bcdUSB: 0x0100bDeviceClass: 0x00bDeviceSubClass: 0x00bDeviceProtocol: 0x00bMaxPacketSize0: 0x08 (8)idVendor: 0x05E3 (Genesys Logic Inc.)idProduct: 0x0001bcdDevice: 0x0101iManufacturer: 0x00iProduct: 0x01iSerialNumber: 0x00bNumConfigurations: 0x01ConnectionStatus: DeviceConnectedCurrent Config Value: 0x01Device Bus Speed: LowDevice Address: 0x02Open Pipes: 1Endpoint Descriptor:
bEndpointAddress: 0x81Transfer Type: InterruptwMaxPacketSize: 0x0003 (3)bInterval: 0x0A可以看出上述设备有一个中断PIPE,包的最大值为3。可能有人问上述的值怎么得到的,win2k 的DDK中有个usbview的例程,编译一下,将你的USB设备插到PC机的USB口中,运行usbview.exe即可看得相应的设备信息。
有了这些基本信息,就可以编写USB设备了,首先声明一下,下面的代码取自微软的USB鼠标样本程序,版权归微软所有,此处仅仅借用来描述一下USB鼠标驱动的开发过程,读者如需要引用此代码,需要得到微软的同意。
首先,必须输出USBD要求调用的三个函数,首先到设备插入到USB端口时,USBD会调用USBDeviceAttach()函数,相应的代码如下:
extern "C" BOOLUSBDeviceAttach(USB_HANDLE hDevice, // USB设备句柄LPCUSB_FUNCS lpUsbFuncs, // USBDI的函数集合LPCUSB_INTERFACE lpInterface, // 设备接口描述信息LPCWSTR szUniqueDriverId, // 设备ID描述字符串。
LPBOOL fAcceptControl, // 返回TRUE,标识我们可以控制此设备, 反之表示不能控制DWORD dwUnused){*fAcceptControl = FALSE;// 我们的鼠标设备有特定的描述信息,要检测是否是我们的设备。
if (lpInterface == NULL)return FALSE;// 打印相关的USB设备接口描述信息。
DEBUGMSG(ZONE_INIT,(TEXT("USBMouse: DeviceAttach, IF %u, #EP:%u, Class:%u, Sub:%u,Prot:%urn"), lpInterface->Descriptor.bInterfaceNumber,lpInterface->Descriptor.bNumEndpoints, lpInterface->Descriptor.bInterfaceClass,lpInterface->Descriptor.bInterfaceSubClass,lpInterface->Descriptor.bInterfaceProtocol));// 初试数据USB鼠标类,产生一个接受USB鼠标数据的线程CMouse * pMouse = new CMouse(hDevice, lpUsbFuncs, lpInterface);if (pMouse == NULL)return FALSE;if (!pMouse->Initialize()){delete pMouse;return FALSE;}
// 注册一个监控USB设备事件的回调函数,用于监控USB设备是否已经拔掉。
(*lpUsbFuncs->lpRegisterNotificationRoutine)(hDevice,USBDeviceNotifications, pMouse);*fAcceptControl = TRUE;return TRUE;}
第二个函数是 USBInstallDriver()函数,一些基本定义如下:
const WCHAR gcszRegisterClientDriverId[] = L"RegisterClientDriverID";const WCHAR gcszRegisterClientSettings[] = L"RegisterClientSettings";const WCHAR gcszUnRegisterClientDriverId[] = L"UnRegisterClientDriverID";const WCHAR gcszUnRegisterClientSettmngs[] = L"UnRegisterClientSettings";const WCHAR gcszMouseDriverId[] = L"Generic_Sample_Mouse_Driver";函数接口如下:
extern "C" BOOLUSBInstallDriver(LPCWSTR szDriverLibFile) // @parm [IN] - Contains client driver DLL name{BOOL fRet = FALSE;HINSTANCE hInst = LoadLibrary(L"USBD.DLL");// 注册USB设备信息if(hInst){LPREGISTER_CLIENT_DRIVER_ID pRegisterId = (LPREGISTER_CLIENT_DRIVER_ID)GetProcAddress(hInst, gcszRegisterClientDriverId);LPREGISTER_CLIENT_SETTINGS pRegisterSettings =(LPREGISTER_CLIENT_SETTINGS) GetProcAddress(hInst,gcszRegisterClientSettings);if(pRegisterId && pRegisterSettings){USB_DRIVER_SETTINGS DriverSettings;DriverSettings.dwCount = sizeof(DriverSettings);// 设置我们的特定的信息。
DriverSettings.dwVendorId = USB_NO_INFO;DriverSettings.dwProductId = USB_NO_INFO;DriverSettings.dwReleaseNumber = USB_NO_INFO;DriverSettings.dwDeviceClass = USB_NO_INFO;DriverSettings.dwDeviceSubClass = USB_NO_INFO;DriverSettings.dwDeviceProtocol = USB_NO_INFO;DriverSettings.dwInterfaceClass = 0x03; // HIDDriverSettings.dwInterfaceSubClass = 0x01; // boot deviceDriverSettings.dwInterfaceProtocol = 0x02; // mousefRet = (*pRegisterId)(gcszMouseDriverId);if(fRet){fRet = (*pRegisterSettings)(szDriverLibFile,gcszMouseDriverId, NULL, &DriverSettings);if(!fRet){//BUGBUG unregister the Client Driver’s ID}
}
}
else{RETAILMSG(1,(TEXT("!USBMouse: Error getting USBD function pointersrn")));}
FreeLibrary(hInst);}
return fRet;}
上述代码主要用于产生USB设备驱动程序需要的注册表信息,需要注意的是:USB设备驱动程序不使用标准的注册表函数,而是使用RegisterClientDriverID()和RegisterClientSettings来注册相应的设备信息。
另外一个函数是USBUninstallDriver()函数,具体代码如下:
extern "C" BOOLUSBUnInstallDriver(){BOOL fRet = FALSE;HINSTANCE hInst = LoadLibrary(L"USBD.DLL");if(hInst){LPUN_REGISTER_CLIENT_DRIVER_ID pUnRegisterId =(LPUN_REGISTER_CLIENT_DRIVER_ID)GetProcAddress(hInst, gcszUnRegisterClientDriverId);LPUN_REGISTER_CLIENT_SETTINGS pUnRegisterSettings =(LPUN_REGISTER_CLIENT_SETTINGS) GetProcAddress(hInst,gcszUnRegisterClientSettings);if(pUnRegisterSettings){USB_DRIVER_SETTINGS DriverSettings;DriverSettings.dwCount = sizeof(DriverSettings);// 必须填入与注册时相同的信息。
DriverSettings.dwVendorId = USB_NO_INFO;DriverSettings.dwProductId = USB_NO_INFO;DriverSettings.dwReleaseNumber = USB_NO_INFO;DriverSettings.dwDeviceClass = USB_NO_INFO;DriverSettings.dwDeviceSubClass = USB_NO_INFO;DriverSettings.dwDeviceProtocol = USB_NO_INFO;DriverSettings.dwInterfaceClass = 0x03; // HIDDriverSettings.dwInterfaceSubClass = 0x01; // boot deviceDriverSettings.dwInterfaceProtocol = 0x02; // mousefRet = (*pUnRegisterSettings)(gcszMouseDriverId, NULL,&DriverSettings);}
if(pUnRegisterId){BOOL fRetTemp = (*pUnRegisterId)(gcszMouseDriverId);fRet = fRet ? fRetTemp : fRet;}
FreeLibrary(hInst);}
return fRet;}
此函数主要用于删除USBInstallDriver()时创建的注册表信息,同样的它使用自己的函数接口UnRegisterClientDriverID()和UnRegisterClientSettings()来做相应的处理。
另外一个需要处理的注册的监控通知函数USBDeviceNotifications():
extern "C" BOOL USBDeviceNotifications(LPVOID lpvNotifyParameter, DWORD dwCode,LPDWORD * dwInfo1, LPDWORD * dwInfo2, LPDWORD * dwInfo3,LPDWORD * dwInfo4){CMouse * pMouse = (CMouse *)lpvNotifyParameter;switch(dwCode){case USB_CLOSE_DEVICE:
//删除相关的资源。
delete pMouse;return TRUE;}
return FALSE;}
USB鼠标的类的定义如下:
class CMouse{public:
CMouse::CMouse(USB_HANDLE hDevice, LPCUSB_FUNCS lpUsbFuncs,LPCUSB_INTERFACE lpInterface);~CMouse();BOOL Initialize();private:
// 传输完毕调用的回调函数static DWORD CALLBACK MouseTransferCompleteStub(LPVOID lpvNotifyParameter);// 中断处理函数static ULONG CALLBACK CMouse::MouseThreadStub(PVOID context);DWORD MouseTransferComplete();DWORD MouseThread();BOOL SubmitInterrupt();BOOL HandleInterrupt();BOOL m_fClosing;BOOL m_fReadyForMouseEvents;HANDLE m_hEvent;HANDLE m_hThread;USB_HANDLE m_hDevice;USB_PIPE m_hInterruptPipe;USB_TRANSFER m_hInterruptTransfer;LPCUSB_FUNCS m_lpUsbFuncs;LPCUSB_INTERFACE m_pInterface;BOOL m_fPrevButton1;BOOL m_fPrevButton2;BOOL m_fPrevButton3;// 数据接受缓冲区。
BYTE m_pbDataBuffer[8];};具体实现如下:
// 构造函数,初始化时调用CMouse::CMouse(USB_HANDLE hDevice, LPCUSB_FUNCS lpUsbFuncs,LPCUSB_INTERFACE lpInterface){m_fClosing = FALSE;m_fReadyForMouseEvents = FALSE;m_hEvent = NULL;m_hThread = NULL;m_hDevice = hDevice;m_hInterruptPipe = NULL;m_hInterruptTransfer = NULL;m_lpUsbFuncs = lpUsbFuncs;m_pInterface = lpInterface;m_fPrevButton1 = FALSE;m_fPrevButton2 = FALSE;m_fPrevButton3 = FALSE;memset(m_pbDataBuffer, 0, sizeof(m_pbDataBuffer));}
// 析构函数,用于清除申请的资源。
CMouse::~CMouse(){// 通知系统去关闭相关的函数接口。
m_fClosing = TRUE;// Wake up the connection thread again and give it time to die.
if (m_hEvent != NULL){// 通知关闭数据接受线程。
SetEvent(m_hEvent);if (m_hThread != NULL){DWORD dwWaitReturn;dwWaitReturn = WaitForSingleObject(m_hThread, 1000);if (dwWaitReturn != WAIT_OBJECT_0){TerminateThread(m_hThread, DWORD(-1));}
CloseHandle(m_hThread);m_hThread = NULL;}
CloseHandle(m_hEvent);m_hEvent = NULL;}
if(m_hInterruptTransfer)(*m_lpUsbFuncs->lpCloseTransfer)(m_hInterruptTransfer);if(m_hInterruptPipe)(*m_lpUsbFuncs->lpClosePipe)(m_hInterruptPipe);}
// 初始化USB鼠标驱动程序BOOL CMouse::Initialize(){LPCUSB_DEVICE lpDeviceInfo = (*m_lpUsbFuncs->lpGetDeviceInfo)(m_hDevice);// 检测配置:USB鼠标应该只有一个中断管道if ((m_pInterface->lpEndpoints[0].Descriptor.bmAttributes & USB_ENDPOINT_TYPE_MASK) != USB_ENDPOINT_TYPE_INTERRUPT){RETAILMSG(1,(TEXT("!USBMouse: EP 0 wrong type (%u)!rn"),m_pInterface->lpEndpoints[0].Descriptor.bmAttributes));return FALSE;}
DEBUGMSG(ZONE_INIT,(TEXT("USBMouse: EP 0:MaxPacket: %u, Interval: %urn"),m_pInterface->lpEndpoints[0].Descriptor.wMaxPacketSize,m_pInterface->lpEndpoints[0].Descriptor.bInterval));m_hInterruptPipe = (*m_lpUsbFuncs->lpOpenPipe)(m_hDevice,&m_pInterface->lpEndpoints[0].Descriptor);if (m_hInterruptPipe == NULL) {RETAILMSG(1,(TEXT("Mouse: Error opening interrupt pipern")));return (FALSE);}
m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);if (m_hEvent == NULL){RETAILMSG(1,(TEXT("USBMouse: Error on CreateEvent for connect eventrn")));return(FALSE);}
// 创建数据接受线程m_hThread = CreateThread(0, 0, MouseThreadStub, this, 0, NULL);if (m_hThread == NULL){RETAILMSG(1,(TEXT("USBMouse: Error on CreateThreadrn")));return(FALSE);}
return(TRUE);}
// 从USB鼠标设备中读出数据,产生相应的鼠标事件。
BOOL CMouse::SubmitInterrupt(){if(m_hInterruptTransfer)(*m_lpUsbFuncs->lpCloseTransfer)(m_hInterruptTransfer);// 从USB鼠标PIPE中读数据m_hInterruptTransfer = (*m_lpUsbFuncs->lpIssueInterruptTransfer)(m_hInterruptPipe, MouseTransferCompleteStub, this,USB_IN_TRANSFER | USB_SHORT_TRANSFER_OK, // 表示读数据min(m_pInterface->lpEndpoints[0].Descriptor.wMaxPacketSize,sizeof(m_pbDataBuffer)),m_pbDataBuffer,NULL);if (m_hInterruptTransfer == NULL){DEBUGMSG(ZONE_ERROR,(L "!USBMouse: Error in IssueInterruptTransferrn"));return FALSE;}
else{DEBUGMSG(ZONE_TRANSFER,(L"USBMouse::SubmitInterrupt,Transfer:0x%Xrn",m_hInterruptTransfer));}
return TRUE;}
// 处理鼠标中断传输的数据BOOL CMouse::HandleInterrupt(){DWORD dwError;DWORD dwBytes;DWORD dwFlags = 0;INT dx = (signed char)m_pbDataBuffer[1];INT dy = (signed char)m_pbDataBuffer[2];BOOL fButton1 = m_pbDataBuffer[0] & 0x01 ? TRUE : FALSE;BOOL fButton2 = m_pbDataBuffer[0] & 0x02 ? TRUE : FALSE;BOOL fButton3 = m_pbDataBuffer[0] & 0x04 ? TRUE : FALSE;if (!(*m_lpUsbFuncs->lpGetTransferStatus)(m_hInterruptTransfer, &dwBytes,&dwError)){DEBUGMSG(ZONE_ERROR,(TEXT("!USBMouse: Error in GetTransferStatus(0x%X)rn"),m_hInterruptTransfer));return FALSE;}
else{DEBUGMSG(ZONE_TRANSFER,(TEXT("USBMouse::HandleInterrupt, hTransfer 0x%X complete (%u bytes, Error:%X)rn"),m_hInterruptTransfer,dwBytes,dwError));}
if (!SubmitInterrupt())return FALSE;if(dwError != USB_NO_ERROR){DEBUGMSG(ZONE_ERROR,(TEXT("!USBMouse: Error 0x%X in interrupt transferrn"),dwError));return TRUE;}
if(dwBytes < 3){DEBUGMSG(ZONE_ERROR,(TEXT("!USBMouse: Invalid byte cnt %u from interrupt transferrn"),dwBytes));return TRUE;}
if(dx || dy)dwFlags |= MOUSEEVENTF_MOVE;if(fButton1 != m_fPrevButton1){if(fButton1)dwFlags |= MOUSEEVENTF_LEFTDOWN;elsedwFlags |= MOUSEEVENTF_LEFTUP;}
if(fButton2 != m_fPrevButton2){if(fButton2)dwFlags |= MOUSEEVENTF_RIGHTDOWN;elsedwFlags |= MOUSEEVENTF_RIGHTUP;}
if(fButton3 != m_fPrevButton3){if(fButton3)dwFlags |= MOUSEEVENTF_MIDDLEDOWN;elsedwFlags |= MOUSEEVENTF_MIDDLEUP;}
m_fPrevButton1 = fButton1;m_fPrevButton2 = fButton2;m_fPrevButton3 = fButton3;DEBUGMSG(ZONE_EVENTS,(TEXT("USBMouse event: dx:%d, dy:%d, dwFlags:0x%X (B1:%u, B2:%u, B3:%u)rn"),dx,dy,dwFlags,fButton1,fButton2,fButton3));// 通知系统产生鼠标事件if (m_fReadyForMouseEvents)mouse_event(dwFlags, dx, dy, 0, 0);elsem_fReadyForMouseEvents = IsAPIReady(SH_WMGR);return TRUE;}
DWORD CALLBACK CMouse::MouseTransferCompleteStub(LPVOID lpvNotifyParameter){CMouse * pMouse = (CMouse *)lpvNotifyParameter;return(pMouse->MouseTransferComplete());}
// 数据传输完毕回调函数DWORD CMouse::MouseTransferComplete(){if (m_hEvent)SetEvent(m_hEvent);return 0;}
ULONG CALLBACK CMouse::MouseThreadStub(PVOID context){CMouse * pMouse = (CMouse *)context;return(pMouse->MouseThread());}
// USB鼠标线程DWORD CMouse::MouseThread(){DEBUGMSG(ZONE_INIT,(TEXT("USBMouse: Worker thread startedrn")));SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);if (SubmitInterrupt()){while (!m_fClosing){WaitForSingleObject(m_hEvent, INFINITE);if (m_fClosing)break;if ((*m_lpUsbFuncs->lpIsTransferComplete)(m_hInterruptTransfer)){if (!HandleInterrupt())break;}
else{RETAILMSG(1,(TEXT("!USBMouse: Event signalled, but transfer not completern")));// The only time this should happen is if we get an error on the transferASSERT(m_fClosing || (m_hInterruptTransfer == NULL));break;}
}
}
RETAILMSG(1,(TEXT("USBMouse: Worker thread exitingrn")));return(0);}
看到了没有,其实USB的驱动程序编写就这么简单,类似的其他设备,例如打印机设备,就有Bulk OUT PIPE,需要Bulk传输,那就需要了解一下IssueBulkTransfer()的应用。当然如果是开发USB Mass Storage Disk的驱动,那就需要了解更多的协议,例如Bulk-Only Transport协议等。
微软的Windows CE.NET的Platform Build中已经带有USB Printer和USB Mass Storage Disk的驱动的源代码了,好好研究一下,你一定回受益非浅的。
上一篇:Windows下NFS服务器设置
下一篇:Windows CE下的注册表简介
- 安世半导体&世平集团 Nexperia 在5G基础架构的应用 下载赢好礼!
- 有奖直播 | ST在工业自动化中的PLC方案
- e络盟邀您齐聚2023慕尼黑电子展,三重好礼等你拿!
- 免费测评|ESP32-S2-Kaluga-1新型多媒体开发板,灵活拆装,满足多种需求
- 有奖技术直播:Keysight量子计算测量方案线上研讨会
- TI MCU 推新了!八月直播揭秘新特性~报名观看赢好礼!
- 是德科技电子书 《X-Apps藏宝图: 能够加速测试的信号分析仪必备测量App》下载有好礼!
- 抢楼有礼:看直播,深入了解ST最新 MEMS气压计原理、操作、防水结构设计
- 阅读TI工业应用方案精彩专题,开启任意宝箱,挑战答题抽好礼!