Partager via


Accéder à un périphérique USB à l’aide de fonctions WinUSB

Cet article inclut une procédure pas à pas détaillée sur l’utilisation des fonctions WinUSB pour communiquer avec un périphérique USB qui utilise Winusb.sys comme pilote de fonction.

Résumé

  • Ouverture de l’appareil et obtention du handle WinUSB.
  • Obtention d’informations sur l’appareil, la configuration et les paramètres d’interface de toutes les interfaces, ainsi que leurs points de terminaison.
  • Lecture et écriture de données dans des points de terminaison de bloc et d’interruption.

API importantes

Si vous utilisez Microsoft Visual Studio 2013, créez votre application squelette à l’aide du modèle WinUSB. Dans ce cas, ignorez les étapes 1 à 3 et passez de l’étape 4 dans cet article. Le modèle ouvre un handle de fichier sur l’appareil et obtient le handle WinUSB requis pour les opérations suivantes. Ce handle est stocké dans la structure DEVICE_DATA définie par l’application dans device.h.

Pour plus d’informations sur le modèle, consultez Écrire une application de bureau Windows basée sur le modèle WinUSB.

Remarque

Les fonctions WinUSB nécessitent Windows XP ou une version ultérieure. Vous pouvez utiliser ces fonctions dans votre application C/C++ pour communiquer avec votre appareil USB. Microsoft ne fournit pas d’API managée pour WinUSB.

Avant de commencer

Les éléments suivants s’appliquent à cette procédure pas à pas :

  • Ces informations s’appliquent aux versions Windows 8.1, Windows 8, Windows 7, Windows Server 2008, Windows Vista de Windows.
  • Vous avez installé Winusb.sys en tant que pilote de fonction de l’appareil. Pour plus d’informations sur ce processus, consultez Installation de WinUSB (Winusb.sys).
  • Les exemples de cet article sont basés sur l’appareil OSR USB FX2 Learning Kit. Vous pouvez utiliser ces exemples pour étendre les procédures à d’autres périphériques USB.

Étape 1 : Créer une application squelette basée sur le modèle WinUSB

Pour accéder à un périphérique USB, commencez par créer une application squelette basée sur le modèle WinUSB inclus dans l’environnement intégré du Kit de pilotes Windows (WDK) (avec les outils de débogage pour Windows) et Microsoft Visual Studio. Vous pouvez utiliser le modèle comme point de départ.

Pour plus d’informations sur le code du modèle, sur la création, la génération, le déploiement et le débogage de l’application squelette, consultez Écrire une application de bureau Windows basée sur le modèle WinUSB.

Le modèle énumère les appareils à l’aide de routines SetupAPI , ouvre un handle de fichier pour l’appareil et crée un handle d’interface WinUSB requis pour les tâches suivantes. Pour obtenir un exemple de code qui obtient le handle d’appareil et ouvre l’appareil, consultez la discussion sur le code de modèle.

Étape 2 : Interroger l’appareil pour les descripteurs USB

Ensuite, interrogez l’appareil pour obtenir des informations spécifiques à l’USB, telles que la vitesse de l’appareil, les descripteurs d’interface, les points de terminaison associés et leurs canaux. La procédure est similaire à celle utilisée par les pilotes de périphérique USB. Toutefois, l’application termine les requêtes d’appareil en appelant WinUsb_GetDescriptor.

La liste suivante montre les fonctions WinUSB que vous pouvez appeler pour obtenir des informations spécifiques à USB :

  • Plus d’informations sur l’appareil.

    Appelez WinUsb_QueryDeviceInformation pour demander des informations auprès des descripteurs d’appareil pour l’appareil. Pour obtenir la vitesse de l’appareil, définissez DEVICE_SPEED (0x01) dans le paramètre InformationType . La fonction retourne LowSpeed (0x01) ou HighSpeed (0x03).

  • Descripteurs d’interface

    Appelez WinUsb_QueryInterfaceSettings et transmettez les handles d’interface de l’appareil pour obtenir les descripteurs d’interface correspondants. Le handle d’interface WinUSB correspond à la première interface. Certains périphériques USB, tels que l’appareil OSR Fx2, ne prennent en charge qu’une seule interface sans aucun autre paramètre. Par conséquent, pour ces appareils, le paramètre AlternateSettingNumber est défini sur zéro et la fonction n’est appelée qu’une seule fois. WinUsb_QueryInterfaceSettings remplit la structure USB_INTERFACE_DESCRIPTOR allouée par l’appelant (transmise dans le paramètre UsbAltInterfaceDescriptor) avec des informations sur l’interface. Par exemple, le nombre de points de terminaison de l’interface est défini dans le membre bNumEndpoints de USB_INTERFACE_DESCRIPTOR.

    Pour les appareils qui prennent en charge plusieurs interfaces, appelez WinUsb_GetAssociatedInterface pour obtenir des handles d’interface pour les interfaces associées en spécifiant les autres paramètres dans le paramètre AssociatedInterfaceIndex .

  • Points de terminaison

    Appelez WinUsb_QueryPipe pour obtenir des informations sur chaque point de terminaison sur chaque interface. WinUsb_QueryPipe remplit la structure WINUSB_PIPE_INFORMATION allouée par l’appelant avec des informations sur le canal du point de terminaison spécifié. Les canaux des points de terminaison sont identifiés par un index de base zéro et doivent être inférieurs à la valeur du membre bNumEndpoints du descripteur d’interface récupéré dans l’appel précédent à **WinUsb_QueryInterfaceSettings. L’appareil OSR Fx2 a une interface qui a trois points de terminaison. Pour cet appareil, le paramètre AlternateInterfaceNumber de la fonction est défini sur 0 et la valeur du paramètre PipeIndex varie de 0 à 2.

    Pour déterminer le type de canal, examinez le membre PipeInfo de la structure WINUSB_PIPE_INFORMATION. Ce membre est défini sur l’une des valeurs d’énumération USBD_PIPE_TYPE : UsbdPipeTypeControl, UsbdPipeTypeIsochronous, UsbdPipeTypeBulk ou UsbdPipeTypeInterrupt. L’appareil OSR USB FX2 prend en charge un canal d’interruption, un canal en bloc et un canal en bloc. PipeInfo est donc défini sur UsbdPipeTypeInterrupt ou UsbdPipeTypeBulk. La valeur UsbdPipeTypeBulk identifie les canaux en bloc, mais ne fournit pas la direction du canal. Les informations de direction sont encodées dans le bit élevé de l’adresse du canal, qui est stockée dans le membre PipeId de la structure WINUSB_PIPE_INFORMATION. La façon la plus simple de déterminer la direction du canal consiste à passer la valeur PipeId à l’une des macros suivantes à partir de Usb100.h :

    • La USB_ENDPOINT_DIRECTION_IN (PipeId) macro retourne TRUE si la direction se trouve.
    • La USB_ENDPOINT_DIRECTION_OUT(PipeId) macro retourne TRUE si la direction est en panne.

    L’application utilise la valeur PipeId pour identifier le canal à utiliser pour le transfert de données dans les appels à des fonctions WinUSB, telles que WinUsb_ReadPipe (décrite dans la section « Émettre des demandes d’E/S » de cette rubrique), de sorte que l’exemple stocke les trois valeurs PipeId pour une utilisation ultérieure.

L’exemple de code suivant obtient la vitesse de l’appareil spécifié par le handle d’interface WinUSB.

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

L’exemple de code suivant interroge les différents descripteurs pour le périphérique USB spécifié par le handle d’interface WinUSB. L’exemple de fonction récupère les types de points de terminaison pris en charge et leurs identificateurs de canal. L’exemple stocke les trois valeurs PipeId pour une utilisation ultérieure.

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

Étape 3 : Envoyer le transfert de contrôle au point de terminaison par défaut

Ensuite, communiquez avec l’appareil en émettant une demande de contrôle au point de terminaison par défaut.

Tous les périphériques USB ont un point de terminaison par défaut en plus des points de terminaison associés aux interfaces. L’objectif principal du point de terminaison par défaut est de fournir à l’hôte des informations qu’il peut utiliser pour configurer l’appareil. Toutefois, les appareils peuvent également utiliser le point de terminaison par défaut à des fins spécifiques à l’appareil. Par exemple, l’appareil USB FX2 OSR utilise le point de terminaison par défaut pour contrôler la barre lumineuse et l’affichage numérique de sept segments.

Les commandes de contrôle se composent d’un paquet d’installation de 8 octets, qui inclut un code de requête qui spécifie la requête particulière et une mémoire tampon de données facultative. Les codes de requête et les formats de mémoire tampon sont définis par le fournisseur. Dans cet exemple, l’application envoie des données à l’appareil pour contrôler la barre lumineuse. Le code permettant de définir la barre lumineuse est 0xD8, qui est défini pour des raisons pratiques comme SET_BARGRAPH_DISPLAY. Pour cette demande, l’appareil nécessite une mémoire tampon de données de 1 octet qui spécifie les éléments devant être allumés en définissant les bits appropriés.

L’application peut fournir un ensemble de huit contrôles de case à cocher pour spécifier les éléments de la barre lumineuse qui doivent être allumés. Les éléments spécifiés correspondent aux bits appropriés dans la mémoire tampon. Pour éviter le code de l’interface utilisateur, l’exemple de code de cette section définit les bits afin que d’autres lumières soient allumées.

Pour émettre une demande de contrôle

  1. Allouez une mémoire tampon de données d’un octet et chargez les données dans la mémoire tampon qui spécifie les éléments qui doivent être allumés en définissant les bits appropriés.

  2. Construisez un paquet d’installation dans une structure WINUSB_SETUP_PACKET allouée par l’appelant. Initialisez les membres pour représenter le type de requête et les données comme suit :

    • Le membre RequestType spécifie le sens de la requête. Il est défini sur 0, ce qui indique le transfert de données hôte à appareil. Pour les transferts appareil-à-hôte, définissez RequestType sur 1.
    • Le membre demande est défini sur le code défini par le fournisseur pour cette requête, 0xD8. Il est défini pour des raisons pratiques comme SET_BARGRAPH_DISPLAY.
    • Le membre Length est défini sur la taille de la mémoire tampon de données.
    • Les membres Index et Valeur ne sont pas requis pour cette requête. Ils sont donc définis sur zéro.
  3. Appelez WinUsb_ControlTransfer pour transmettre la requête au point de terminaison par défaut en passant le handle d’interface WinUSB de l’appareil, le paquet d’installation et la mémoire tampon de données. La fonction reçoit le nombre d’octets qui ont été transférés vers l’appareil dans le paramètre LengthTransferred .

L’exemple de code suivant envoie une demande de contrôle au périphérique USB spécifié pour contrôler les lumières sur la barre de lumière.

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

Étape 4 : Émettre des demandes d’E/S

Ensuite, envoyez des données aux points de terminaison en bloc et en bloc de l’appareil qui peuvent être utilisés pour les demandes de lecture et d’écriture, respectivement. Sur l’appareil OSR USB FX2, ces deux points de terminaison sont configurés pour le bouclage. L’appareil déplace donc les données du point de terminaison en bloc vers le point de terminaison en bloc. Elle ne modifie pas la valeur des données ni n’ajoute de nouvelles données. Pour la configuration de bouclage, une demande de lecture lit les données envoyées par la demande d’écriture la plus récente. WinUSB fournit les fonctions suivantes pour l’envoi de requêtes d’écriture et de lecture :

Pour envoyer une demande d’écriture

  1. Allouez une mémoire tampon et remplissez-la avec les données que vous souhaitez écrire sur l’appareil. La taille de la mémoire tampon n’est pas limitée si l’application ne définit pas RAW_IO comme type de stratégie du canal. WinUSB divise la mémoire tampon en blocs de taille appropriée, si nécessaire. Si RAW_IO est définie, la taille de la mémoire tampon est limitée par la taille maximale de transfert prise en charge par WinUSB.
  2. Appelez WinUsb_WritePipe pour écrire la mémoire tampon sur l’appareil. Transmettez le handle d’interface WinUSB pour l’appareil, l’identificateur de canal pour le canal en bloc (comme décrit dans la section Interroger l’appareil pour les descripteurs USB de cet article) et la mémoire tampon. La fonction retourne le nombre d’octets écrits sur l’appareil dans le paramètre bytesWritten . Le paramètre qui se chevauche est défini sur NULL pour demander une opération synchrone. Pour effectuer une requête d’écriture asynchrone, définissez Chevauchement sur un pointeur vers une structure QUI SE CHEVAUCHE .

Les requêtes d’écriture qui contiennent des données de longueur nulle sont transférées vers le bas de la pile USB. Si la longueur de transfert est supérieure à une longueur de transfert maximale, WinUSB divise la requête en demandes plus petites de longueur de transfert maximale et les envoie en série. L’exemple de code suivant alloue une chaîne et l’envoie au point de terminaison en bloc de l’appareil.

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

Pour envoyer une demande de lecture

  • Appelez WinUsb_ReadPipe pour lire les données à partir du point de terminaison en bloc de l’appareil. Transmettez le handle d’interface WinUSB de l’appareil, l’identificateur de canal pour le point de terminaison en bloc et une mémoire tampon vide de taille appropriée. Lorsque la fonction est retournée, la mémoire tampon contient les données lues à partir de l’appareil. Le nombre d’octets lus est retourné dans le paramètre bytesRead de la fonction. Pour les demandes de lecture, la mémoire tampon doit être un multiple de la taille maximale des paquets.

Les demandes de lecture de longueur zéro se terminent immédiatement avec succès et ne sont pas envoyées dans la pile. Si la longueur de transfert est supérieure à une longueur de transfert maximale, WinUSB divise la requête en demandes plus petites de longueur de transfert maximale et les envoie en série. Si la longueur du transfert n’est pas un multiple de MaxPacketSize du point de terminaison, WinUSB augmente la taille du transfert vers le multiple suivant de MaxPacketSize. Si un appareil retourne plus de données que demandé, WinUSB enregistre les données excédentaires. Si les données restent d’une demande de lecture précédente, WinUSB la copie au début de la demande de lecture suivante et termine la demande, si nécessaire. L’exemple de code suivant lit les données à partir du point de terminaison en bloc de l’appareil.

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

Étape 5 : Libérer les handles de l’appareil

Une fois que vous avez terminé tous les appels requis à l’appareil, relâchez le handle de fichier et le handle d’interface WinUSB pour l’appareil en appelant les fonctions suivantes :

  • CloseHandle pour libérer le handle créé par CreateFile, comme décrit à l’étape 1.
  • WinUsb_Free de libérer le handle d’interface WinUSB pour l’appareil, qui est retourné par **WinUsb_Initialize.

Étape 6 : Implémenter la fonction principale

L’exemple de code suivant montre la fonction principale de votre application console.

Pour obtenir un exemple de code qui obtient le handle d’appareil et ouvre l’appareil (GetDeviceHandle et GetWinUSBHandle dans cet exemple), consultez la discussion sur le code de modèle.

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

Étapes suivantes

Si votre appareil prend en charge les points de terminaison isochrones, vous pouvez utiliser des fonctions WinUSB pour envoyer des transferts. Cette fonctionnalité n’est prise en charge que dans Windows 8.1. Pour plus d’informations, consultez Envoyer des transferts isochrones USB à partir d’une application de bureau WinUSB.

Voir aussi