原创
基于STM32的USB程序开发笔记(七) - 结束
第七篇:XP下USB驱动开发的最终完成 这是我进行的唯一一次驱动开发,对DDK以及DriverStudio知之甚少 ,驱动代码部分不做阐述 ,在这我将STM32-USB驱动-应用程序串联起来说明。 在VC6环境下,连接USB驱动部分我写了个类CUSBAPI来封装该操作,在USBAPI.h文件中: #define FILE_DEVICE_EZUSB 0x8000 #define EZUSB_IOCTL(index) \ CTL_CODE(FILE_DEVICE_EZUSB, index, METHOD_BUFFERED, FILE_READ_DATA) #define ReadFrom_EP2 \ CTL_CODE(FILE_DEVICE_EZUSB, 0x800, METHOD_IN_DIRECT, FILE_ANY_ACCESS) #define WriteTo_EP1 \ CTL_CODE(FILE_DEVICE_EZUSB, 0x801, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) 这部分定义的是DeviceIoControl()函数所需要的I/O控制代码,此定义在DriverWizard生成的interface.h文件中,在这可包含interface.h也可以复制过来进行定义。 typedef struct _NODE_ENUDEVICEINTERFACE { CHAR pDeviceInterfaceSymbolicName[MAX_PATH]; // Index for this node struct _NODE_ENUDEVICEINTERFACE *pNext; } NODE_ENUMDI, *PNODE_ENUMDI; 这个链表用以存储USB设备的接口名称, 需要另外说明的一点,在USBAPI.c文件中定义了: DEFINE_GUID(GUID_DEVICEINTERFACE, 0xE075C5B2, 0xE7FB, 0x4186, 0xA1, 0x39, 0x0B, 0x3F, 0xD2, 0x05, 0xE7, 0x6E); 需要通过这个 GUID_DEVICEINTERFACE 取得该接口名称,有了该 接口名称后就可以通过CreateFile()获取该接口的句柄,进而可以通过ReadFile()/WriteFile()或者DeviceIoControl()读写USB设备。获取 一个设备可能有多个接口,在该类中建立了一个循环链表用以存储该信息。 typedef struct _STRUCT_IO { HWND hTargetWnd; HANDLE hDevice; DWORD dwIoControlCode; PCHAR pInBuffer; DWORD dwInSize; PCHAR pOutBuffer; DWORD dwOutSize; LPDWORD lpBytesReturned; } STRUCT_IO, *PSTRUCT_IO; 这个结构中的 hTargetWnd定义了 消息对象的窗口句柄,用以向该窗口发送读写数据完成的消息; hDevice即USB设备接口的 句柄; 其他的含义很明了,就不说明了。 #define CORESTATUS_SUCCESS 0x0000L #define CORESTATUS_DESTROY 0x0001L #define CORESTATUS_READWRITE_EVENT_ERROR 0x0002L #define CORESTATUS_READWRITE_THREAD_ERROR 0x0003L #define CORESTATUS_IOCONTROL_EVENT_ERROR 0x0004L #define CORESTATUS_IOCONTROL_THREAD_ERROR 0x0005L 这些是定义的类状态,应用程序可以获取这些状态。 #define MSG_READWRITE_COMPLETION WM_USER+0x0010 #define MSG_IOCONTROL_COMPLETION WM_USER+0x0011这些定义的是自定义消息码,应用程序识别此消息码可得知读写操作已完成。 #define ERROR_HANDLE_WINDOW 0x1000L #define ERROR_HANDLE_DEVICE 0x1001L #define ERROR_BUFFER_LENGTH 0x1002L #define ERROR_BUFFER_ISNULL 0x1003L #define ERROR_READWRITE_BUSY 0x1004L #define ERROR_IOCONTROL_BUSY 0x1005L 这些定义的进行读写操作时,进行的一些参数检查并返回的状态。 下面这些是类成员函数以及变量,USBAPI类内建立了两个独立线程,这样在对USB设备进行读写时,就不会堵塞应用程序的窗口线程,读写操作完成后由 消息MSG_READWRITE_COMPLETION和 MSG_IOCONTROL_COMPLETION通知应用程序。详细代码请参考源程序。 // ********************************************************************************************************** // Class members definition // ********************************************************************************************************** class CUSBAPI { public: CUSBAPI(); virtual ~CUSBAPI(); public: DWORD EnumDeviceInterface(LPGUID pGUID); HANDLE OpenDeviceInterface(PCHAR pDeviceInterfaceSymbolicName); DWORD Execute_ReadFile( HWND hWnd, HANDLE hDevice, PCHAR pInBuffer, DWORD dwInSize, LPDWORD lpBytesReturned ); DWORD Execute_WriteFile( HWND hWnd, HANDLE hDevice, PCHAR pOutBuffer, DWORD dwOutSize, LPDWORD lpBytesReturned ); DWORD Execute_IoControl( HWND hWnd, HANDLE hDevice, DWORD dwIoControlCode, PCHAR pInBuffer, DWORD dwInSize, PCHAR pOutBuffer, DWORD dwOutSize, LPDWORD lpBytesReturned ); BOOL Node_HeadCreate(VOID); VOID Node_HeadDelete(VOID); VOID Node_RemoveAll(VOID); BOOL Node_Append(PCHAR pDeviceInterfaceSymbolicName); VOID Node_Remove(PCHAR pDeviceInterfaceSymbolicName); PNODE_ENUMDI Node_Find(PCHAR pDeviceInterfaceSymbolicName); public: GUID GUID_Device; PNODE_ENUMDI pEnumDeviceNode; PNODE_ENUMDI pEnumDeviceHead; HANDLE hEvent_ReadWrite; HANDLE hEvent_IoControl; HANDLE hThread_ReadWrite; HANDLE hThread_IoControl; DWORD dwThreadID_ReadWrite; DWORD dwThreadID_IoControl; STRUCT_IO ReadWrite; STRUCT_IO IoControl; BOOL bExecuting_IoControl; BOOL bExecuting_ReadWrite; DWORD dwCoreStatus; }; #endif 在应用程序中定义变量:CUSBAPI ezUSB; 读写操作函数也就三种: Execute_IoControl() 、Execute_ReadFile()、 Execute_WriteFile() 执行这些函数后,将与USB的驱动程序挂钩,分别响应:Execute_IoControl() -> NTSTATUS ezUSBDevice::DeviceControl(KIrp I) Execute_ReadFile() -> NTSTATUS ezUSBDevice::Read(KIrp I) Execute_WriteFile() -> NTSTATUS ezUSBDevice::Write(KIrp I) 其中NTSTATUS ezUSBDevice::DeviceControl(KIrp I) 根据I.IoctlCode()区别类型,按照此示例说明: ReadFrom_EP2->Execute_IoControl()-> NTSTATUS ezUSBDevice::ReadFrom_EP2_Handler(KIrp I) WriteTo_EP1->Execute_IoControl()-> NTSTATUS ezUSBDevice::WriteTo_EP1_Handler(KIrp I) 这样应用程序与USB驱动之间就建立了通讯渠道, 在驱动函数中:NTSTATUS ezUSBDevice::DeviceControl(KIrp I) NTSTATUS ezUSBDevice::Read(KIrp I) NTSTATUS ezUSBDevice::Write(KIrp I) 执行一些操作就可以与STM32的USB设备进行通讯了,此时就需要很好的掌握DriverStudio封装的各种类库了。 DriverStudio向导生成的框架,一般就只需要更改这三个函数接口,当然,对于DriverStudio向导的一个BUG不可不知: // Initialize each Pipe object EP1_OUT.Initialize(m_Lower, 1, 64); EP2_IN.Initialize(m_Lower, 82, 64); Initialize()函数第二参数是端点地址,在这是16进制表示,这里需要补上0x: // Initialize each Pipe object EP1_OUT.Initialize(m_Lower, 0x01, 64); EP2_IN.Initialize(m_Lower, 0x82, 64); 忘记此处修改的后果是,执行EP2_IN操作将会使系统直接蓝屏。 这三个函数接口中涉及到读写操作方式,比如说buffer或者direct io,具体有什么区别,请从网络搜寻。 USB驱动负责底层通过端口地址及方式与STM32连接后, EP1_OUT:USB主机向USB设备发送数据,void CTR_OUT1(void)函数响应 EP2_IN:USB主机请求USB设备发送数据,void CTR_IN2(void)函数响应 示例中CTR_OUT1()接收2Bytes数据,CTR_IN2()发送2Bytes数据,分别控制LED1-4和定时获取Joystick的状态: void CTR_OUT1(void) { unsigned short portc; unsigned short wCount; wCount = GetBuffDescTable_RXCount(ENDP1); if(wCount == 2) { //portc = GPIO_ReadInputData(GPIOC); BufferCopy_PMAToUser((unsigned char *)&portc, GetBuffDescTable_RXAddr(ENDP1), 2); GPIO_Write(GPIOC, (GPIO_ReadInputData(GPIOC)&0xFF0F)|(portc&0x00F0)); } SetEPR_RXStatus(ENDP1,EP_RX_VALID); SetEPR_TXStatus(ENDP1,EP_TX_STALL); } void CTR_IN2(void) { unsigned short portd = GPIO_ReadInputData(GPIOD) & 0xF800; // 11-15 // Copy the transfer buffer to the endpoint0's buffer BufferCopy_UserToPMA( (unsigned char *)&portd, // transfer buffer GetBuffDescTable_TXAddr(ENDP2), // endpoint 0 TX address 2); SetBuffDescTable_TXCount(ENDP2, 2); SetEPR_RXStatus(ENDP2,EP_RX_DIS); SetEPR_TXStatus(ENDP2,EP_TX_VALID); } 至此,整个基于STM32的USB开发过程的介绍大致说了一遍,详细情况请参考源代码,这篇学习笔记到此结束了,谢谢这段时间关心与支持的朋友们,水平有限欢迎朋友们一起探讨学习。 2008-11-25
用户190532 2009-2-13 23:59
用户187401 2008-12-7 18:09