Comunicación USB-PIC con HID

Ejemplo de comunicación USB Multiplataforma entre un PIC (18F4550) y un PC utilizando la clase HID y la librería HIDAPI

MICROCONTROLADORES

Biblioman

1/15/20129 min read

Ejemplo de comunicación USB Multiplataforma entre un PIC (18F4550) y un PC utilizando la clase HID y la librería HIDAPI. La aplicación de escritorio está realizada en C++ con las librerías gráficas de Qt y el IDE QtCreator. El firmware utilizado en el PIC es el ejemplo ex_usb_hid.c proporcionado por CCS y que podéis encontrar en la carpeta Examples en la ruta donde hayáis instalado el compilador. La aplicación permite el envío de datos hacía el PIC (control del estado ON-OFF de dos Leds) y la recepción de dos entradas de datos (Señal analógica a través de un potenciómetro y entrada digital proporcionada por un pulsador).

Acerca de la clase HID (Human Interface Device)

Una característica que hace muy interesante el uso de dispositivos que implemente en su firmware la clase HID es que la mayoría de Sistemas Operativos modernos implementan los controladores necesarios para poder comunicarse con ellos sin ser necesaria la instalación de ningún driver adicional por parte del usuario. Ejemplos de dispositivos HID tenemos por ejemplo los teclados y ratones del PC sin embargo, también pueden utilizar esta clase dispositivos que no requieran una interacción humana directa como termómetros, voltímetros, lectores de códigos de barras, etc. Lo que la hace muy interesante a la hora de utilizarla en proyectos que utilizan Microcontroladores.


Un esquema general y personalizado a lo que sería esta demo de una comunicación HID entre una aplicación de escritorio y un Microcontrolador sería el siguiente:

Sobre este diagrama se pueden comentar algunos conceptos:

  • Un Endpoint es un buffer de memoria RAM situado físicamente en el Microcontrolador, desde ese buffer se reciben o se envían los datos desde el dispositivo hacía el Host. Como se ve en la figura el host también tiene Buffers para almacenar temporalmente la información pero solo a los buffers del dispositivo se les denomina Endpoint. Cada Endpoint puede ser de entrada o de salida o bidireccional, la dirección se considera siempre desde el punto de vista del Host, así un Endpoint de salida será un canal que transmita datos desde el host al dispositivo (PIC). Un Endpoint configurado como control es bidireccional, es decir, será capaz de enviar o recibir datos en ambas direcciones. Todos los dispositivos deben de tener un Endpoint de control, numerado con el número cero. El Sistema Operativo instalado en el PC realiza el proceso de enumeración y control a través de él cuando el dispositivo es enchufado al PC.

  • Una interfaz está formada por una colección de Endpoint y una determinada configuración puede estar formada a su vez por varias interfaces.

  • Las pipes o tuberías son canales de comunicación que sirven para transmitir datos entre el PC-Host y un Endpoint en particular.

  • En la clase HID los datos se intercambian a través de unas estructuras de datos llamadas informes (Reports). Este formato es flexible y permite manejar casi cualquier tipo de dato, en los descriptores es donde se especifica el tamaño de los datos de los Reports así como información adicional sobre ellos.

  • Las limitaciones que tienen en cuanto a velocidad de transferencia los dispositivos HID son: para un dispositivo low-speed máximo 800 bytes/s, para full-speed 64KB/s. las únicas transferencias soportadas son las de control e Interrupción.

¿Qué es HIDAPI?

Es una librería Multiplataforma (Windows, Linux y MAC) que facilita la comunicación entre una aplicación y un dispositivo que implementa la clase HID.


Hay varias formas de utilizar HIDAPI dependientes del sistema operativo utilizado:

  • Windows (usando hid.dll)

  • Linux/hidraw (usando el driver hidraw del kernel)

  • Linux/libusb (usando libusb-1.0)

  • Mac (usando IOHidManager)

Para esta demo se utilizará hid.dll para la aplicación de escritorio de Windows y la librería libusb 1-0 para la aplicación de escritorio que correrá en Linux.

Aplicación de escritorio para Windows

Para poder trabajar con la librería HIDAPI en Windows debemos hacerlo a través de una librería dinámica, para compilar la .dll seguiremos los siguientes pasos:

  • Nos Bajamos las fuentes de la librería (signal11-hidapi) desde aquí.

  • Descargarnos e instalamos el Visual Studio C++ 2008 desde la página de Microsoft.

  • Ejecutamos el Visual Studio C++ 2008 y desde el menú del programa seleccionamos File-> Open->Project/Solution… y seleccionamos la solución al proyecto contenida en la carpeta Windows de la librería (hidapi.sln)

  • Seleccionamos la configuración Release.

  • Y compilamos seleccionando en el menú Build-> BuildSolution (F7).

  • La compilación nos generará la librería dinámica hidapi.dll que tendremos que incluir en el directorio donde tengamos el ejecutable de nuestra aplicación de escritorio y el archivo hidapi.lib que será la librería que tendremos que añadir al proyecto de la aplicación desde QtCreator.


La librería hidapi.lib la añadimos seleccionando la carpeta del proyecto y haciendo clic en Add Library… en el menú contextual.

Nota: esta opción solo está disponible en las últimas versiones de QtCreator.

El la ventana que nos aparece seleccionamos External library:

Y al pulsar Next seleccionaremos hidapi.lib, las demás opciones dejamos las que vienen por defecto.
A los archivos del proyecto también tenemos que añadir el archivo de cabecera hidapi.h.
La configuración del proyecto en QtCreator una vez añadidas las librerías necesarias debe de quedar según se muestra en la figura:

La aplicación de escritorio mostrará la siguiente interfaz:

Esquema y prototipo montado de la demo:

Prototipo:

Conectando el dispositivo al PC

Cuando conectemos el cable USB al PC empezará el proceso de enumeración, por medio del cual el PC identifica al dispositivo y si todo es correcto es añadido al Bus de dispositivos USB que el PC tenga configurados en ese momento, como se trata de un dispositivo que en su firmware implementa la clase HID no se necesita instalar ningún driver adicional por parte del usuario, el Sistema Operativo ya incorpora los manejadores necesarios para comunicarse a bajo nivel con él. Si todo ha ido bien veremos el siguiente mensaje:

y si abrimos el Administrador de dispositivos veremos nuestro dispositivo instalado correctamente.

Enviando y recibiendo datos

Como comentábamos anteriormente en los dispositivos que implementan la clase HID la información se transmite y recibe a través de unas estructuras de datos llamadas Informes o Reports. El tamaño y tipos de datos de esa estructura queda definida en el firmware del PIC concretamente en el archivo fuente de la demo (ex_usb_hid.c ) y en el archivo que define los descriptores (usb_desc_hid.h).

Si editamos el archivo ex_usb_hid.c encontraremos las siguientes definiciones:

#define USB_CONFIG_HID_TX_SIZE 2

#define USB_CONFIG_HID_RX_SIZE 2

Que nos define el tamaño de los Reports (no confundir con el tamaño del buffer del Endpoint). En esta demo el PIC envía hacia el PC dos Bytes en el primero envíanos el estado del pulsador (su valor será 0 o 1) y en el segundo el estado del potenciómetro. El PC envía al PIC también dos bytes para manejar el estado de cada uno de los leds.

Si editamos el archivo de los descriptores (usb_desc_hid.h) encontramos lo siguiente:

0x75, 8, // Report size = 8 (bits)

0x95, USB_CONFIG_HID_RX_SIZE, // Report count = 16 bits (2 bytes)

Donde Report Size describe el tamaño del tipo de dato del Item (Elemento) que formara el Report y Report count el nº de Items que contiene el report. En este caso dos Item y como el tamaño de cada Item es de un byte el tamaño del Report será de 2 bytes. Lo mismo para los paquetes transmitidos:

0x75, 8, // Report size = 8 (bits)

0x95, USB_CONFIG_HID_TX_SIZE, // Report count = 16 bits (2 bytes)

¿Y donde se define el tamaño del Endpoint?

Si editamos el archivo de los descriptores (usb_desc_hid.h) vemos que el tamaño del Endpoint de transmisión (igual para el de recepción) queda definido a través de una macro y su valor será el del tamaño de los paquetes de datos definido en el archivo principal de la Demo (ex_usb_hid.c ) por la etiqueta USB_CONFIG_HID_TX_SIZE mas un byte, de esta manera el tamaño del Endpoint siempre será más grande que el tamaño del mensaje o report y se podrá enviar todo el mensaje de una vez, a excepción de que el tamaño del report sea mayor o igual a 64 bytes (tamaño máximo permitido para un Endpoint de Interrupción) en cuyo caso el tamaño del Endpoint se fija a 64 bytes y el mensaje no se podrá enviar de una vez.

#ifndef USB_EP1_TX_SIZE

#if (USB_CONFIG_HID_TX_SIZE >= 64)

// interrupt endpoint max packet size is 64.

#define USB_EP1_TX_SIZE 64

#else

// by making EP packet size larger than message size, we can send message in one

// packet.

#define USB_EP1_TX_SIZE (USB_CONFIG_HID_TX_SIZE+1)

#endif

#endif

Nota: esto es así para la versión del compilador CCS 4.107 que es la que estoy utilizando, puede que en versiones anteriores el tamaño del Endpoint se asigne directamente.

Estos descriptores no son los únicos que necesitan los reports para quedar bien definidos hay otros como: Usage, Usage Page, Usage minimum, Usage máximum, Logical minimum, Logical máximum, etc. Todos ellos están descritos en el libro USB Completo de Jan Axelson.

Existe una herramienta gratuita proporcionada por el USB Implementers Forum llamada HID Descriptor Tool y que se puede descargar desde esta página.

La herramienta facilita la creación y chequeo de nuestros propios descriptores para la clase HID, según se muestra en la figura de abajo:

A través de un Sniffer USB se pueden ver los datos enviados y recibidos:

En la figura de arriba vemos la captura de una transferencia de entrada (datos enviados del PIC al PC) en el recuadro en verde se ve la dirección (0x81) del Endpoint IN y el tamaño del Report de 2 bytes. En el recuadro en rojo en el campo Raw Data tenemos 3F 00 el primer byte (00) representa el estado del pulsador y el segundo byte (3F) el valor en hexadecimal de la posición del potenciómetro.

Una captura de una transferencia de salida (del PC al PIC) sería la siguiente:

En ella vemos que la dirección configurada para el Endpoint de salida es 0x01 y que el tamaño del Report es de 2 bytes también, el primero para encender o apagar el Led 1 y el segundo para el Led 2.

Un vídeo de la aplicación funcionando en Windows XP:

Con respecto a Windows la demo está probada y funciona correctamente en los siguientes sistemas operativos: Windows XP profesional SP3, Windows 7 Profesional SP1 en arquitecturas de 32 y 64 bits.

Uso de HIDAPI en Linux

La librería Hidapi se puede utilizar de dos formas en Linux usando el driver hidraw del kernel o usando libusb-1.0, en esta demo se utilizará esta última opción. Para ello deberemos de instalar primero libusb-1.0 (versión dev) en nuestro sistema operativo en mi caso Ubuntu 11.04.

Para instalar libusb-1.0 lo podemos hacer por medio de la terminal:

sudo apt-get install libusb-1.0-0-dev

O por medio del gestor de paquetes sinaptic:

Después de ello debemos incluir el archivo de cabecera hidapi.h y hid-libusb.c a nuestro proyecto en Qtcreator, la configuración debe de quedar de la siguiente forma:

La interfaz de la aplicación en Linux será la siguiente:

En Linux para saber si nuestro dispositivo es reconocido y enumerado correctamente por el sistema operativo podemos utilizar el comando:

lsusb

En la consola nos aparecerá la lista de dispositivos USB conectados:

Donde podemos identificar nuestro dispositivo a través de VID (0461) y el PID (0020) del fabricante.

Si queremos obtener más información sobre el dispositivo utilizaremos el siguiente comando:

lsusb -d0461:0020 -v

Aquí tenéis un vídeo de la demo funcionando en Linux:

Descargas

Podéis descargar el código fuente completo de la aplicación de escritorio en Windows y Linux + Esquema circuito + versión de este artículo en .pdf desde aquí así como los ejemplos hechos por memo con libsub 0.1 y 1.0.

Fuentes de información


Saludos