Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Este artigo contém um passo a passo detalhado de como usar funções WinUSB para se comunicar com um dispositivo USB que está usando Winusb.sys como seu driver de função.
Resumo
- Abrir o dispositivo e obter o identificador WinUSB.
- Obter informações sobre o dispositivo, configuração e 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 de interrupção.
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 da etapa 4 neste 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 o Windows XP ou posterior. Pode utilizar estas funções na sua aplicação C/C++ para comunicar com o seu dispositivo USB. A Microsoft não fornece uma API gerenciada para WinUSB.
Antes de começar
Os seguintes itens se aplicam a esta explicação passo a passo:
- Estas informações aplicam-se ao Windows 8.1, Windows 8, Windows 7, Windows Server 2008, Windows Vista versões do Windows.
- Winusb.sys é instalado como driver de função do dispositivo. Para obter mais informações sobre este processo, consulte Instalação do WinUSB (Winusb.sys).
- Os exemplos neste artigo baseiam-se 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 baseado no modelo WinUSB incluído no ambiente integrado do Windows Driver Kit (WDK) (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 e sobre como criar, compilar, implantar e depurar o aplicativo esqueleto, consulte Escrever um aplicativo de ambiente de trabalho do Windows com base no modelo WinUSB.
O template enumera dispositivos usando rotinas de SetupAPI , abre um manipulador de ficheiro para o dispositivo e cria um manipulador de interface WinUSB necessário para as tarefas subsequentes. Por exemplo, o código que obtém o identificador do dispositivo e abre o dispositivo, consulte Discussão de código de modelo.
Passo 2: Consultar o dispositivo para descritores USB
Em seguida, consulte o dispositivo para obter informações específicas do USB, como velocidade do dispositivo, descritores de interface, pontos de extremidade relacionados e seus tubos. O procedimento é semelhante ao que os drivers de dispositivo USB usam. No entanto, o aplicativo conclui 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.
Chame 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 manipuladores 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 qualquer 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 bNumEndpoints membro de USB_INTERFACE_DESCRIPTOR.
Para dispositivos que suportam várias interfaces, chame WinUsb_GetAssociatedInterface para obter identificadores de interface para interfaces associadas, especificando as configurações alternativas no parâmetro AssociatedInterfaceIndex.
Pontos finais
Ligue para WinUsb_QueryPipe para obter informações sobre cada ponto de extremidade em cada interface. WinUsb_QueryPipe preenche a estrutura WINUSB_PIPE_INFORMATION alocada pelo chamador com informações sobre o pipe do ponto de extremidade especificado. Um índice baseado em zero identifica os canais dos pontos de extremidade e deve ser inferior ao valor no membro bNumEndpoints do descritor de interface, que é recuperado na chamada anterior para **WinUsb_QueryInterfaceSettings. O dispositivo OSR Fx2 tem uma interface que tem três pontos finais. Para este 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 tubo, examine o membro do PipeInfo da estrutura WINUSB_PIPE_INFORMATION. Este membro é definido como um dos USBD_PIPE_TYPE valores de enumeração: UsbdPipeTypeControl, UsbdPipeTypeIsochronous, UsbdPipeTypeBulk ou UsbdPipeTypeInterrupt. O dispositivo OSR USB FX2 suporta um tubo de interrupção, um tubo de entrada em massa e um tubo de saída a granel, portanto, PipeInfo está definido como UsbdPipeTypeInterrupt ou UsbdPipeTypeBulk. O valor UsbdPipeTypeBulk identifica tubos a granel, mas não fornece a direção do tubo. 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 tubo é passar o valor PipeId para uma das seguintes macros de Usb100.h:
- A macro
USB_ENDPOINT_DIRECTION_IN (PipeId)retorna TRUE se a direção estiver dentro. - A macro
USB_ENDPOINT_DIRECTION_OUT(PipeId)retorna TRUE se a direção estiver fora.
A aplicação utiliza o valor PipeId para identificar qual canal usar para transferência de dados em chamadas para funções WinUSB, como WinUsb_ReadPipe (descrita na secção "Issue I/O Requests" deste tópico), armazenando assim todos os três valores PipeId para uso posterior.
- A macro
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 suportados e os seus identificadores de canal de comunicação. 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;
}
Passo 3: Enviar transferência de controlo para o ponto de extremidade 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. O objetivo 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 propósitos específicos do dispositivo. Por exemplo, o dispositivo OSR USB FX2 usa o ponto de extremidade padrão para controlar a barra de luz e o display digital de sete segmentos.
Os comandos de controle consistem em um pacote de instalaçã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 para o 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 se acendam.
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 acesos 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. RequestType é definido como 0, indicando a transferência de dados de host para dispositivo. Para transferências de dispositivo para host, defina RequestType como 1.
- O membro Solicitação é definido como o código definido pelo fornecedor para esta solicitação, 0xD8. A solicitação é denominada SET_BARGRAPH_DISPLAY por conveniência.
- O membro Length é configurado com o tamanho do buffer de dados.
- Os membros do Index e Value não são necessários para esta solicitação, portanto, eles são definidos como zero.
Chame WinUsb_ControlTransfer para transmitir a solicitação para o endpoint padrão, fornecendo o handle da interface WinUSB do dispositivo, o pacote de configuração e o buffer de dados. A função recebe, no parâmetro LengthTransferred, o número de bytes que foram transferidos para o dispositivo.
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 de entrada e saída em massa 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 recupera os dados enviados pela solicitação de gravação mais recente. WinUSB fornece as seguintes funções para enviar solicitações de gravação e leitura:
Para enviar um pedido de escrita
- 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. WinUSB divide o buffer em partes de tamanho adequado, se necessário. Se RAW_IO estiver definido, o tamanho do buffer é limitado pelo tamanho máximo de transferência suportado pelo WinUSB.
- Chame WinUsb_WritePipe para gravar o buffer no dispositivo. Passe o manipulador da interface WinUSB para o dispositivo, o identificador do canal 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 escritos 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 escrita 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 a pilha USB. Se o comprimento da transferência for maior do que um comprimento máximo de transferência, o WinUSB divide a solicitação em solicitações menores de comprimento máximo de transferência e as envia 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 um pedido de leitura
- Chame WinUsb_ReadPipe para ler dados do endpoint bulk-in do dispositivo. Passe a alça de interface WinUSB do dispositivo, o identificador de canal de comunicação para o endpoint de entrada de dados em massa 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 baixo da pilha. Se o comprimento da transferência for maior do que um comprimento máximo de transferência, o WinUSB divide a solicitação em solicitações menores de comprimento máximo de transferência e as envia em série. Se o comprimento da transferência não for um múltiplo do MaxPacketSize dodo 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 foi solicitado, WinUSB salva 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 endpoint de 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;
}
Passo 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 liberar o identificador que foi criado por CreateFile, conforme descrito na etapa 1.
- WinUsb_Free para liberar o manipulador de interface WinUSB para o dispositivo, que é retornado por **WinUsb_Initialize.
Passo 6: Implementar a função principal
O exemplo de código a seguir mostra a função principal do seu aplicativo de console.
Por exemplo, para o código que obtém o identificador do dispositivo e abre o dispositivo (GetDeviceHandle e GetWinUSBHandle neste exemplo), veja a discussão sobre 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óximos passos
Se o seu dispositivo suportar terminais isócronos, pode utilizar as funções WinUSB para enviar transferências de dados. Esta funcionalidade só é suportada no Windows 8.1. Para obter mais informações, consulte Enviar transferências isócronas USB de um aplicativo de desktop WinUSB.
Ver também
- Introdução ao WinUSB para desenvolvedores
- A arquitetura e os módulos WinUSB
- Instalação do WinUSB (Winusb.sys)
- funções WinUSB para modificação de política de canalização
- Gerenciamento de energia do WinUSB
- Funções do WinUSB
- Escrever um aplicativo da área de trabalho do Windows com base no modelo WinUSB