Acessar um dispositivo USB usando funções WinUSB
Este artigo inclui um passo a passo detalhado de como usar as funções do WinUSB para se comunicar com um dispositivo USB que está usando o Winusb.sys como driver de função.
Resumo
- Abrindo o dispositivo e obtendo o identificador WinUSB.
- Obtendo informações sobre o dispositivo, a configuração e as configurações de interface de todas as interfaces e seus pontos de extremidade.
- Leitura e gravação de dados em pontos de extremidade em massa e interrupções.
APIs importantes
Se você estiver usando o Microsoft Visual Studio 2013, crie seu aplicativo esqueleto usando o modelo WinUSB. Nesse caso, ignore as etapas 1 a 3 e prossiga a partir da etapa 4 deste artigo. O modelo abre um identificador de arquivo para o dispositivo e obtém o identificador WinUSB necessário para operações subsequentes. Esse identificador é armazenado na estrutura de DEVICE_DATA definida pelo aplicativo em device.h.
Para obter mais informações sobre o modelo, consulte Escrever um aplicativo da área de trabalho do Windows com base no modelo WinUSB.
Observação
As funções WinUSB requerem Windows XP ou posterior. Você pode usar essas funções em seu aplicativo C/C++ para se comunicar com seu dispositivo USB. A Microsoft não fornece uma API gerenciada para WinUSB.
Antes de começar
Os seguintes itens se aplicam a este passo a passo:
- Essas informações se aplicam às versões do Windows Windows 8.1, Windows 8, Windows 7, Windows Server 2008 e Windows Vista do Windows.
- Você instalou Winusb.sys como driver de função do dispositivo. Para obter mais informações sobre esse processo, consulte Instalação do WinUSB (Winusb.sys).
- Os exemplos neste artigo são baseados no dispositivo OSR USB FX2 Learning Kit. Você pode usar esses exemplos para estender os procedimentos para outros dispositivos USB.
Etapa 1: Criar um aplicativo esqueleto com base no modelo WinUSB
Para acessar um dispositivo USB, comece criando um aplicativo esqueleto com base no modelo WinUSB incluído no ambiente integrado do WDK (Windows Driver Kit) (com Ferramentas de Depuração para Windows) e do Microsoft Visual Studio. Você pode usar o modelo como ponto de partida.
Para obter informações sobre o código do modelo, como criar, compilar, implantar e depurar o aplicativo esqueleto, consulte Escrever um aplicativo da área de trabalho do Windows com base no modelo WinUSB.
O modelo enumera dispositivos usando rotinas SetupAPI , abre um identificador de arquivo para o dispositivo e cria um identificador de interface WinUSB necessário para tarefas subsequentes. Para obter um código de exemplo que obtém o identificador do dispositivo e abre o dispositivo, consulte Discussão de código de modelo.
Etapa 2: consultar o dispositivo em busca de descritores USB
Em seguida, consulte o dispositivo para obter informações específicas do USB, como velocidade do dispositivo, descritores de interface, endpoints relacionados e seus pipes. O procedimento é semelhante ao que os drivers de dispositivo USB usam. No entanto, o aplicativo conclui as consultas de dispositivo chamando WinUsb_GetDescriptor.
A lista a seguir mostra as funções WinUSB que você pode chamar para obter informações específicas do USB:
Mais informações sobre o dispositivo.
Ligue para WinUsb_QueryDeviceInformation para solicitar informações dos descritores do dispositivo. Para obter a velocidade do dispositivo, defina DEVICE_SPEED (0x01) no parâmetro InformationType . A função retorna LowSpeed (0x01) ou HighSpeed (0x03).
Descritores de interface
Chame WinUsb_QueryInterfaceSettings e passe os identificadores de interface do dispositivo para obter os descritores de interface correspondentes. O identificador de interface WinUSB corresponde à primeira interface. Alguns dispositivos USB, como o dispositivo OSR Fx2, suportam apenas uma interface sem nenhuma configuração alternativa. Portanto, para esses dispositivos, o parâmetro AlternateSettingNumber é definido como zero e a função é chamada apenas uma vez. WinUsb_QueryInterfaceSettings preenche a estrutura de USB_INTERFACE_DESCRIPTOR alocada pelo chamador (passada no parâmetro UsbAltInterfaceDescriptor ) com informações sobre a interface. Por exemplo, o número de pontos de extremidade na interface é definido no membro bNumEndpoints de USB_INTERFACE_DESCRIPTOR.
Para dispositivos que dão suporte a várias interfaces, chame WinUsb_GetAssociatedInterface para obter identificadores de interface para interfaces associadas especificando as configurações alternativas no parâmetro AssociatedInterfaceIndex .
Pontos de extremidade
Ligue para WinUsb_QueryPipe para obter informações sobre cada ponto de extremidade em cada interface. WinUsb_QueryPipe preenche a estrutura de WINUSB_PIPE_INFORMATION alocada pelo chamador com informações sobre o pipe do ponto de extremidade especificado. Os pipes dos pontos de extremidade são identificados por um índice baseado em zero e devem ser menores que o valor no membro bNumEndpoints do descritor de interface recuperado na chamada anterior para **WinUsb_QueryInterfaceSettings. O dispositivo OSR Fx2 tem uma interface que tem três pontos de extremidade. Para esse dispositivo, o parâmetro AlternateInterfaceNumber da função é definido como 0 e o valor do parâmetro PipeIndex varia de 0 a 2.
Para determinar o tipo de tubulação, examine o membro PipeInfo da estrutura WINUSB_PIPE_INFORMATION. Esse membro é definido como um dos USBD_PIPE_TYPE valores de enumeração: UsbdPipeTypeControl, UsbdPipeTypeIsochronous, UsbdPipeTypeBulk ou UsbdPipeTypeInterrupt. O dispositivo OSR USB FX2 dá suporte a um pipe de interrupção, um pipe de entrada em massa e um pipe de saída em massa, portanto , PipeInfo é definido como UsbdPipeTypeInterrupt ou UsbdPipeTypeBulk. O valor UsbdPipeTypeBulk identifica pipes em massa, mas não fornece a direção do pipe. As informações de direção são codificadas no bit alto do endereço do pipe, que é armazenado no membro PipeId da estrutura WINUSB_PIPE_INFORMATION. A maneira mais simples de determinar a direção do pipe é passar o valor PipeId para uma das seguintes macros de Usb100.h:
- A
USB_ENDPOINT_DIRECTION_IN (PipeId)
macro retornará TRUE se a direção estiver em. - A
USB_ENDPOINT_DIRECTION_OUT(PipeId)
macro retornará TRUE se a direção estiver fora.
O aplicativo usa o valor PipeId para identificar qual pipe usar para transferência de dados em chamadas para funções WinUSB, como WinUsb_ReadPipe (descrito na seção "Emitir solicitações de E/S" deste tópico), portanto, o exemplo armazena todos os três valores PipeId para uso posterior.
- A
O código de exemplo a seguir obtém a velocidade do dispositivo especificado pelo identificador de 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;
}
O código de exemplo a seguir consulta os vários descritores para o dispositivo USB especificado pelo identificador de interface WinUSB. A função de exemplo recupera os tipos de pontos de extremidade com suporte e seus identificadores de pipe. O exemplo armazena todos os três valores PipeId para uso posterior.
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;
}
Etapa 3: Enviar transferência de controle para o endpoint padrão
Em seguida, comunique-se com o dispositivo emitindo uma solicitação de controle para o ponto de extremidade padrão.
Todos os dispositivos USB têm um ponto de extremidade padrão, além dos pontos de extremidade associados às interfaces. A finalidade principal do ponto de extremidade padrão é fornecer ao host informações que ele pode usar para configurar o dispositivo. No entanto, os dispositivos também podem usar o ponto de extremidade padrão para fins específicos do dispositivo. Por exemplo, o dispositivo OSR USB FX2 usa o ponto final padrão para controlar a barra de luz e o display digital de sete segmentos.
Os comandos de controle consistem em um pacote de configuração de 8 bytes, que inclui um código de solicitação que especifica a solicitação específica e um buffer de dados opcional. Os códigos de solicitação e os formatos de buffer são definidos pelo fornecedor. Neste exemplo, o aplicativo envia dados ao dispositivo para controlar a barra de luz. O código para definir a barra de luz é 0xD8, que é definido por conveniência como SET_BARGRAPH_DISPLAY. Para essa solicitação, o dispositivo requer um buffer de dados de 1 byte que especifica quais elementos devem ser acesos definindo os bits apropriados.
O aplicativo pode fornecer um conjunto de oito controles de caixa de seleção para especificar quais elementos da barra de luz devem ser acesos. Os elementos especificados correspondem aos bits apropriados no buffer. Para evitar o código da interface do usuário, o código de exemplo nesta seção define os bits para que as luzes alternativas fiquem acesas.
Para emitir uma solicitação de controle
Aloque um buffer de dados de 1 byte e carregue os dados no buffer que especifica os elementos que devem ser iluminados definindo os bits apropriados.
Construa um pacote de instalação em uma estrutura de WINUSB_SETUP_PACKET alocada pelo chamador. Inicialize os membros para representar o tipo de solicitação e os dados da seguinte maneira:
- O membro RequestType especifica a direção da solicitação. Ele é definido como 0, o que indica a transferência de dados do host para o dispositivo. Para transferências de dispositivo para host, defina RequestType como 1.
- O membro Request é definido como o código definido pelo fornecedor para essa solicitação, 0xD8. É definido por conveniência como SET_BARGRAPH_DISPLAY.
- O membro Length é definido como o tamanho do buffer de dados.
- Os membros Index e Value não são necessários para essa solicitação, portanto, eles são definidos como zero.
Chame WinUsb_ControlTransfer para transmitir a solicitação para o ponto de extremidade padrão passando o identificador de interface WinUSB do dispositivo, o pacote de instalação e o buffer de dados. A função recebe o número de bytes que foram transferidos para o dispositivo no parâmetro LengthTransferred .
O exemplo de código a seguir envia uma solicitação de controle para o dispositivo USB especificado para controlar as luzes na barra de luz.
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;
}
Etapa 4: Emitir solicitações de E/S
Em seguida, envie dados para os pontos de extremidade bulk-in e bulk-out do dispositivo que podem ser usados para solicitações de leitura e gravação, respectivamente. No dispositivo OSR USB FX2, esses dois pontos de extremidade são configurados para loopback, de modo que o dispositivo move dados do ponto de extremidade de entrada em massa para o ponto de extremidade de saída em massa. Ele não altera o valor dos dados nem adiciona novos dados. Para a configuração de loopback, uma solicitação de leitura lê os dados que foram enviados pela solicitação de gravação mais recente. O WinUSB fornece as seguintes funções para enviar solicitações de gravação e leitura:
Para enviar uma solicitação de gravação
- Aloque um buffer e preencha-o com os dados que você deseja gravar no dispositivo. Não há limitação no tamanho do buffer se o aplicativo não definir RAW_IO como o tipo de política do pipe. O WinUSB divide o buffer em partes de tamanho apropriado, se necessário. Se RAW_IO estiver definido, o tamanho do buffer será limitado pelo tamanho máximo de transferência com suporte do WinUSB.
- Chame WinUsb_WritePipe para gravar o buffer no dispositivo. Passe o identificador de interface WinUSB para o dispositivo, o identificador de pipe para o pipe de saída em massa (conforme descrito na seção Consultar o dispositivo para descritores USB deste artigo) e o buffer. A função retorna o número de bytes gravados no dispositivo no parâmetro bytesWritten . O parâmetro Overlapped é definido como NULL para solicitar uma operação síncrona. Para executar uma solicitação de gravação assíncrona, defina Overlapped como um ponteiro para uma estrutura OVERLAPPED .
As solicitações de gravação que contêm dados de comprimento zero são encaminhadas para baixo na pilha USB. Se o comprimento da transferência for maior que um comprimento máximo de transferência, o WinUSB dividirá a solicitação em solicitações menores de comprimento máximo de transferência e as enviará em série. O exemplo de código a seguir aloca uma cadeia de caracteres e a envia para o ponto de extremidade de saída em massa do 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;
}
Para enviar uma solicitação de leitura
- Chame WinUsb_ReadPipe para ler dados do ponto de extremidade bulk-in do dispositivo. Passe o identificador de interface WinUSB do dispositivo, o identificador de pipe para o ponto de extremidade bulk-in e um buffer vazio de tamanho apropriado. Quando a função retorna, o buffer contém os dados que foram lidos do dispositivo. O número de bytes que foram lidos é retornado no parâmetro bytesRead da função. Para solicitações de leitura, o buffer deve ser um múltiplo do tamanho máximo do pacote.
As solicitações de leitura de comprimento zero são concluídas imediatamente com sucesso e não são enviadas para a pilha. Se o comprimento da transferência for maior que um comprimento máximo de transferência, o WinUSB dividirá a solicitação em solicitações menores de comprimento máximo de transferência e as enviará em série. Se o comprimento da transferência não for um múltiplo do MaxPacketSize do ponto de extremidade, o WinUSB aumentará o tamanho da transferência para o próximo múltiplo de MaxPacketSize. Se um dispositivo retornar mais dados do que o solicitado, o WinUSB salvará os dados em excesso. Se os dados permanecerem de uma solicitação de leitura anterior, o WinUSB os copiará para o início da próxima solicitação de leitura e concluirá a solicitação, se necessário. O exemplo de código a seguir lê dados do ponto de extremidade bulk-in do 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;
}
Etapa 5: Solte as alças do dispositivo
Depois de concluir todas as chamadas necessárias para o dispositivo, libere o identificador de arquivo e o identificador de interface WinUSB para o dispositivo chamando as seguintes funções:
- CloseHandle para liberar o identificador que foi criado por CreateFile, conforme descrito na etapa 1.
- WinUsb_Free liberar o identificador de interface WinUSB para o dispositivo, que é retornado por **WinUsb_Initialize.
Etapa 6: Implementar a função principal
O exemplo de código a seguir mostra a função principal do aplicativo de console.
Para obter um código de exemplo que obtém o identificador do dispositivo e abre o dispositivo (GetDeviceHandle e GetWinUSBHandle neste exemplo), consulte Discussão de código de modelo.
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;
}
Próximas etapas
Se o seu dispositivo der suporte a pontos de extremidade isócronos, você poderá usar as funções WinUSB para enviar transferências. Esse recurso só tem suporte no Windows 8.1. Para obter mais informações, consulte Enviar transferências isócronas USB de um aplicativo da área de trabalho WinUSB.