Acceso a un dispositivo USB mediante funciones winUSB
En este artículo se incluye un tutorial detallado sobre cómo usar funciones de WinUSB para comunicarse con un dispositivo USB que usa Winusb.sys como controlador de funciones.
Resumen
- Apertura del dispositivo y obtención del identificador WinUSB.
- Obtener información sobre el dispositivo, la configuración y la configuración de interfaz de todas las interfaces y sus puntos de conexión.
- Leer y escribir datos en puntos de conexión masivos e interrumpirlos.
API importantes
Si usa Microsoft Visual Studio 2013, cree la aplicación de esqueleto mediante la plantilla winUSB. En ese caso, omita los pasos del 1 al 3 y continúe con el paso 4 de este artículo. La plantilla abre un identificador de archivo en el dispositivo y obtiene el identificador winUSB necesario para las operaciones posteriores. Ese identificador se almacena en la estructura de DEVICE_DATA definida por la aplicación en device.h.
Para obtener más información sobre la plantilla, consulte Escritura de una aplicación de escritorio de Windows basada en la plantilla de WinUSB.
Nota:
Las funciones winUSB requieren Windows XP o posterior. Puede usar estas funciones en la aplicación de C/C++ para comunicarse con el dispositivo USB. Microsoft no proporciona una API administrada para WinUSB.
Antes de comenzar
Los siguientes elementos se aplican a este tutorial:
- Esta información se aplica a Windows 8.1, Windows 8, Windows 7, Windows Server 2008, versiones de Windows Vista de Windows.
- Ha instalado Winusb.sys como controlador de funciones del dispositivo. Para obtener más información sobre este proceso, consulte Instalación de WinUSB (Winusb.sys).
- Los ejemplos de este artículo se basan en el dispositivo OSR USB FX2 Learning Kit. Puede usar estos ejemplos para ampliar los procedimientos a otros dispositivos USB.
Paso 1: Crear una aplicación de esqueleto basada en la plantilla de WinUSB
Para acceder a un dispositivo USB, empiece por crear una aplicación de esqueleto basada en la plantilla winUSB incluida en el entorno integrado del Kit de controladores de Windows (WDK) (con herramientas de depuración para Windows) y Microsoft Visual Studio. Puede usar la plantilla como punto de partida.
Para obtener información sobre el código de plantilla, cómo crear, compilar, implementar y depurar la aplicación de esqueleto, consulte Escritura de una aplicación de escritorio de Windows basada en la plantilla winUSB.
La plantilla enumera los dispositivos mediante rutinas setupAPI , abre un identificador de archivo para el dispositivo y crea un identificador de interfaz winUSB necesario para las tareas posteriores. Para obtener código de ejemplo que obtiene el identificador del dispositivo y abre el dispositivo, consulte Explicación del código de plantilla.
Paso 2: Consultar el dispositivo para descriptores USB
A continuación, consulte el dispositivo para obtener información específica de USB, como la velocidad del dispositivo, los descriptores de interfaz, los puntos de conexión relacionados y sus canalizaciones. El procedimiento es similar al que usan los controladores de dispositivos USB. Sin embargo, la aplicación completa las consultas del dispositivo llamando a WinUsb_GetDescriptor.
En la lista siguiente se muestran las funciones de WinUSB a las que puede llamar para obtener información específica de USB:
Más información del dispositivo.
Llame a WinUsb_QueryDeviceInformation para solicitar información de los descriptores de dispositivo para el dispositivo. Para obtener la velocidad del dispositivo, establezca DEVICE_SPEED (0x01) en el parámetro InformationType . La función devuelve LowSpeed (0x01) o HighSpeed (0x03).
Descriptores de interfaz
Llame a WinUsb_QueryInterfaceSettings y pase los identificadores de interfaz del dispositivo para obtener los descriptores de interfaz correspondientes. El identificador de interfaz winUSB corresponde a la primera interfaz. Algunos dispositivos USB, como el dispositivo OSR Fx2, solo admiten una interfaz sin ninguna configuración alternativa. Por lo tanto, para estos dispositivos, el parámetro AlternateSettingNumber se establece en cero y la función se llama solo una vez. WinUsb_QueryInterfaceSettings rellena la estructura de USB_INTERFACE_DESCRIPTOR asignada por el autor de la llamada (que se pasa en el parámetro UsbAltInterfaceDescriptor) con información sobre la interfaz. Por ejemplo, el número de puntos de conexión de la interfaz se establece en el miembro bNumEndpoints de USB_INTERFACE_DESCRIPTOR.
Para los dispositivos que admiten varias interfaces, llame a WinUsb_GetAssociatedInterface para obtener identificadores de interfaz para las interfaces asociadas especificando la configuración alternativa en el parámetro AssociatedInterfaceIndex .
Puntos de conexión
Llame a WinUsb_QueryPipe para obtener información sobre cada punto de conexión en cada interfaz. WinUsb_QueryPipe rellena la estructura de WINUSB_PIPE_INFORMATION asignada por el autor de la llamada con información sobre la canalización del punto de conexión especificado. Las canalizaciones de los puntos de conexión se identifican mediante un índice de base cero y deben ser menores que el valor del miembro bNumEndpoints del descriptor de interfaz que se recupera en la llamada anterior a **WinUsb_QueryInterfaceSettings. El dispositivo OSR Fx2 tiene una interfaz que tiene tres puntos de conexión. Para este dispositivo, el parámetro AlternateInterfaceNumber de la función se establece en 0 y el valor del parámetro PipeIndex varía de 0 a 2.
Para determinar el tipo de canalización, examine el miembro PipeInfo de la estructura WINUSB_PIPE_INFORMATION. Este miembro se establece en uno de los valores de enumeración de USBD_PIPE_TYPE : UsbdPipeTypeControl, UsbdPipeTypeIsochronous, UsbdPipeTypeBulk o UsbdPipeTypeInterrupt. El dispositivo USB FX2 de OSR admite una canalización de interrupción, una canalización masiva y una canalización de salida masiva, por lo que PipeInfo se establece en UsbdPipeTypeInterrupt o UsbdPipeTypeBulk. El valor UsbdPipeTypeBulk identifica canalizaciones masivas, pero no proporciona la dirección de la canalización. La información de dirección se codifica en el bit alto de la dirección de canalización, que se almacena en el miembro PipeId de la estructura de WINUSB_PIPE_INFORMATION. La manera más sencilla de determinar la dirección de la canalización es pasar el valor PipeId a una de las siguientes macros de Usb100.h:
- La
USB_ENDPOINT_DIRECTION_IN (PipeId)
macro devuelve TRUE si la dirección está en. - La
USB_ENDPOINT_DIRECTION_OUT(PipeId)
macro devuelve TRUE si la dirección está fuera.
La aplicación usa el valor PipeId para identificar la canalización que se usará para la transferencia de datos en llamadas a funciones winUSB, como WinUsb_ReadPipe (que se describe en la sección "Solicitudes de E/S de problemas" de este tema), por lo que el ejemplo almacena los tres valores pipeId para su uso posterior.
- La
El código de ejemplo siguiente obtiene la velocidad del dispositivo especificado por el identificador de interfaz 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;
}
El código de ejemplo siguiente consulta los distintos descriptores para el dispositivo USB especificado por el identificador de interfaz winUSB. La función de ejemplo recupera los tipos de puntos de conexión admitidos y sus identificadores de canalización. En el ejemplo se almacenan los tres valores de PipeId para su 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;
}
Paso 3: Enviar transferencia de control al punto de conexión predeterminado
A continuación, comunique con el dispositivo mediante la emisión de la solicitud de control al punto de conexión predeterminado.
Todos los dispositivos USB tienen un punto de conexión predeterminado además de los puntos de conexión asociados a las interfaces. El propósito principal del punto de conexión predeterminado es proporcionar al host información que puede usar para configurar el dispositivo. Sin embargo, los dispositivos también pueden usar el punto de conexión predeterminado para fines específicos del dispositivo. Por ejemplo, el dispositivo OSR USB FX2 usa el punto de conexión predeterminado para controlar la barra de luz y la pantalla digital de siete segmentos.
Los comandos de control constan de un paquete de configuración de 8 bytes, que incluye un código de solicitud que especifica la solicitud concreta y un búfer de datos opcional. Los códigos de solicitud y los formatos de búfer son definidos por el proveedor. En este ejemplo, la aplicación envía datos al dispositivo para controlar la barra de luz. El código para establecer la barra de luz es 0xD8, que se define para mayor comodidad como SET_BARGRAPH_DISPLAY. Para esta solicitud, el dispositivo requiere un búfer de datos de 1 byte que especifica qué elementos se deben iluminar estableciendo los bits adecuados.
La aplicación podría proporcionar un conjunto de ocho controles de casilla para especificar qué elementos de la barra de luz se deben iluminar. Los elementos especificados corresponden a los bits adecuados del búfer. Para evitar el código de la interfaz de usuario, el código de ejemplo de esta sección establece los bits para que las luces alternativas se iluminan.
Para emitir una solicitud de control
Asigne un búfer de datos de 1 byte y cargue los datos en el búfer que especifica los elementos que se deben iluminar estableciendo los bits adecuados.
Construya un paquete de instalación en una estructura de WINUSB_SETUP_PACKET asignada por el autor de la llamada. Inicialice los miembros para representar el tipo de solicitud y los datos de la siguiente manera:
- El miembro RequestType especifica la dirección de la solicitud. Se establece en 0, que indica la transferencia de datos de host a dispositivo. Para las transferencias de dispositivo a host, establezca RequestType en 1.
- El miembro Request se establece en el código definido por el proveedor para esta solicitud, 0xD8. Se define para mayor comodidad como SET_BARGRAPH_DISPLAY.
- El miembro Length se establece en el tamaño del búfer de datos.
- Los miembros Index y Value no son necesarios para esta solicitud, por lo que se establecen en cero.
Llame a WinUsb_ControlTransfer para transmitir la solicitud al punto de conexión predeterminado pasando el identificador de interfaz WinUSB del dispositivo, el paquete de instalación y el búfer de datos. La función recibe el número de bytes transferidos al dispositivo en el parámetro LengthTransferred .
En el ejemplo de código siguiente se envía una solicitud de control al dispositivo USB especificado para controlar las luces de la 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;
}
Paso 4: Emitir solicitudes de E/S
A continuación, envíe datos a los puntos de conexión masivos y de salida masiva del dispositivo que se pueden usar para las solicitudes de lectura y escritura, respectivamente. En el dispositivo OSR USB FX2, estos dos puntos de conexión se configuran para bucle invertido, por lo que el dispositivo mueve los datos del punto de conexión masivo al punto de conexión de salida masiva. No cambia el valor de los datos ni agrega ningún dato nuevo. Para la configuración de bucle invertido, una solicitud de lectura lee los datos enviados por la solicitud de escritura más reciente. WinUSB proporciona las siguientes funciones para enviar solicitudes de escritura y lectura:
Para enviar una solicitud de escritura
- Asigne un búfer y llene con los datos que desea escribir en el dispositivo. No hay ninguna limitación en el tamaño del búfer si la aplicación no establece RAW_IO como tipo de directiva de la canalización. WinUSB divide el búfer en fragmentos de tamaño adecuado, si es necesario. Si se establece RAW_IO, el tamaño del búfer está limitado por el tamaño máximo de transferencia admitido por WinUSB.
- Llame a WinUsb_WritePipe para escribir el búfer en el dispositivo. Pase el identificador de interfaz WinUSB para el dispositivo, el identificador de canalización de la canalización masiva (como se describe en la sección Consulta del dispositivo para descriptores USB de este artículo) y el búfer. La función devuelve el número de bytes que se escriben en el dispositivo en el parámetro bytesWritten . El parámetro Superpuesto se establece en NULL para solicitar una operación sincrónica. Para realizar una solicitud de escritura asincrónica, establezca Superpuesto a un puntero a una estructura SUPERPUESTA .
Las solicitudes de escritura que contienen datos de longitud cero se reenvieron a la pila USB. Si la longitud de la transferencia es mayor que una longitud máxima de transferencia, WinUSB divide la solicitud en solicitudes más pequeñas de longitud máxima de transferencia y las envía en serie. En el ejemplo de código siguiente se asigna una cadena y se envía al punto de conexión masivo 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;
}
Para enviar una solicitud de lectura
- Llame a WinUsb_ReadPipe para leer datos del punto de conexión masivo del dispositivo. Pase el identificador de interfaz WinUSB del dispositivo, el identificador de canalización para el punto de conexión masivo y un búfer vacío de tamaño adecuado. Cuando se devuelve la función, el búfer contiene los datos leídos desde el dispositivo. El número de bytes leídos se devuelve en el parámetro bytesRead de la función. Para las solicitudes de lectura, el búfer debe ser un múltiplo del tamaño máximo del paquete.
Las solicitudes de lectura de longitud cero se completan inmediatamente con éxito y no se envían a la pila. Si la longitud de la transferencia es mayor que una longitud máxima de transferencia, WinUSB divide la solicitud en solicitudes más pequeñas de longitud máxima de transferencia y las envía en serie. Si la longitud de la transferencia no es un múltiplo de MaxPacketSize del punto de conexión, WinUSB aumenta el tamaño de la transferencia al siguiente múltiplo de MaxPacketSize. Si un dispositivo devuelve más datos de los solicitados, WinUSB guarda los datos excesivos. Si los datos permanecen de una solicitud de lectura anterior, WinUSB lo copia al principio de la siguiente solicitud de lectura y completa la solicitud, si es necesario. En el ejemplo de código siguiente se leen los datos del punto de conexión masivo 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;
}
Paso 5: Liberar los identificadores del dispositivo
Después de completar todas las llamadas necesarias al dispositivo, libere el identificador de archivo y el identificador de interfaz winUSB para el dispositivo llamando a las funciones siguientes:
- CloseHandle para liberar el identificador creado por CreateFile, como se describe en el paso 1.
- WinUsb_Free liberar el identificador de interfaz winUSB para el dispositivo, que devuelve **WinUsb_Initialize.
Paso 6: Implementar la función principal
En el ejemplo de código siguiente se muestra la función principal de la aplicación de consola.
Para obtener código de ejemplo que obtiene el identificador del dispositivo y abre el dispositivo (GetDeviceHandle y GetWinUSBHandle en este ejemplo), consulte Explicación del código de plantilla.
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;
}
Pasos siguientes
Si el dispositivo admite puntos de conexión isócronos, puede usar funciones winUSB para enviar transferencias. Esta característica solo se admite en Windows 8.1. Para obtener más información, consulta Enviar transferencias isocronosas USB desde una aplicación de escritorio winUSB.
Consulte también
- Introducción a WinUSB para desarrolladores
- Arquitectura y módulos de WinUSB
- Instalación de WinUSB (Winusb.sys)
- Funciones winUSB para la modificación de directivas de canalización
- Administración de energía winUSB
- Funciones de WinUSB
- Escribir una aplicación de escritorio de Windows basada en la plantilla de WinUSB