Accedere a un dispositivo USB usando le funzioni WinUSB
Questo articolo contiene una procedura dettagliata su come utilizzare le funzioni WinUSB di per comunicare con un dispositivo USB che utilizza 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 endpoint bulk e interrupt.
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 dal passaggio 4 in 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.
- Winusb.sys viene installato come driver funzionale del dispositivo. Per altre informazioni su questo processo, vedere Installazione di WinUSB (Winusb.sys).
- Gli esempi in questo articolo sono basati sul dispositivo OSR USB FX2 Learning Kit. È possibile usare questi esempi per estendere le procedure ad altri dispositivi USB.
Passaggio 1: Creare un'app scheletro 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 scheletro, 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 dell'USB, ad esempio velocità del dispositivo, descrittori di interfaccia, endpoint correlati e relative pipe. La procedura è simile a quella usata dai driver di dispositivo USB. Tuttavia, l'applicazione completa le query del 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. Un indice in base zero identifica le pipe degli endpoint e deve essere minore del valore nel bNumEndpoints membro 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.
- La
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 pipe. Nell'esempio vengono archiviati tutti e tre i valori PipeId per un uso successivo.
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
Successivamente, comunicare con il dispositivo inviando la 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 luminosa. Il codice per impostare la barra luminosa è 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 luminosa devono essere accesi. 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
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.
Costruire un pacchetto di installazione in una struttura 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. RequestType è impostato su 0, che indica un trasferimento dati da host a dispositivo. Per i trasferimenti da dispositivo a host, impostare RequestType su 1.
- Il membro Request è impostato sul codice definito dal fornitore per questa richiesta, 0xD8. La richiesta è definita come SET_BARGRAPH_DISPLAY per praticità.
- 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.
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 .
L'esempio di codice seguente invia 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 del 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
- Allocare un buffer e riempirlo con i dati da scrivere nel dispositivo. Non esiste alcuna limitazione per le dimensioni del buffer se l'applicazione non imposta RAW_IO come tipo di criteri della pipe. WinUSB divide il buffer in blocchi di dimensioni appropriate, se necessario. Se RAW_IO è impostato, la dimensione del buffer è limitata dalle dimensioni massime di trasferimento supportate da WinUSB.
- Chiamare WinUsb_WritePipe per scrivere il buffer nel dispositivo. Passare l'handle di interfaccia WinUSB per il dispositivo, l'identificatore della pipe per la pipe bulk-out (come descritto nella sezione Eseguire query sul dispositivo per descrittori USB di questo articolo) e il buffer. La funzione restituisce il numero di byte scritti nel dispositivo nel parametro bytesWritten . Il parametro Overlapped è impostato su NULL per richiedere un'operazione sincrona. Per eseguire una richiesta di scrittura asincrona, impostare Overlapped su un puntatore a una struttura OVERLAPPED .
Le richieste di scrittura che contengono dati di lunghezza zero vengono inoltrate nello stack USB. Se la lunghezza del trasferimento è maggiore di una lunghezza massima di trasferimento, WinUSB divide la richiesta in richieste più piccole di lunghezza massima di trasferimento e le invia in modo seriale. L'esempio di codice seguente alloca una stringa e la invia all'endpoint bulk-out 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 del dispositivo. Passare l'handle di interfaccia WinUSB del dispositivo, l'identificatore della pipe per l'endpoint in blocco e un buffer vuoto di dimensioni appropriate. Al termine della funzione, il buffer contiene i dati letti dal dispositivo. Il numero di byte letti viene restituito nel parametro bytesRead della funzione. Per le richieste di lettura, il buffer deve essere un multiplo delle dimensioni massime del pacchetto.
Le richieste di lettura di lunghezza zero vengono completate immediatamente con esito positivo e non vengono inviate allo stack. Se la lunghezza del trasferimento è maggiore di una lunghezza massima di trasferimento, WinUSB divide la richiesta in richieste più piccole di lunghezza massima di trasferimento e le invia in modo seriale. 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 quelli richiesti, WinUSB salva i dati in eccesso. Se i dati rimangono da una richiesta di lettura precedente, WinUSB lo copia all'inizio della richiesta di lettura successiva 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 di file e l'handle dell'interfaccia WinUSB per il dispositivo mediante le seguenti funzioni:
- CloseHandle per rilasciare l'handle creato da CreateFile, come descritto nel passaggio 1.
- WinUsb_Free rilasciare l'handle dell'interfaccia WinUSB per il dispositivo, restituito da **WinUsb_Initialize.
Passaggio 6: Implementare la funzione principale
L'esempio di codice seguente illustra 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), vedi Discussione sul codice del 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 isocroni, è possibile usare le funzioni WinUSB per inviare trasferimenti. Questa funzionalità è supportata solo in Windows 8.1. Per altre informazioni, vedere Inviare trasferimenti isocroni USB da un'app desktop WinUSB.