Få åtkomst till en USB-enhet med hjälp av WinUSB-funktioner
Den här artikeln innehåller en detaljerad genomgång av hur du använder WinUSB-funktioner för att kommunicera med en USB-enhet som använder Winusb.sys som funktionsdrivrutin.
Sammanfattning
- Öppna enheten och hämta WinUSB-handtaget.
- Hämta information om enhets-, konfigurations- och gränssnittsinställningarna för alla gränssnitt och deras slutpunkter.
- Läsa och skriva data till bulköverförings- och avbrottsslutpunkter.
Viktiga API:er
Om du använder Microsoft Visual Studio 2013 skapar du din skelettapp med hjälp av WinUSB-mallen. I så fall hoppar du över steg 1 till och med 3 och fortsätter från steg 4 i den här artikeln. Mallen öppnar ett filhandtag till enheten och hämtar WinUSB-handtaget som krävs för senare åtgärder. Handtaget lagras i den appdefinierade DEVICE_DATA-strukturen i device.h.
Mer information om mallen finns i Skriva en Windows-skrivbordsapp baserat på WinUSB-mallen.
Not
WinUSB-funktioner kräver Windows XP eller senare. Du kan använda dessa funktioner i C/C++-programmet för att kommunicera med DIN USB-enhet. Microsoft tillhandahåller inte ett hanterat API för WinUSB.
Innan du börjar
Följande objekt gäller för den här genomgången:
- Den här informationen gäller för Windows 8.1, Windows 8, Windows 7, Windows Server 2008, Windows Vista-versioner av Windows.
- Winusb.sys installeras som enhetens funktionsdrivrutin. Mer information om den här processen finns i WinUSB (Winusb.sys) Installation.
- Exemplen i den här artikeln baseras på OSR USB FX2 Learning Kit-enheten. Du kan använda de här exemplen för att utöka procedurerna till andra USB-enheter.
Steg 1: Skapa en skelettapp baserat på WinUSB-mallen
Om du vill komma åt en USB-enhet börjar du med att skapa en skelettapp baserat på WinUSB-mallen som ingår i den integrerade miljön i Windows Driver Kit (WDK) (med felsökningsverktyg för Windows) och Microsoft Visual Studio. Du kan använda mallen som utgångspunkt.
Information om mallkoden, hur du skapar, skapar, distribuerar och felsöker skelettappen finns i Skriva en Windows-skrivbordsapp baserat på WinUSB-mallen.
Mallen räknar upp enheter med hjälp av SetupAPI- rutiner, öppnar ett filhandtag för enheten och skapar ett WinUSB-gränssnittshandtag som krävs för efterföljande uppgifter. Exempel på kod som hämtar enhetshandtaget och öppnar enheten finns i Mallkodsdiskussion.
Steg 2: Fråga enheten efter USB-beskrivningar
Fråga sedan enheten efter USB-specifik information, till exempel enhetshastighet, gränssnittsbeskrivningar, relaterade slutpunkter och deras rör. Proceduren liknar den som USB-enhetsdrivrutiner använder. Programmet slutför dock enhetsfrågor genom att anropa WinUsb_GetDescriptor.
I följande lista visas de WinUSB-funktioner som du kan anropa för att hämta USB-specifik information:
Fler enhetsdetaljer
Anropa WinUsb_QueryDeviceInformation för att begära information från enhetens enhetsbeskrivningar. Om du vill hämta enhetens hastighet anger du DEVICE_SPEED (0x01) i parametern InformationType. Funktionen returnerar LowSpeed (0x01) eller HighSpeed (0x03).
Gränssnittsbeskrivningar
Anropa WinUsb_QueryInterfaceSettings och passera enhetens gränssnittshandtag för att erhålla de motsvarande gränssnittsbeskrivningarna. WinUSB-gränssnittshandtaget motsvarar det första gränssnittet. Vissa USB-enheter, till exempel OSR Fx2-enheten, stöder endast ett gränssnitt utan någon alternativ inställning. För dessa enheter anges därför parametern AlternateSettingNumber till noll och funktionen anropas bara en gång. WinUsb_QueryInterfaceSettings fyller den av anroparen allokerade USB_INTERFACE_DESCRIPTOR struktur (som skickas i parametern UsbAltInterfaceDescriptor) med information om gränssnittet. Till exempel anges antalet slutpunkter i gränssnittet i bNumEndpoints medlem i USB_INTERFACE_DESCRIPTOR.
För enheter som stöder flera gränssnitt anropar du WinUsb_GetAssociatedInterface för att hämta gränssnittshandtag för associerade gränssnitt genom att ange de alternativa inställningarna i parametern AssociatedInterfaceIndex.
Slutpunkter
Anropa WinUsb_QueryPipe för att få information om varje slutpunkt i varje gränssnitt. WinUsb_QueryPipe fyller i den anroparallokerade WINUSB_PIPE_INFORMATION strukturen med information om pipen för den angivna slutpunkten. Ett nollbaserat index identifierar slutpunkternas rör och måste vara mindre än värdet i bNumEndpoints medlem i gränssnittsbeskrivningen som hämtas i föregående anrop till **WinUsb_QueryInterfaceSettings. OSR Fx2-enheten har ett gränssnitt som har tre slutpunkter. För den här enheten är funktionens parameter AlternateInterfaceNumber inställd på 0 och värdet för parametern PipeIndex varierar från 0 till 2.
För att fastställa rörtypen undersöker du WINUSB_PIPE_INFORMATION strukturens PipeInfo- medlem. Den här medlemmen är inställd på något av USBD_PIPE_TYPE uppräkningsvärdena: UsbdPipeTypeControl, UsbdPipeTypeIsochronous, UsbdPipeTypeBulk eller UsbdPipeTypeInterrupt. OSR USB FX2-enheten har stöd för ett avbrottsrör, ett massindelat rör och ett bulk-out-rör, så PipeInfo är inställt på antingen UsbdPipeTypeInterrupt eller UsbdPipeTypeBulk. UsbdPipeTypeBulk-värdet identifierar bulkrör, men anger inte rörets riktning. Riktningsinformationen kodas i den höga biten av pipeadressen, som lagras i WINUSB_PIPE_INFORMATION-strukturens PipeId element. Det enklaste sättet att fastställa riktningen för röret är att skicka PipeId värde till något av följande makron från Usb100.h:
- Makrot
USB_ENDPOINT_DIRECTION_IN (PipeId)
returnerar SANN om riktningen är inåt. - Makrot
USB_ENDPOINT_DIRECTION_OUT(PipeId)
returnerar TRUE om riktningen är utåt.
Programmet använder värdet PipeId för att identifiera vilket rör som ska användas för dataöverföring i anrop till WinUSB-funktioner, till exempel WinUsb_ReadPipe (beskrivet i avsnittet "Utfärda I/O-begäranden" i denna text), så exemplet lagrar alla tre PipeId-värden för senare användning.
- Makrot
Följande exempelkod hämtar hastigheten på den enhet som specificeras av WinUSB-gränssnittshandtaget.
BOOL GetUSBDeviceSpeed(WINUSB_INTERFACE_HANDLE hDeviceHandle, UCHAR* pDeviceSpeed)
{
if (!pDeviceSpeed || hDeviceHandle==INVALID_HANDLE_VALUE)
{
return FALSE;
}
BOOL bResult = TRUE;
ULONG length = sizeof(UCHAR);
bResult = WinUsb_QueryDeviceInformation(hDeviceHandle, DEVICE_SPEED, &length, pDeviceSpeed);
if(!bResult)
{
printf("Error getting device speed: %d.\n", GetLastError());
goto done;
}
if(*pDeviceSpeed == LowSpeed)
{
printf("Device speed: %d (Low speed).\n", *pDeviceSpeed);
goto done;
}
if(*pDeviceSpeed == FullSpeed)
{
printf("Device speed: %d (Full speed).\n", *pDeviceSpeed);
goto done;
}
if(*pDeviceSpeed == HighSpeed)
{
printf("Device speed: %d (High speed).\n", *pDeviceSpeed);
goto done;
}
done:
return bResult;
}
Följande exempelkod hämtar de olika deskriptorerna för den USB-enhet som anges av WinUSB-gränssnittshandtaget. Exempelfunktionen hämtar typerna av ändpunkter som stöds och deras pipe-ID. I exemplet lagras alla tre PipeId-värden för senare användning.
struct PIPE_ID
{
UCHAR PipeInId;
UCHAR PipeOutId;
};
BOOL QueryDeviceEndpoints (WINUSB_INTERFACE_HANDLE hDeviceHandle, PIPE_ID* pipeid)
{
if (hDeviceHandle==INVALID_HANDLE_VALUE)
{
return FALSE;
}
BOOL bResult = TRUE;
USB_INTERFACE_DESCRIPTOR InterfaceDescriptor;
ZeroMemory(&InterfaceDescriptor, sizeof(USB_INTERFACE_DESCRIPTOR));
WINUSB_PIPE_INFORMATION Pipe;
ZeroMemory(&Pipe, sizeof(WINUSB_PIPE_INFORMATION));
bResult = WinUsb_QueryInterfaceSettings(hDeviceHandle, 0, &InterfaceDescriptor);
if (bResult)
{
for (int index = 0; index < InterfaceDescriptor.bNumEndpoints; index++)
{
bResult = WinUsb_QueryPipe(hDeviceHandle, 0, index, &Pipe);
if (bResult)
{
if (Pipe.PipeType == UsbdPipeTypeControl)
{
printf("Endpoint index: %d Pipe type: Control Pipe ID: %d.\n", index, Pipe.PipeType, Pipe.PipeId);
}
if (Pipe.PipeType == UsbdPipeTypeIsochronous)
{
printf("Endpoint index: %d Pipe type: Isochronous Pipe ID: %d.\n", index, Pipe.PipeType, Pipe.PipeId);
}
if (Pipe.PipeType == UsbdPipeTypeBulk)
{
if (USB_ENDPOINT_DIRECTION_IN(Pipe.PipeId))
{
printf("Endpoint index: %d Pipe type: Bulk Pipe ID: %c.\n", index, Pipe.PipeType, Pipe.PipeId);
pipeid->PipeInId = Pipe.PipeId;
}
if (USB_ENDPOINT_DIRECTION_OUT(Pipe.PipeId))
{
printf("Endpoint index: %d Pipe type: Bulk Pipe ID: %c.\n", index, Pipe.PipeType, Pipe.PipeId);
pipeid->PipeOutId = Pipe.PipeId;
}
}
if (Pipe.PipeType == UsbdPipeTypeInterrupt)
{
printf("Endpoint index: %d Pipe type: Interrupt Pipe ID: %d.\n", index, Pipe.PipeType, Pipe.PipeId);
}
}
else
{
continue;
}
}
}
done:
return bResult;
}
Steg 3: Skicka kontrollöverföring till standardslutpunkten
Kommunicera sedan med enheten genom att utfärda kontrollbegäran till standardslutpunkten.
Alla USB-enheter har en standardslutpunkt utöver de slutpunkter som är associerade med gränssnitt. Det primära syftet med standardslutpunkten är att ge värden information som den kan använda för att konfigurera enheten. Enheter kan dock också använda standardslutpunkten för enhetsspecifika ändamål. Till exempel använder OSR USB FX2-enheten standardslutpunkten för att styra ljusstapeln och den digitala bildskärmen med sju segment.
Kontrollkommandon består av ett konfigurationspaket på 8 byte, som innehåller en begärandekod som anger den specifika begäran och en valfri databuffert. Begärandekoderna och buffertformaten är leverantörsdefinierade. I det här exemplet skickar programmet data till enheten för att styra ljusfältet. Koden för att ange ljusstapeln är 0xD8, som definieras för enkelhetens skull som SET_BARGRAPH_DISPLAY. För den här begäran kräver enheten en databuffert på 1 byte som anger vilka element som ska tändas genom att ange lämpliga bitar.
Programmet kan ge en uppsättning med åtta kryssrutekontroller för att ange vilka element i ljusstapeln som ska tändas. De angivna elementen motsvarar lämpliga bitar i bufferten. För att undvika UI-kod anger exempelkoden i det här avsnittet bitarna så att alternativa lampor tänds.
Så här utfärdar du en kontrollbegäran
Allokera en databuffert på 1 byte och läs in data i bufferten som anger de element som ska tändas genom att ange lämpliga bitar.
Konstruera ett installationspaket i en uppringarallokerad WINUSB_SETUP_PACKET struktur. Initiera medlemmarna så att de representerar begärandetypen och data enligt följande:
- Medlemmen RequestType anger begäranderiktning. RequestType är inställt på 0, vilket anger dataöverföring från värd till enhet. För överföringar från enhet till värd anger du RequestType till 1.
- Medlemmen för Request är angiven till leverantörens definierade kod för denna begäran, 0xD8. Request definieras som SET_BARGRAPH_DISPLAY för enkelhetens skull.
- Fältet Längd är inställt på storleken på databufferten.
- Index och Value är medlemmar som inte krävs för den här begäran, så de sätts till noll.
Anropa WinUsb_ControlTransfer för att överföra begäran till standardslutpunkten genom att skicka enhetens WinUSB-gränssnittshandtag, installationspaketet och databufferten. Funktionen tar emot antalet byte som överfördes till enheten i parametern LengthTransferred.
I följande kodexempel skickas en kontrollbegäran till den angivna USB-enheten för att styra lamporna i ljusfältet.
BOOL SendDatatoDefaultEndpoint(WINUSB_INTERFACE_HANDLE hDeviceHandle)
{
if (hDeviceHandle==INVALID_HANDLE_VALUE)
{
return FALSE;
}
BOOL bResult = TRUE;
UCHAR bars = 0;
WINUSB_SETUP_PACKET SetupPacket;
ZeroMemory(&SetupPacket, sizeof(WINUSB_SETUP_PACKET));
ULONG cbSent = 0;
//Set bits to light alternate bars
for (short i = 0; i < 7; i+= 2)
{
bars += 1 << i;
}
//Create the setup packet
SetupPacket.RequestType = 0;
SetupPacket.Request = 0xD8;
SetupPacket.Value = 0;
SetupPacket.Index = 0;
SetupPacket.Length = sizeof(UCHAR);
bResult = WinUsb_ControlTransfer(hDeviceHandle, SetupPacket, &bars, sizeof(UCHAR), &cbSent, 0);
if(!bResult)
{
goto done;
}
printf("Data sent: %d \nActual data transferred: %d.\n", sizeof(bars), cbSent);
done:
return bResult;
}
Steg 4: Utfärda I/O-begäranden
Skicka sedan data till enhetens bulk-in och bulk-ut ändpunkter som kan användas för läs- och skrivbegäranden, respektive. På OSR USB FX2-enheten konfigureras dessa två slutpunkter för loopback, så enheten flyttar data från bulk-in-slutpunkten till bulk-out-slutpunkten. Det ändrar inte datavärdet eller lägger till nya data. För loopback-konfiguration läser en läsbegäran de data som skickats av den senaste skrivbegäran. WinUSB tillhandahåller följande funktioner för att skicka skriv- och läsbegäranden:
Skicka en skrivbegäran
- Allokera en buffert och fyll den med de data som du vill skriva till enheten. Det finns ingen begränsning för buffertstorleken om programmet inte anger RAW_IO som rörets principtyp. WinUSB delar upp bufferten i segment av lämplig storlek om det behövs. Om RAW_IO anges begränsas buffertens storlek av den maximala överföringsstorlek som stöds av WinUSB.
- Anropa WinUsb_WritePipe för att skriva bufferten till enheten. Tillhandahåll WinUSB-gränssnittshandtaget för enheten, identifieraren för bulk-out-pipen (enligt beskrivningen i avsnittet Fråga enheten om USB-beskrivningar i den här artikeln) och bufferten. Funktionen returnerar antalet byte som skrivs till enheten i parametern bytesWritten. Parametern Overlapped är inställd på NULL- för att begära en synkron åtgärd. Om du vill utföra en asynkron skrivbegäran anger du Överlappade till en pekare till en OVERLAPPED- struktur.
Skrivbegäranden som innehåller data med noll längd vidarebefordras nedåt i USB-stacken. Om överföringslängden är större än en maximal överföringslängd delar WinUSB upp begäran i mindre begäranden med maximal överföringslängd och skickar dem seriellt. Följande exempel på kod allokerar en sträng och skickar den till enhetens bulk-out-endpunkt.
BOOL WriteToBulkEndpoint(WINUSB_INTERFACE_HANDLE hDeviceHandle, UCHAR* pID, ULONG* pcbWritten)
{
if (hDeviceHandle==INVALID_HANDLE_VALUE || !pID || !pcbWritten)
{
return FALSE;
}
BOOL bResult = TRUE;
UCHAR szBuffer[] = "Hello World";
ULONG cbSize = strlen(szBuffer);
ULONG cbSent = 0;
bResult = WinUsb_WritePipe(hDeviceHandle, *pID, szBuffer, cbSize, &cbSent, 0);
if(!bResult)
{
goto done;
}
printf("Wrote to pipe %d: %s \nActual data transferred: %d.\n", *pID, szBuffer, cbSent);
*pcbWritten = cbSent;
done:
return bResult;
}
Skicka en läsbegäran
- Anropa WinUsb_ReadPipe för att läsa data från enhetens bulk-in-slutpunkt. Skicka WinUSB-gränssnittshandtaget för enheten, pipe-identifieraren för bulk-in-slutpunkten och en tom buffert av lämplig storlek. När funktionen returnerar innehåller bufferten de data som lästes från enheten. Antalet byte som lästes returneras i funktionens byteLäs parameter. För läsbegäranden måste bufferten vara en multipel av den maximala paketstorleken.
Läsbegäranden med noll längd slutförs omedelbart med framgång och skickas inte ned i stacken. Om överföringslängden är större än en maximal överföringslängd delar WinUSB upp begäran i mindre begäranden med maximal överföringslängd och skickar dem seriellt. Om överföringslängden inte är en multipel av slutpunktens MaxPacketSizeökar WinUSB storleken på överföringen till nästa multipel av MaxPacketSize. Om en enhet returnerar mer data än vad som begärdes sparar WinUSB överskottsdata. Om data förblir från en tidigare läsbegäran kopierar WinUSB den till början av nästa läsbegäran och slutför begäran om det behövs. I följande kodexempel läser man data från enhetens bulk-in-slutpunkt.
BOOL ReadFromBulkEndpoint(WINUSB_INTERFACE_HANDLE hDeviceHandle, UCHAR* pID, ULONG cbSize)
{
if (hDeviceHandle==INVALID_HANDLE_VALUE)
{
return FALSE;
}
BOOL bResult = TRUE;
UCHAR* szBuffer = (UCHAR*)LocalAlloc(LPTR, sizeof(UCHAR)*cbSize);
ULONG cbRead = 0;
bResult = WinUsb_ReadPipe(hDeviceHandle, *pID, szBuffer, cbSize, &cbRead, 0);
if(!bResult)
{
goto done;
}
printf("Read from pipe %d: %s \nActual data read: %d.\n", *pID, szBuffer, cbRead);
done:
LocalFree(szBuffer);
return bResult;
}
Steg 5: Släpp handtagen på enheten
När du har slutfört alla nödvändiga anrop till enheten släpper du filhandtaget och WinUSB-gränssnittshandtaget för enheten genom att anropa följande funktioner:
- CloseHandle för att släppa handtaget som skapades av CreateFile, enligt beskrivningen i steg 1.
- WinUsb_Free för att frigöra handtaget för WinUSB-gränssnittet för enheten, som returneras av **WinUsb_Initialize.
Steg 6: Implementera huvudfunktionen
I följande kodexempel visas huvudfunktionen i konsolprogrammet.
Exempelkod som hämtar enhetshandtaget och öppnar enheten (GetDeviceHandle och GetWinUSBHandle i det här exemplet) finns i Mallkodsdiskussion.
int _tmain(int argc, _TCHAR* argv[])
{
GUID guidDeviceInterface = OSR_DEVICE_INTERFACE; //in the INF file
BOOL bResult = TRUE;
PIPE_ID PipeID;
HANDLE hDeviceHandle = INVALID_HANDLE_VALUE;
WINUSB_INTERFACE_HANDLE hWinUSBHandle = INVALID_HANDLE_VALUE;
UCHAR DeviceSpeed;
ULONG cbSize = 0;
bResult = GetDeviceHandle(guidDeviceInterface, &hDeviceHandle);
if(!bResult)
{
goto done;
}
bResult = GetWinUSBHandle(hDeviceHandle, &hWinUSBHandle);
if(!bResult)
{
goto done;
}
bResult = GetUSBDeviceSpeed(hWinUSBHandle, &DeviceSpeed);
if(!bResult)
{
goto done;
}
bResult = QueryDeviceEndpoints(hWinUSBHandle, &PipeID);
if(!bResult)
{
goto done;
}
bResult = SendDatatoDefaultEndpoint(hWinUSBHandle);
if(!bResult)
{
goto done;
}
bResult = WriteToBulkEndpoint(hWinUSBHandle, &PipeID.PipeOutId, &cbSize);
if(!bResult)
{
goto done;
}
bResult = ReadFromBulkEndpoint(hWinUSBHandle, &PipeID.PipeInId, cbSize);
if(!bResult)
{
goto done;
}
system("PAUSE");
done:
CloseHandle(hDeviceHandle);
WinUsb_Free(hWinUSBHandle);
return 0;
}
Nästa steg
Om enheten stöder isochroniska slutpunkter kan du använda WinUSB-funktioner för att skicka överföringar. Den här funktionen stöds endast i Windows 8.1. För mer information, se Skicka USB-isochrona överföringar från en WinUSB-skrivbordsapp.