Condividi tramite


Accedere a un dispositivo USB usando le funzioni WinUSB

Questo articolo include una procedura dettagliata su come usare le funzioni WinUSB per comunicare con un dispositivo USB che usa Winusb.sys come driver di funzione.

Riepilogo

  • Apertura del dispositivo e recupero dell'handle WinUSB.
  • Ottenere informazioni sulle impostazioni del dispositivo, della configurazione e dell'interfaccia di tutte le interfacce e dei relativi endpoint.
  • Lettura e scrittura di dati in blocco e interruzione degli endpoint.

API importanti

Se usi Microsoft Visual Studio 2013, crea la tua bozza di app usando il modello WinUSB. In tal caso, ignorare i passaggi da 1 a 3 e procedere con il passaggio 4 di questo articolo. Il modello apre un handle di file nel dispositivo e ottiene l'handle WinUSB necessario per le operazioni successive. Tale handle viene archiviato nella struttura di DEVICE_DATA definita dall'app in device.h.

Per altre informazioni sul modello, vedere Scrivere un'app desktop di Windows basata sul modello WinUSB.

Nota

Le funzioni WinUSB richiedono Windows XP o versioni successive. È possibile usare queste funzioni nell'applicazione C/C++ per comunicare con il dispositivo USB. Microsoft non fornisce un'API gestita per WinUSB.

Prima di iniziare

Gli elementi seguenti si applicano a questa procedura dettagliata:

  • Queste informazioni si applicano a Windows 8.1, Windows 8, Windows 7, Windows Server 2008, versioni di Windows Vista di Windows.
  • È stato installato Winusb.sys come driver di funzione del dispositivo. Per altre informazioni su questo processo, vedere Installazione di WinUSB (Winusb.sys).
  • Gli esempi in questo articolo si basano sul dispositivo OSR USB FX2 Learning Kit. È possibile usare questi esempi per estendere le procedure ad altri dispositivi USB.

Passaggio 1: Creare una bozza di app basata sul modello WinUSB

Per accedere a un dispositivo USB, iniziare creando una bozza di app basata sul modello WinUSB incluso nell'ambiente integrato di Windows Driver Kit (WDK) (con Strumenti di debug per Windows) e Microsoft Visual Studio. È possibile usare il modello come punto di partenza.

Per informazioni sul codice del modello, su come creare, compilare, distribuire ed eseguire il debug dell'app skeleton, vedere Scrivere un'app desktop di Windows basata sul modello WinUSB.

Il modello enumera i dispositivi usando routine SetupAPI , apre un handle di file per il dispositivo e crea un handle di interfaccia WinUSB necessario per le attività successive. Per un esempio di codice che ottiene l'handle del dispositivo e apre il dispositivo, vedi Discussione sul codice del modello.

Passaggio 2: Eseguire una query sul dispositivo per i descrittori USB

Eseguire quindi una query sul dispositivo per ottenere informazioni specifiche su USB, ad esempio velocità del dispositivo, descrittori di interfaccia, endpoint correlati e pipe. La procedura è simile a quella usata dai driver di dispositivo USB. Tuttavia, l'applicazione completa le query sul dispositivo chiamando WinUsb_GetDescriptor.

L'elenco seguente mostra le funzioni WinUSB che è possibile chiamare per ottenere informazioni specifiche di USB:

  • Altre informazioni sul dispositivo.

    Chiamare WinUsb_QueryDeviceInformation per richiedere informazioni dai descrittori del dispositivo per il dispositivo. Per ottenere la velocità del dispositivo, impostare DEVICE_SPEED (0x01) nel parametro InformationType . La funzione restituisce LowSpeed (0x01) o HighSpeed (0x03).

  • Descrittori di interfaccia

    Chiamare WinUsb_QueryInterfaceSettings e passare gli handle di interfaccia del dispositivo per ottenere i descrittori di interfaccia corrispondenti. L'handle di interfaccia WinUSB corrisponde alla prima interfaccia. Alcuni dispositivi USB, ad esempio il dispositivo OSR Fx2, supportano una sola interfaccia senza alcuna impostazione alternativa. Pertanto, per questi dispositivi il parametro AlternateSettingNumber è impostato su zero e la funzione viene chiamata una sola volta. WinUsb_QueryInterfaceSettings riempie la struttura USB_INTERFACE_DESCRIPTOR allocata dal chiamante (passata nel parametro UsbAltInterfaceDescriptor ) con informazioni sull'interfaccia. Ad esempio, il numero di endpoint nell'interfaccia viene impostato nel membro bNumEndpoints di USB_INTERFACE_DESCRIPTOR.

    Per i dispositivi che supportano più interfacce, chiamare WinUsb_GetAssociatedInterface per ottenere handle di interfaccia per le interfacce associate specificando le impostazioni alternative nel parametro AssociatedInterfaceIndex .

  • Endpoint

    Chiamare WinUsb_QueryPipe per ottenere informazioni su ogni endpoint in ogni interfaccia. WinUsb_QueryPipe popola la struttura di WINUSB_PIPE_INFORMATION allocata dal chiamante con informazioni sulla pipe dell'endpoint specificato. Le pipe degli endpoint sono identificate da un indice in base zero e devono essere inferiori al valore nel membro bNumEndpoints del descrittore di interfaccia recuperato nella chiamata precedente a **WinUsb_QueryInterfaceSettings. Il dispositivo OSR Fx2 ha un'interfaccia con tre endpoint. Per questo dispositivo, il parametro AlternateInterfaceNumber della funzione è impostato su 0 e il valore del parametro PipeIndex varia da 0 a 2.

    Per determinare il tipo di pipe, esaminare il membro PipeInfo della struttura WINUSB_PIPE_INFORMATION. Questo membro è impostato su uno dei valori di enumerazione USBD_PIPE_TYPE : UsbdPipeTypeControl, UsbdPipeTypeIsochronous, UsbdPipeTypeBulk o UsbdPipeTypeInterrupt. Il dispositivo OSR USB FX2 supporta una pipe di interrupt, una pipe bulk-in e una pipe bulk-out, quindi PipeInfo è impostato su UsbdPipeTypeInterrupt o UsbdPipeTypeBulk. Il valore UsbdPipeTypeBulk identifica le pipe bulk, ma non fornisce la direzione della pipe. Le informazioni sulla direzione vengono codificate nel bit elevato dell'indirizzo della pipe, archiviate nel membro PipeId della struttura WINUSB_PIPE_INFORMATION. Il modo più semplice per determinare la direzione della pipe consiste nel passare il valore PipeId a una delle macro seguenti da Usb100.h:

    • La USB_ENDPOINT_DIRECTION_IN (PipeId) macro restituisce TRUE se la direzione è in.
    • La USB_ENDPOINT_DIRECTION_OUT(PipeId) macro restituisce TRUE se la direzione è in uscita.

    L'applicazione usa il valore PipeId per identificare la pipe da usare per il trasferimento dei dati nelle chiamate alle funzioni WinUSB, ad esempio WinUsb_ReadPipe (descritta nella sezione "Problemi richieste di I/O" di questo argomento), quindi nell'esempio vengono archiviati tutti e tre i valori PipeId per un uso successivo.

Il codice di esempio seguente ottiene la velocità del dispositivo specificato dall'handle di interfaccia 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;
}

Il codice di esempio seguente esegue una query sui vari descrittori per il dispositivo USB specificato dall'handle di interfaccia WinUSB. La funzione di esempio recupera i tipi di endpoint supportati e i relativi identificatori di pipe. Nell'esempio vengono archiviati tutti e tre i valori PipeId per usarli in un secondo momento.

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

Passaggio 3: Inviare il trasferimento del controllo all'endpoint predefinito

Comunicare quindi con il dispositivo inviando una richiesta di controllo all'endpoint predefinito.

Tutti i dispositivi USB hanno un endpoint predefinito oltre agli endpoint associati alle interfacce. Lo scopo principale dell'endpoint predefinito è fornire all'host informazioni che può usare per configurare il dispositivo. Tuttavia, i dispositivi possono anche usare l'endpoint predefinito per scopi specifici del dispositivo. Ad esempio, il dispositivo OSR USB FX2 usa l'endpoint predefinito per controllare la barra della luce e la visualizzazione digitale a sette segmenti.

I comandi di controllo sono costituiti da un pacchetto di installazione a 8 byte, che include un codice di richiesta che specifica la richiesta specifica e un buffer di dati facoltativo. I codici di richiesta e i formati di buffer sono definiti dal fornitore. In questo esempio, l'applicazione invia dati al dispositivo per controllare la barra della luce. Il codice per impostare la barra della luce è 0xD8, definito per praticità come SET_BARGRAPH_DISPLAY. Per questa richiesta, il dispositivo richiede un buffer di dati a 1 byte che specifica quali elementi devono essere illuminati impostando i bit appropriati.

L'applicazione può fornire un set di otto controlli casella di controllo per specificare quali elementi della barra di illuminazione devono essere illuminati. Gli elementi specificati corrispondono ai bit appropriati nel buffer. Per evitare il codice dell'interfaccia utente, il codice di esempio in questa sezione imposta i bit in modo che le luci alternative vengano accese.

Per emettere una richiesta di controllo

  1. Allocare un buffer di dati a 1 byte e caricare i dati nel buffer che specifica gli elementi che devono essere illuminati impostando i bit appropriati.

  2. Costruire un pacchetto di installazione in una struttura di WINUSB_SETUP_PACKET allocata dal chiamante. Inizializzare i membri per rappresentare il tipo di richiesta e i dati come indicato di seguito:

    • Il membro RequestType specifica la direzione della richiesta. È impostato su 0, che indica il trasferimento dei dati da host a dispositivo. Per i trasferimenti da dispositivo a host, impostare RequestType su 1.
    • Il membro Request viene impostato sul codice definito dal fornitore per questa richiesta 0xD8. Viene definito per praticità come SET_BARGRAPH_DISPLAY.
    • Il membro Length è impostato sulle dimensioni del buffer di dati.
    • I membri Index e Value non sono necessari per questa richiesta, quindi sono impostati su zero.
  3. Chiamare WinUsb_ControlTransfer per trasmettere la richiesta all'endpoint predefinito passando l'handle dell'interfaccia WinUSB del dispositivo, il pacchetto di installazione e il buffer dei dati. La funzione riceve il numero di byte trasferiti al dispositivo nel parametro LengthTransferred .

Nell'esempio di codice seguente viene inviata una richiesta di controllo al dispositivo USB specificato per controllare le luci sulla barra della luce.

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

Passaggio 4: Inviare richieste di I/O

Inviare quindi i dati agli endpoint bulk-in e bulk-out del dispositivo che possono essere usati rispettivamente per le richieste di lettura e scrittura. Nel dispositivo OSR USB FX2 questi due endpoint sono configurati per il loopback, quindi il dispositivo sposta i dati dall'endpoint bulk-in all'endpoint bulk-out. Non modifica il valore dei dati o aggiunge nuovi dati. Per la configurazione di loopback, una richiesta di lettura legge i dati inviati dalla richiesta di scrittura più recente. WinUSB offre le funzioni seguenti per l'invio di richieste di scrittura e lettura:

Per inviare una richiesta di scrittura

  1. Allocare un buffer e riempirlo con i dati da scrivere nel dispositivo. Non esiste alcuna limitazione sulle dimensioni del buffer se l'applicazione non imposta RAW_IO come tipo di criterio della pipe. WinUSB divide il buffer in blocchi di dimensioni appropriate, se necessario. Se RAW_IO è impostato, le dimensioni del buffer sono limitate dalle dimensioni massime di trasferimento supportate da WinUSB.
  2. Chiamare WinUsb_WritePipe per scrivere il buffer nel dispositivo. Passare l'handle dell'interfaccia WinUSB per il dispositivo, l'identificatore della pipe in blocco (come descritto nella sezione Query del descrittore USB per i descrittori USB ) e il buffer. La funzione restituisce il numero di byte scritti nel dispositivo nel parametro byteScritto . Il parametro Sovrapposto è impostato su NULL per richiedere un'operazione sincrona. Per eseguire una richiesta di scrittura asincrona, impostare Sovrapposizione su un puntatore a una struttura OVERLAPPED .

Le richieste di scrittura contenenti dati a lunghezza zero vengono inoltrate nello stack USB. Se la lunghezza del trasferimento è maggiore di una lunghezza di trasferimento massima, WinUSB divide la richiesta in richieste più piccole della lunghezza massima del trasferimento e li invia serialmente. Nell'esempio di codice seguente viene allocata una stringa e la invia all'endpoint bulkout del dispositivo.

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

Per inviare una richiesta di lettura

  • Chiamare WinUsb_ReadPipe per leggere i dati dall'endpoint bulk-in del dispositivo. Passare l'handle dell'interfaccia WinUSB del dispositivo, l'identificatore della pipe per l'endpoint bulk-in e un buffer vuoto di dimensioni appropriate. Quando la funzione restituisce, il buffer contiene i dati letti dal dispositivo. Il numero di byte letti viene restituito nel parametro byteRead della funzione. Per le richieste di lettura, il buffer deve essere un multiplo delle dimensioni massime dei pacchetti.

Le richieste di lettura a lunghezza zero vengono completate immediatamente con esito positivo e non vengono inviate allo stack. Se la lunghezza del trasferimento è maggiore di una lunghezza di trasferimento massima, WinUSB divide la richiesta in richieste più piccole della lunghezza massima del trasferimento e li invia serialmente. Se la lunghezza del trasferimento non è un multiplo di MaxPacketSize dell'endpoint, WinUSB aumenta le dimensioni del trasferimento al multiplo successivo di MaxPacketSize. Se un dispositivo restituisce più dati di quanto richiesto, WinUSB salva i dati in eccesso. Se i dati rimangono da una richiesta di lettura precedente, WinUSB lo copia all'inizio della successiva richiesta di lettura e completa la richiesta, se necessario. L'esempio di codice seguente legge i dati dall'endpoint bulk-in del dispositivo.

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

Passaggio 5: Rilasciare gli handle del dispositivo

Dopo aver completato tutte le chiamate necessarie al dispositivo, rilasciare l'handle file e l'handle dell'interfaccia WinUSB per il dispositivo chiamando le funzioni seguenti:

  • CloseHandle per rilasciare l'handle creato da CreateFile, come descritto nel passaggio 1.
  • WinUsb_Free per rilasciare l'handle dell'interfaccia WinUSB per il dispositivo, restituito da **WinUsb_Initialize.

Passaggio 6: Implementare la funzione principale

Nell'esempio di codice seguente viene illustrata la funzione principale dell'applicazione console.

Ad esempio, il codice che ottiene l'handle del dispositivo e apre il dispositivo (GetDeviceHandle e GetWinUSBHandle in questo esempio), vedere Discussione sul codice modello.

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

Passaggi successivi

Se il dispositivo supporta endpoint isochronous, è possibile usare funzioni WinUSB per inviare trasferimenti. Questa funzionalità è supportata solo in Windows 8.1. Per altre informazioni, vedere Inviare trasferimenti USB isochronous da un'app desktop WinUSB.

Vedi anche