Delen via


Toegang krijgen tot een USB-apparaat met behulp van WinUSB-functies

Dit artikel bevat een gedetailleerd overzicht van het gebruik van WinUSB-functies om te communiceren met een USB-apparaat dat Winusb.sys gebruikt als functiestuurprogramma.

Samenvatting

  • Het apparaat openen en WinUSB-ingang verkrijgen.
  • Informatie ophalen over de apparaat-, configuratie- en interface-instellingen van alle interfaces en hun eindpunten.
  • Gegevens lezen en schrijven naar bulk- en interrupt-eindpunten.

Belangrijke API's

Als u Microsoft Visual Studio 2013 gebruikt, maakt u uw skelet-app met behulp van de WinUSB-sjabloon. In dat geval slaat u stap 1 tot en met 3 over en gaat u verder met stap 4 in dit artikel. Met de sjabloon wordt een file handle naar het apparaat geopend en wordt de benodigde WinUSB-handle verkregen voor volgende bewerkingen. Deze handle wordt opgeslagen in de door de app gedefinieerde DEVICE_DATA-structuur in device.h.

Zie Een Windows-bureaublad-app schrijven op basis van de WinUSB-sjabloon voor meer informatie over de sjabloon.

Notitie

WinUSB-functies vereisen Windows XP of hoger. U kunt deze functies in uw C/C++-toepassing gebruiken om te communiceren met uw USB-apparaat. Microsoft biedt geen beheerde API voor WinUSB.

Voordat u begint

De volgende items zijn van toepassing op deze handleiding:

  • Deze informatie is van toepassing op Windows 8.1, Windows 8, Windows 7, Windows Server 2008, Windows Vista-versies van Windows.
  • Winusb.sys wordt geïnstalleerd als het functiestuurprogramma van het apparaat. Zie WinUSB (Winusb.sys) Installationvoor meer informatie over dit proces.
  • De voorbeelden in dit artikel zijn gebaseerd op de OSR USB FX2 Learning Kit-apparaat. U kunt deze voorbeelden gebruiken om de procedures uit te breiden naar andere USB-apparaten.

Stap 1: Een skeleton-app maken op basis van de WinUSB-sjabloon

Als u toegang wilt krijgen tot een USB-apparaat, maakt u eerst een skeleton-app op basis van de WinUSB-sjabloon die is opgenomen in de geïntegreerde omgeving van Windows Driver Kit (WDK) (met hulpprogramma's voor foutopsporing voor Windows) en Microsoft Visual Studio. U kunt de sjabloon als uitgangspunt gebruiken.

Zie Een Windows-bureaublad-app schrijven op basis van de WinUSB-sjabloonvoor informatie over de sjablooncode, het maken, bouwen, implementeren en opsporen van fouten in de skelet-app.

De sjabloon somt apparaten op met de SetupAPI- routines, opent een bestandsgreep voor het apparaat en maakt een WinUSB-interfacehandle die vereist is voor volgende taken. Zie Sjablooncodediscussievoor voorbeeldcode die de apparaat handle verkrijgt en het apparaat opent.

Stap 2: Een query uitvoeren op het apparaat voor USB-descriptors

Voer vervolgens een query uit op het apparaat voor USB-specifieke informatie, zoals apparaatsnelheid, interfacedescriptors, gerelateerde eindpunten en hun pijpen. De procedure is vergelijkbaar met de procedure die door USB-apparaatstuurprogramma's wordt gebruikt. De toepassing voltooit echter apparaatquery's door WinUsb_GetDescriptoraan te roepen.

De volgende lijst bevat de WinUSB-functies die u kunt aanroepen om USB-specifieke informatie op te halen:

  • Meer informatie over het apparaat.

    Roep WinUsb_QueryDeviceInformation aan om informatie op te vragen van de apparaatdescriptors voor het apparaat. Als u de snelheid van het apparaat wilt ophalen, stelt u DEVICE_SPEED (0x01) in de parameter InformationType in. De functie retourneert LowSpeed (0x01) of HighSpeed (0x03).

  • Interface-descriptoren

    Roep WinUsb_QueryInterfaceSettings aan en geef de interfacegrepen van het apparaat door om de bijbehorende interfacedescriptors te verkrijgen. De WinUSB-interfacegreep komt overeen met de eerste interface. Sommige USB-apparaten, zoals het OSR Fx2-apparaat, ondersteunen slechts één interface zonder alternatieve instelling. Daarom is voor deze apparaten de parameter AlternateSettingNumber ingesteld op nul en wordt de functie slechts één keer aangeroepen. WinUsb_QueryInterfaceSettings vult de door de aanroeper toegewezen USB_INTERFACE_DESCRIPTOR structuur (doorgegeven in de parameter UsbAltInterfaceDescriptor) met informatie over de interface. Het aantal eindpunten in de interface wordt bijvoorbeeld ingesteld in de bNumEndpoints lid van USB_INTERFACE_DESCRIPTOR.

    Voor apparaten die meerdere interfaces ondersteunen, roept u WinUsb_GetAssociatedInterface aan om interface-ingangen voor gekoppelde interfaces te verkrijgen door de alternatieve instellingen op te geven in de parameter AssociatedInterfaceIndex.

  • Eindpunten

    Roep WinUsb_QueryPipe aan om informatie over elk eindpunt op elke interface te verkrijgen. WinUsb_QueryPipe vult de aan de aanroeper toegewezen structuur genaamd WINUSB_PIPE_INFORMATION met informatie over de pijp van het opgegeven eindpunt. Een op nul gebaseerde index identificeert de kanalen van de eindpunten en moet kleiner zijn dan de waarde in het bNumEndpoints element van de interfacedescriptor die in de vorige aanroep naar **WinUsb_QueryInterfaceSettingsis opgehaald. Het OSR Fx2-apparaat heeft één interface met drie eindpunten. Voor dit apparaat is de parameter AlternateInterfaceNumber ingesteld op 0 en de waarde van de parameter PipeIndex varieert van 0 tot 2.

    Als u het pijptype wilt bepalen, bekijkt u het PipeInfo lid van de WINUSB_PIPE_INFORMATION structuur. Dit lid is ingesteld op een van de USBD_PIPE_TYPE opsommingswaarden: UsbdPipeTypeControl, UsbdPipeTypeIsochronous, UsbdPipeTypeBulk of UsbdPipeTypeInterrupt. Het OSR USB FX2-apparaat ondersteunt een interruptpijp, een bulk-in pijp en een bulk-out pijp, dus PipeInfo is ingesteld op UsbdPipeTypeInterrupt of UsbdPipeTypeBulk. De waarde UsbdPipeTypeBulk identificeert bulkpijpen, maar biedt de richting van de pijp niet. De richtinginformatie wordt gecodeerd in het hoogste bit van het pipe-adres, dat is opgeslagen in het WINUSB_PIPE_INFORMATION structuurelement PipeId. De eenvoudigste manier om de richting van de pijp te bepalen, is door de PipeId waarde door te geven aan een van de volgende macro's van Usb100.h:

    • De macro USB_ENDPOINT_DIRECTION_IN (PipeId) retourneert WAAR als de richting naar binnen is.
    • De USB_ENDPOINT_DIRECTION_OUT(PipeId) macro retourneert TRUE als de richting uit is.

    De toepassing gebruikt de waarde PipeId om te bepalen welke pijp moet worden gebruikt voor gegevensoverdracht in aanroepen naar WinUSB-functies, zoals WinUsb_ReadPipe (beschreven in de sectie 'Probleem I/O-aanvragen' van dit onderwerp), zodat in het voorbeeld alle drie PipeId waarden worden opgeslagen voor later gebruik.

De volgende voorbeeldcode haalt de snelheid op van het apparaat dat wordt gespecificeerd door de WinUSB-interfacehandle.

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;
}

De volgende voorbeeldcode voert een query uit op de verschillende descriptors voor het USB-apparaat dat is opgegeven door de WinUSB-interfacehandgreep. Met de voorbeeldfunctie worden de typen ondersteunde eindpunten en de bijbehorende pipe-id's opgehaald. In het voorbeeld worden alle drie de PipeId-waarden opgeslagen voor later gebruik.

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;
}

Stap 3: Besturingsoverdracht verzenden naar het standaardeindpunt

Communiceer vervolgens met het apparaat door een controleaanvraag uit te geven aan het standaardeindpunt.

Alle USB-apparaten hebben een standaardeindpunt naast de eindpunten die zijn gekoppeld aan interfaces. Het primaire doel van het standaardeindpunt is om de host te voorzien van informatie die het kan gebruiken om het apparaat te configureren. Apparaten kunnen echter ook het standaardeindpunt gebruiken voor apparaatspecifieke doeleinden. Het OSR USB FX2-apparaat gebruikt bijvoorbeeld het standaardeindpunt om de lichtbalk en het zevensegment digitale beeldscherm te beheren.

Besturingsopdrachten bestaan uit een installatiepakket van 8 bytes, dat een aanvraagcode bevat waarmee de specifieke aanvraag wordt opgegeven en een optionele gegevensbuffer. De aanvraagcodes en bufferindelingen zijn door de leverancier gedefinieerd. In dit voorbeeld verzendt de toepassing gegevens naar het apparaat om de lichte balk te beheren. De code voor het instellen van de lichtbalk is 0xD8, die voor het gemak wordt gedefinieerd als SET_BARGRAPH_DISPLAY. Voor deze aanvraag vereist het apparaat een gegevensbuffer van 1 byte die aangeeft welke elementen moeten worden verlicht door de juiste bits in te stellen.

De toepassing kan een set van acht selectievakjebesturingselementen bieden om op te geven welke elementen van de lichtbalk moeten worden verlicht. De opgegeven elementen komen overeen met de juiste bits in de buffer. Om UI-code te voorkomen, stelt de voorbeeldcode in deze sectie de bits in zodat alternatieve lichten worden verlicht.

Een controleaanvraag uitgeven

  1. Wijs een gegevensbuffer van 1 byte toe en laad de gegevens in de buffer die de elementen aangeeft die moeten worden verlicht door de juiste bits in te stellen.

  2. Maak een installatiepakket in een structuur toegewezen door de aanroeper WINUSB_SETUP_PACKET. Initialiseer de leden om het aanvraagtype en de gegevens als volgt te vertegenwoordigen:

    • De RequestType lid geeft de aanvraagrichting aan. RequestType is ingesteld op 0, wat aangeeft dat de gegevensoverdracht van de host naar het apparaat wordt overgedragen. Stel RequestType in op 1 voor apparaat-naar-hostoverdrachten.
    • Het aanvraaglid is ingesteld op de door de leverancier gedefinieerde code voor deze aanvraag, 0xD8. Aanvraag is gedefinieerd als SET_BARGRAPH_DISPLAY voor het gemak.
    • Het Lengte lid wordt ingesteld op de grootte van de gegevensbuffer.
    • De Index en waarde elementen zijn niet vereist voor deze aanvraag, dus worden ze op nul gezet.
  3. Roep WinUsb_ControlTransfer aan om de aanvraag naar het standaardeindpunt te verzenden door de WinUSB-interfacegreep van het apparaat, het installatiepakket en de gegevensbuffer door te geven. De functie ontvangt het aantal bytes dat is overgebracht naar het apparaat in de parameter LengthTransferred.

In het volgende codevoorbeeld wordt een controleaanvraag verzonden naar het opgegeven USB-apparaat om de lichten op de lichtbalk te bedienen.

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;
}

Stap 4: I/O-aanvragen uitgeven

Verzend vervolgens gegevens naar de bulk-in- en bulk-out-eindpunten van het apparaat die kunnen worden gebruikt voor respectievelijk lees- en schrijfaanvragen. Op het OSR USB FX2-apparaat zijn deze twee eindpunten geconfigureerd voor loopback, zodat het apparaat gegevens verplaatst van het bulk-in-eindpunt naar het bulk-out-eindpunt. De waarde van de gegevens wordt niet gewijzigd of er worden nieuwe gegevens toegevoegd. Voor de loopback-configuratie leest een leesaanvraag de gegevens die zijn verzonden door de meest recente schrijfaanvraag. WinUSB biedt de volgende functies voor het verzenden van schrijf- en leesaanvragen:

Een schrijfaanvraag verzenden

  1. Wijs een buffer toe en vul deze in met de gegevens die u naar het apparaat wilt schrijven. Er is geen beperking voor de buffergrootte als de toepassing geen RAW_IO instelt als het beleidstype van de pijp. WinUSB verdeelt de buffer in segmenten van de juiste grootte, indien nodig. Als RAW_IO is ingesteld, wordt de grootte van de buffer beperkt door de maximale overdrachtsgrootte die wordt ondersteund door WinUSB.
  2. Roep WinUsb_WritePipe aan om de buffer naar het apparaat te schrijven. Geef de WinUSB-hendel voor het apparaat door, de pijp-id voor de bulk-out pijp (zoals beschreven in de sectie Voer een query uit op het apparaat voor USB-descriptors van dit artikel), en de buffer. De functie retourneert het aantal bytes dat naar het apparaat is geschreven in de parameter bytesWritten. De parameter Overlappende is ingesteld op NULL- om een synchrone bewerking aan te vragen. Als u een asynchrone schrijfaanvraag wilt uitvoeren, stelt u OVERLAPPED in op een aanwijzer naar een OVERLAPPED structuur.

Schrijfaanvragen die gegevens met lengte nul bevatten, worden doorgestuurd naar de USB-stack. Als de overdrachtslengte groter is dan een maximale overdrachtlengte, verdeelt WinUSB de aanvraag in kleinere aanvragen van maximale overdrachtlengte en verzendt deze serieel. In het volgende codevoorbeeld wordt een tekenreeks toegewezen en verzonden naar het bulk-out-eindpunt van het apparaat.

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;
}

Een leesaanvraag verzenden

  • Roep WinUsb_ReadPipe aan om gegevens te lezen van het bulk-in-eindpunt van het apparaat. Geef de WinUSB-interfacegreep van het apparaat, de pipe-id voor het bulk-in-eindpunt en een lege buffer met de juiste grootte door. Wanneer de functie wordt geretourneerd, bevat de buffer de gegevens die van het apparaat zijn gelezen. Het aantal bytes dat is gelezen, wordt geretourneerd in de parameter bytesRead van de functie. Voor leesaanvragen moet de buffer een veelvoud van de maximale pakketgrootte zijn.

Leesaanvragen met lengte nul worden onmiddellijk voltooid met succes en worden niet naar beneden verzonden. Als de overdrachtslengte groter is dan een maximale overdrachtlengte, verdeelt WinUSB de aanvraag in kleinere aanvragen van maximale overdrachtlengte en verzendt deze serieel. Als de overdrachtslengte geen veelvoud is van de MaxPacketSizevan het eindpunt, verhoogt WinUSB de grootte van de overdracht naar het volgende veelvoud van MaxPacketSize. Als een apparaat meer gegevens retourneert dan is aangevraagd, slaat WinUSB de overtollige gegevens op. Als gegevens uit een eerdere leesaanvraag blijven, kopieert WinUSB deze naar het begin van de volgende leesaanvraag en voltooit u de aanvraag, indien nodig. In het volgende codevoorbeeld leest men gegevens van het bulk-in-eindpunt van het apparaat.

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;
}

Stap 5: De apparaatingangen vrijgeven

Nadat u alle vereiste aanroepen naar het apparaat hebt voltooid, laat u de bestandsgreep en de WinUSB-interfacegreep voor het apparaat los door de volgende functies aan te roepen:

  • CloseHandle- om de handle vrij te geven die is gemaakt door CreateFile, zoals beschreven in stap 1.
  • WinUsb_Free om de WinUSB-interfacegreep voor het apparaat vrij te geven, dat wordt geretourneerd door **WinUsb_Initialize.

Stap 6: De hoofdfunctie implementeren

In het volgende codevoorbeeld ziet u de hoofdfunctie van uw consoletoepassing.

Bijvoorbeeld code die de handle van het apparaat verkrijgt en het apparaat opent (GetDeviceHandle- en GetWinUSBHandle- in dit voorbeeld), zie Sjablooncodediscussie.

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;
}

Volgende stappen

Als uw apparaat isochronische eindpunten ondersteunt, kunt u WinUSB-functies gebruiken om overdrachten te verzenden. Deze functie wordt alleen ondersteund in Windows 8.1. Zie USB-isochronische overdrachten verzenden vanuit een WinUSB-desktop-appvoor meer informatie.

Zie ook