scieee AI-readable full text Open interactive document viewer

Infraestructura hardware/software de bajo coste para implementación de dispositivos USB y drivers en Linux

Gascón Celdrán, Guillermo; Rodríguez-Avello Tapias, Javier

Abstract

El objetivo de este Trabajo de Fin de Grado ha sido construir una infraestructura hardware y software de bajo coste que permita el prototipado de dispositivos USB con diferentes características, además del desarrollo de controladores software para dichos dispositivos. Esta infraestructura está orientada a la docencia, y más concretamente a la adquisición de los conocimientos necesarios para el desarrollo de drivers USB. La infraestructura permite también acercar a los estudiantes a ciertas partes del complejo protocolo USB, todo ello sin realizar una gran inversión económica, gracias al bajo coste de los dispositivos que la conforman. Asimismo, su flexibilidad ofrece un gran potencial para desarrollo de prácticas de laboratorio, permitiendo una fácil extensión y adaptación de estas a un amplio espectro de dispositivos de entrada-salida. La parte software de la infraestructura tiene dos componentes bien diferenciados. El primero de ellos es un firmware desarrollado en este proyecto (basado en V-USB) que permite la implementación de dispositivos USB por software. Este firmware hace posible la construcción de dispositivos USB empleando un microcontrolador, y uno o varios dispositivos periféricos básicos, que pueden ser expuestos al usuario como un único dispositivo USB. Esto, unido al sistema de perfiles hardware del firmware, proporciona gran flexibilidad, ya que con un solo producto hardware se puede obtener una amplia funcionalidad combinando las funciones de múltiples perifericos. En segundo lugar, como parte del proyecto se ha desarrollado una colección de drivers de ejemplo que interactúan con diferentes periféricos haciendo uso de la API del kernel Linux. Este paquete de drivers sencillos permite a los estudiantes iniciarse en aspectos de desarrollo de controladores software para dispositivos USB en Linux. En cuanto a la parte hardware, se han construido distintos prototipos basados en microcontroladores AVR de ATMEL. El prototipo final de la infraestructura hace uso de una placa con microcontrolador ATMega328p, y está provisto de un circuito extra que proporciona el puerto USB para la conexión a un ordenador o dispositivo móvil. Este prototipo permite la conexión de distintos periféricos sencillos (conjuntos de LEDs RGBs, zumbadores, displays 7 segmentos, pantallas LCD, etc.), y soporta la depuración de su firmware USB via puerto serie, acessible mediante un segundo puerto USB.

Full text

Infraestructura hardware/software de bajo coste para implementación de dispositivos USB y drivers en Linux TRABAJO FIN DE GRADO Guillermo Gascón Celdrán y Javier Rodríguez-Avello Tapias Dirigido por: Juan Carlos Sáez Alcaide y Christian Tomás Tenllado Van Der Reijden Grado en Ingeniería del Software. Grado en Ingeniería de Computadores Facultad de Informática Universidad Complutense de Madrid Curso 2022-2023 Infraestructura hardware/software de bajo coste para implementación de dispositivos USB y drivers en Linux Memoria de Trabajo Fin de Grado Guillermo Gascón Celdrán y Javier Rodríguez-Avello Tapias Dirigido por: Juan Carlos Sáez Alcaide y Christian Tomás Tenllado Van Der Reijden Grado en Ingeniería del Software. Grado en Ingeniería de Computadores Facultad de Informática Universidad Complutense de Madrid Curso 2022-2023 ii Agradecimientos En primer lugar, nos gustaría agradecer a nuestros tutores de este Trabajo de Fin de Grado, Juan Carlos Sáez y Christian Tenllado, por su inestimable ayuda y dedicación. Su compromiso y generosidad nos han brindado todas las facilidades necesarias, así como un valioso aporte de conocimiento. De igual manera, queremos expresar nuestro profundo agradecimiento a nuestras familias y amigos, quienes han sido una fuente constante de motivación y apoyo. Su presencia en los momentos difíciles y su capacidad para extraer lo mejor de nosotros ha sido de gran ayuda. Por último, muchas gracias a ti Ana, por tu apoyo incondicional y por haber confiado en mí desde el inicio. iii iv Resumen El objetivo de este Trabajo de Fin de Grado ha sido construir una infraestructura hardware y software de bajo coste que permita el prototipado de dispositivos USB con diferentes características, además del desarrollo de controladores software para dichos dispositivos. Esta infraestructura está orientada a la docencia, y más concretamente a la adquisición de los conocimientos necesarios para el desarrollo de drivers USB. La infraestructura permite también acercar a los estudiantes a ciertas partes del complejo protocolo USB, todo ello sin realizar una gran inversión económica, gracias al bajo coste de los dispositivos que la conforman. Asimismo, su flexibilidad ofrece un gran potencial para desarrollo de prácticas de laboratorio, permitiendo una fácil extensión y adaptación de estas a un amplio espectro de dispositivos de entrada-salida. La parte software de la infraestructura tiene dos componentes bien diferenciados. El primero de ellos es un firmware desarrollado en este proyecto (basado en V-USB) que permite la implementación de dispositivos USB por software. Este firmware hace posible la construcción de dispositivos USB empleando un microcontrolador, y uno o varios dispositivos periféricos básicos, que pueden ser expuestos al usuario como un único dispositivo USB. Esto, unido al sistema de perfiles hardware del firmware, proporciona gran flexibilidad, ya que con un solo producto hardware se puede obtener una amplia funcionalidad combinando las funciones de múltiples perifericos. En segundo lugar, como parte del proyecto se ha desarrollado una colección de drivers de ejemplo que interactúan con diferentes periféricos haciendo uso de la API del kernel Linux. Este paquete de drivers sencillos permite a los estudiantes iniciarse en aspectos de desarrollo de controladores software para dispositivos USB en Linux. En cuanto a la parte hardware, se han construido distintos prototipos basados en microcontroladores AVR de ATMEL. El prototipo final de la infraestructura hace uso de una placa con microcontrolador ATMega328p, y está provisto de un circuito extra que proporciona el puerto USB para la conexión a un ordenador o dispositivo móvil. Este prototipo permite la conexión de distintos periféricos sencillos (conjuntos de LEDs RGBs, zumbadores, displays 7 segmentos, pantallas LCD, etc.), y soporta la depuración de su firmware USB via puerto serie, acessible mediante un segundo puerto USB. palabras clave: Firmware, USB, driver, kernel Linux, AVR, V-USB. v vi Abstract The objective of this Final Degree Project has been to build a low-cost hardware and software infrastructure that allows the prototyping of USB devices with different characteristics, as well as the development of software drivers for these devices. This infrastructure is oriented towards teaching, and more specifically to the acquisition of the necessary knowledge for the development of USB drivers. The infrastructure also allows to bring students closer to certain parts of the complex USB protocol, all this without making a large financial investment, thanks to the low cost of the devices that comprise it. Furthermore, its flexibility offers great potential for the development of laboratory practices, allowing easy extension and adaptation of these to a wide range of input-output devices. The software part of the infrastructure has two distinct components. The first one is a firmware developed in this project (based on V-USB) that allows the implementation of USB devices by software. This firmware makes it possible to build USB devices using a microcontroller, and one or several basic peripheral devices, which can be exposed to the user as a single USB device. This, coupled with the firmware hardware profiling system, provides great flexibility, since with a single hardware product a wide range of functionality can be obtained by combining the functions of multiple peripherals. Secondly, as part of the project, a collection of example drivers has been developed that interact with different peripherals making use of the Linux kernel API. This package of simple drivers allows students to get started in software driver development aspects for USB devices in Linux. On the hardware side, different prototypes based on ATMEL AVR microcontrollers have been built. The final prototype of the infrastructure makes use of an ATMega328p microcontroller board and is provided with an extra circuit that provides the USB port for connection to a computer or mobile device. This prototype allows the connection of different simple peripherals (RGB LED arrays, buzzers, 7-segment displays, LCD displays, etc.), and supports debugging of its USB firmware via serial port, accessible through a second USB port. Keywords: Firmware, USB, driver, Linux kernel, AVR, V-USB. vii Bibliografía 107 vi Índice de figuras 1.1 Diagrama de Gantt del proyecto . . . . . . . . . . . . . . . . . . . . . . 4 2.1 Arquitectura del subsistema USB en Linux. [9] . . . . . . . . . . . . . . 8 2.2 Jerarquía de descriptores USB. [10] . . . . . . . . . . . . . . . . . . . . 13 2.3 Dispositivo Blinkstick usado actualmente en LIN [14] . . . . . . . . . . 18 4.1 PinoutATTiny85[26] ........................... 28 4.2 Pinout de la placa Digispark [29] . . . . . . . . . . . . . . . . . . . . . 28 4.3 Pinout ATmega328p [30] . . . . . . . . . . . . . . . . . . . . . . . . . . 29 4.4 Pinout de la placa NANO [33] . . . . . . . . . . . . . . . . . . . . . . . 30 4.5 Esquema del circuito del conector MicroUSB . . . . . . . . . . . . . . . 31 4.6 Placa Bee 2.0 de la asignatura Arquitectura interna de Linux y Android [3] 32 4.7 Tira de LEDs circular de NeoPixel . . . . . . . . . . . . . . . . . . . . 33 4.8 Arquitectura de cada LED [35] . . . . . . . . . . . . . . . . . . . . . . 34 4.9 Flujo de bits dentro del controlador de cada LED [35] . . . . . . . . . . 35 4.10 Cálculo de cada dato según los flancos de subida o bajada en 1-Wire [35] 35 4.11 Diagrama del circuito del display 7 segmentos de la placa Bee 2.0 [36] . 36 4.12 Pantalla OLED utilizada en el proyecto . . . . . . . . . . . . . . . . . . 37 B.1 Menú de herramientas de Arduino IDE . . . . . . . . . . . . . . . . . . 90 B.2 Mensaje de salida una vez flasheado el firmware . . . . . . . . . . . . . 91 C.1 Captura mostrando el tráfico USB de Wireshark capturado . . . . . . . 94 D.1 ProjectGanttChart ............................ 100 vii viii Capítulo 1 Introducción En este capítulo introductorio se presenta la motivación de este proyecto, y se describen los objetivos y la planificación de las tareas para llevarlo a cabo. 1.1 Motivación El protocolo USB se usa ampliamente en todo el mundo para la interacción con periféricos de muy diversa naturaleza. Cualquier usuario puede conectar un dispositivo a su ordenador. Al hacerlo, el driver correspondiente instalado en el sistema reconoce el dispositivo y permite realizar las funciones para las que este ha sido desarrollado. Para lograr esta simplicidad de uso, el desarrollador del driver y el fabricante del dispositivo USB tienen que lidiar con la complejidad del protocolo USB. En las titulaciones actuales de grado en Informática, el tiempo limitado que puede dedicarse a describir aspectos de bajo nivel de entrada-salida unido a la complejidad de la tecnología y especificación USB dificulta a los estudiantes la adquisición de conocimientos sobre diseño de dispositivos USB y desarrollo de drivers para estos. Para desarrollar contenidos prácticos sobre estos aspectos se ha disponer además de hardware USB suficientemente sencillo, versátil y de bajo coste, lo cual puede constituir un reto significativo. En la Facultad de Informática de la Universidad Complutense de Madrid una de las pocas asignaturas que aborda el desarrollo de drivers USB es la asignatura Arquitectura Interna de Linux y Android, optativa común ofertada a estudiantes de distintas titulaciones de grado. En esta memoria nos referiremos a esta asignatura como “LIN”, ya que este es el nombre corto empleado administrativamente en la Facultad de Informática para referirse a ella. En las prácticas de laboratorio de LIN se hace uso del dispostitivo Blinkstick Strip [1], un dispositivo USB sencillo que cuenta con 8 LEDs de colores cuyo estado puede alterarse individualmente. La simplicidad de este dispositivo permite elaborar prácticas para familiarizarse con el desarrollo de drivers USB, especialmente aquellos 1 implementados a nivel del kernel del sistema operativo. Blinkstick Strip, no obstante, tiene varias limitaciones. En primer lugar, este dispositivo es tan sencillo que impide explorar el potencial de USB en profundidad. Al fin y al cabo, solo permite alterar el estado de cada uno de los LEDs mediante el envío de mensajes de control sencillos al dispositivo. En consecuencia, las prácticas correspondientes no posibilitan la experimentación con otras modalidades de transferencia de datos recogidas por la especificación USB, y, por tanto, emplean un subconjunto muy reducido de las llamadas de API para desarrollo de drivers USB. En segundo lugar, su escasa versatilidad no justifica su precio (alrededor de 20€ por unidad) para posibilitar una adopción a gran escala del dispositivo en distintas asignaturas y titulaciones de grado en la facultad. Por último, aunque el firmware del Blinkstick Strip está disponible de forma gratuita, su fabricante no proporciona documentación sobre cómo realizar modificaciones del mismo empleando la platforma hardware comercial. Esto constituye una barrera para la utilización del dispositivo para prácticas de diseño de firmware, donde es preciso realizar programación bare metal. Con independencia del protocolo o tecnología de entrada-salida empleada, disponer de una plataforma hardware suficientemente flexible es un aspecto clave en prácticas de laboratorio centradas en la interacción con periféricos. En particular, la capacidad de añadir nuevos dispositivos de entrada-salida al sistema sin requerir incorporar mucho cableado adicional permite desarrollar un gran número de prácticas de laboratorio para así poder satisfacer las necesidades de distintas asignaturas. Asimismo, esto posibilita la introducción de pequeñas variaciones en los contenidos prácticos de una misma asignatura a lo largo del tiempo. Estos factores permiten extender el ciclo de vida de una plataforma hardware orientada a docencia, y con ello reducir sustancialmente el coste de equipamiento de laboratorio. La placa Bee [2], [3] desarrollada por el Prof. Christian Tenllado es un claro ejemplo de sistema diseñado para la construcción de plataformas hardware flexibles. Se trata de una placa de expansión para sistemas basados en el pinout del mini PC Raspberry Pi (v1-v4) que está provista de un conjunto de dispositivos periféricos sencillos, y permite la ampliación de estos mediante circuitos de polarización genéricos. Esta placa, utilizada en sus distintas versiones en asignaturas de grado de las Facultades de Informática y de Ciencias Físicas en la UCM, a pesar de no proporcionar soporte para USB, ha supuesto una gran inspiración para la realización de este TFG. 1.2 Objetivos El objetivo principal de este proyecto ha sido construir una infraestructura hardware software de bajo coste que permita el prototipado de dispositivos USB y el desarrollo de drivers para estos dispositivos. Nuestra infraestructura está basada en microcontroladores AVR de Atmel [4], y en el proyecto V-USB [5], que proporciona un firmware genérico para implementar soporte de USB por software. Nuestro proyecto extiende V-USB para 2 facilitar la creación de firmware USB que gestione múltiples periféricos simultáneamente. Aunque existen otras alternativas al uso del framework de V-USB (p.ej. el uso de microcontroladores con soporte de USB por hardware), la infraestructura propuesta no solo tiene bajo coste, sino que también ofrece gran versatilidad para el desarrollo de software de bajo nivel. En particular, permite experimentar tanto con el desarrollo de drivers USB en sistemas provistos de sistema operativo, así como como la implementación de firmware USB para la gestión de hardware, empleando programación bare metal. En este TFG se ha realizado desarrollo en ambos ámbitos. Otro objetivo del proyecto es proporcionar un conjunto de drivers USB que ilustren la interacción con dispositivos de distinta naturaleza, como conjuntos de LEDs RGB, pantallas LCD, displays 7 segmentos, zumbadores o sensores de temperatura. Para ello la infraestructura propuesta se acompaña de una colección de drivers que emplean una amplia colección de funciones de la API proporcionada por el kernel Linux para el desarrollo de drivers USB. Estos drivers se implementan como módulos cargables del kernel Linux, para así servir de base para futuras prácticas de la asignatura LIN. Aunque el desarrollo de drivers USB en espacio de usuario con libusb [6] queda fuera del ámbito de este TFG, los drivers del kernel proporcionados sirven de base para la creación de controladores en espacio de usuario desarrollados con esta biblioteca. Finalmente, dada la flexibilidad proporcionada por la placa Bee [2], [3], en este proyecto se han estudiado formas de emplear dicha placa como base para la implementación de dispositivos USB. Nótese que la Facultad de Informática ya ha adquirido un buen número de estas placas para distintas asignaturas, por lo que la reutilización de este hardware como bloque básico de la infraestructura USB permite amortizar aún más la inversión realizada. 1.3 Plan de trabajo En este proyecto se han desarrollado dos prototipos hardware de la infraestructura. El primer prototipo está basado en el microcontrolador ATTiny85 –mismo chip empleado por el dispositivo Blinkstick Strip. El segundo prototipo supera las limitaciones del anterior (descritas en detalle en el capítulo 4), y emplea el microcontrolador ATMega328p. Durante el proyecto ha sido preciso familiarizarse con la programación sobre los dos citados microcontroladores –integrados en las placas Digispark [7] y Nano [8], respectivamente–, así como con una amplia colección de dispositivos de entrada-salida empleados en los prototipos. Entre estos dispositivos se incluye un anillo circular de LEDs RGB, un zumbador (o buzzer ), una pantalla OLED y un display 7 segmentos (integrado en la placa Bee [2]). Teniendo esto en cuenta, se llevó a cabo un plan de trabajo, que consta de las siguientes tareas (ver Fig D.1): T1 Estudio de la placa Digispark y análisis del control de los diferentes periféricos bajo 3 Figura 1.1: Diagrama de Gantt del proyecto entorno Arduino T2 Estudio del dispositivo Blinkstick Strip y de su firmware de código abierto T3 Análisis en profundidad del firmware del proyecto V-USB T4 Desarrollo de primera versión del firmware V-USB sobre la placa Digispark (sin soporte completo de périféricos) T5 Estudio de las funciones de E/S utilzando los registros del microcontrolador ATTiny85 T6 Ampliación de la funcionalidad del firmware V-USB desarrollado en T4 con soporte de control de dispositivos básicos T7 Desarrollo del control a nivel de registros (sin usar entorno Arduino) del Diodo LED de la placa Digispark T8 Desarrollo del control a nivel de registros del anillo circular de LEDs T9 Desarrollo del control a nivel de registros del buzzer T10 Desarrollo del control a nivel de registros del display 7 segmentos T11 Estudio de alternativas de diseño de circuitería extra asociada al conector micro USB requerido por el segundo prototipo T12 Diseño del prototipo hardware basado en ATMega328p T13 Integración de placa Nano (con ATMega328p) con circuito del conector micro USB T14 Modificación del firmware para funcionar con chip ATMega328p T15 Adaptación de herramienta de depuración a nuestro prototipo T16 Desarrollo del control a nivel de registros de la pantalla OLED 4 T17 Estudio de los temporizadores incluidos en el ATMega328p T18 Estudio de señales PWM y su generación a través de temporizadores T19 Refactorización del firmware de V-USB para la utilización de perfiles T20 Implementación de perfiles hardware a través de macros T21 Migración de la implementación del controlador de LED a PWM T22 Migración de la implementación del controlador del buzzer a PWM T23 Montaje del prototipo final (basado en ATMega328p) en placa perforada T24 Redacción de la memoria del TFG Durante el desarrollo del proyecto se han mantenido distintas reuniones con los directores del TFG. La frecuencia de las mismas se ha ido incrementando de forma gradual conforme ha ido avanzado el desarrollo. Estas reuniones han servido para resolver problemas de diseño, plantear las diferentes fases y solucionar múltiples dudas y limitaciones que han ido surgiendo durante todo el proyecto. Para la gestión tanto del código como de las fuentes de la memoria del proyecto se han utilizado repositorios compartidos en la plataforma GitHub. Los enlaces a ambos repositorios son: •Código fuente. https://github.com/ggasconn/TFG_USB •Memoria. https://github.com/ggasconn/MemoriaTFG-USB/ 1.4 Organización de la memoria El resto de esta memoria se ha dividido en los siguientes capítulos: • El capítulo 2 describe las características de bajo nivel del protocolo USB, así como sus abstracciones principales, como es el caso de los descriptores, endpoints, o los URBs. • El capítulo 3 proporciona una introducción general al proyecto V-USB y analiza la estructura del código fuente del firmware proporcionado. Este capítulo también discute las posibles alternativas al uso de V-USB para la construcción de infraestructuras hardware software como la propuesta de este proyecto. • El capítulo 4 presenta las características de los distintos microcontroladores utilizados en el proyecto, los detalles técnicos de las placas empleadas en diversas fases del proyecto, y los aspectos hardware de los periféricos soportados por nuestro prototipo. • El capítulo 5 analiza en detalle todos los elementos y funcionalidades implementadas en el firmware del prototipo, que constituye una extensión del proyecto 5 V-USB. • En capítulo 6 describe en detalle los drivers USB de ejemplo que gestionan los dispositivos periféricos con soporte en el firmware. • El capítulo 7 recoge las principales conclusiones del TFG y enumera las líneas de trabajo futuro Al final de esta memoria pueden encontrarse también los siguientes cinco apéndices: • El apéndice A enumera las contribuciones aportadas por cada integrante de este Trabajo Fin de Grado al proyecto. • En el apéndice B se describen las instrucciones para poder analizar los paquetes URBs de USB con el software Wireshark. • En el apéndice C se proporciona una guía que detalla el proceso para poder flashear el firmware en la placa del prototipo. • Finalmente los apéndices D y E recogen la traducción al inglés de este capítulo introductorio así como de las conclusiones. 6 Capítulo 2 Tecnología USB Este capítulo consta de una introducción general al protocolo de comunicación USB, así como su implementación en Linux y los detalles a más bajo nivel que forman parte de esta implementación, tratando conceptos como los de Descriptor USB,Endpoint o Clases USB. 2.1 ¿Qué es USB? USB nace en 1996 como una manera de establecer la comunicación entre los ordenadores y sus periféricos, buscando estandarizar la interfaz de conexión de estos ya que por aquél entonces los periféricos disponibles utilizaban diversas formas de conexión como puertos paralelos, puertos serie, PS2, entre otras. Las palabra USB origina de las siglas Universal Serial Bus, dicho nombre describe de una manera clara lo que se intenta conseguir con el desarrollo de este estándar. Aunque originalmente este protocolo fue diseñado para usarse en ordenadores, hoy en día está presente en una inmensa variedad de dispositivos tales como teléfonos móviles, tabletas, adaptadores de carga, dispositivos de almacenamiento, etcétera. Esto ha ocasionado también algunas dificultades ya que su estandarización ha traído consigo un gran incremento de complejidad del protocolo debido a que debe de adaptarse para permitir la interoperabilidad entre dispositivos que en ocasiones son muy dispares entre sí. 2.2 Subsistema USB en Linux USB Core es el subsistema software que implementa todo el estándar USB en el kernel Linux. El stack USB completo está segmentado en tres partes claramente diferenciadas, como se puede observar en la figura 2.1, que trabajan en conjunto para reconocer los dispositivos USB y conectarlos con el usuario, entre muchas otras tareas. 7 2.6.1 Control Endpoints Los endpoints de control se encargan de las transferencias de información relativa a la configuración del dispositivo y pueden ser de tipo IN o de tipo OUT. Aunque este tipo de endpoints no se suelen usar para transferencias masivas de datos debido a sus limitaciones y uso restringido a configuración, pueden usarse para mandar algunos bytes de datos. El endpoint 0 siempre está reservado para la configuración del dispositivo con el host y no tienen ningún descriptor asignado, ya que toda su configuracion está definida por el estandar USB. De igual manera es especial en cuanto a su tipo de dirección, ya que es accesible tanto por el tipo IN como por el OUT. 2.6.2 Interrupt Endpoints Este tipo de transferencias se utiliza cuando la comunicación entre el dispositivo y el host es asíncrona. La manera de avisar que se quieren recibir datos es a través de un polling, cuyo intervalo de consulta es parametrizable por el usuario de 1ms a 255ms para dispositivos low-Speed y de 125us a 4096ms para dispositivos high-Speed. El sentido de la comunicación puede ser IN oOUT. Para comunicarse, se revisa el endpoint con el intervalo definido por el polling para ver si hay alguna interrupción pendiente de atender, si es así, se atiende. Esto es igual para ambas direcciones, cuando sea de tipo IN se enviarán los datos en un sentido y cuando sea de tipo OUT se realizará en sentido contrario. En el caso de que el envío vaya desde el dispositivo al host, la controladora USB de este último será la encargada de realizar el polling, en caso contrario, será el dispositivo a través de su mecanismo concreto de implementación. 2.6.3 Bulk Endpoints Los endpoints de tipo Bulk se usan para enviar grandes cantidades de información por el bus USB. Los escenarios en los que este tipo de endpoints son necesarios son muy variados, desde enviar un documento a una impresora para imprimir, hasta copiar un fichero de datos a un disco USB. Para reservar ancho de banda, este tipo de transferencias usan espacio que otras transferencias no han usado una vez han terminado. Por ello, puede que las transferencias de este tipo se vuelvan algo lentas si no dispone de espacio en el bus. Este tipo de transferencias solo son soportadas por dispositivos Full yHigh-Speed y dependiendo del estandar que cumplan tienen un tamaño máximo de transferencia u otro. También cuentan con detección de errores CRC, por lo que permite asegurar la integridad de la información transmitida. Al igual que los tipos anteriores, permite transferencias de tipo IN oOUT. 14 2.7 Clases USB La organización usb.org [11] tiene definidas una serie de códigos de clase que se utilizan para identificar la funcionalidad del dispositivo y ayudan al host a cargar un driver que sea compatible basado en esa funcionalidad. La información se representa en tres bytes de datos hexadecimales y contiene: • Byte 1 - Clase base. En este byte se describe el uso del dispositivo de manera general. Por ejemplo: 01h para dispositivos de audio o 07h para impresoras. • Byte 2 - Subclase. Este byte describe dentro de la clase el uso concreto que se le va a dar. Por ejemplo, dentro de la clase de audio podemos encontrar 00h para dispositivos de streaming o 03h para dispositivos MIDI. • Byte 3 - Protocolo. En este último byte se define el protocolo a usar. Siguiendo con el ejemplo de Audio, podemos usar 00h para USB 1.0, 20h para USB 2.0 o 02h para especificar que es asíncrono. Como veíamos en el apartado anterior, esta información viaja al host a través de los descriptores y podemos encontrarla o bien en el descriptor de dispositivo, en el de interfaz o incluso en ambos. Aunque en este proyecto solo se trabaja y se trata con la clase HID, Human Interface Device, hay algunas otras clases que también son muy conocidas y usadas como: •Audio Class. Usada por dispositivos de audio. •CDC Class. Usada por dispositivos de comunicación. •Mass Storage Class. Usada por dispositivos de almacenamiento. 2.7.1 Human Interface Device Class (HID) La clase de dispositivos HID, codificada como 03h ,se usa principalmente para representar dispositivos que se comunican con el host y son controlados por humanos. A través de este tipo de dispositivos, el host es capaz de interpretar acciones que los humanos realizan sobre los dispositivos. Son realmente cotidianos, ya que dentro de esta categoría podemos englobar a los teclados, ratones, mandos, joysticks, botones y un largo etcétera. Al implementar un dispositivo de este tipo es necesario seguir ciertos estándares: • Toda la información debe moverse en Reports. Los trataremos más adelante pero son estructuras que contienen la información necesaria para la acción a realizar. • Un endpoint de tipo INTERRUPT IN es necesario para enviar reports de tipo Input al host. • El máximo número de endpoints INTERRUPT es uno IN y otro OUT, siendo este último opcional. 15 • Al poder el dispositivo enviar información a través del endpoint INTERRUPT en cualquier momento, el host debe de estar preparado para revisar el polling y recoger esta información 2.7.1.1 Reports Como hemos mencionado anteriormente, los Reports son estructuras donde se encapsula la información que viaja en la comunicación con un dispositivo HID. Son de un tamaño fijo y previamente definido, ya que el host debe conocer esta información para generarlos. Un dispositivo HID puede tener múltiples Reports, identificados por Report IDs, que básicamente son indentificadores hexadecimales que permiten diferenciar el tipo de información que se envía y se recibe. Todas estas estructuras tienen un uso definido, aunque en caso de utilizarse para acciones personalizadas este puede dejarse sin definir. Los Reports se mueven a través de transferencias, y las dos más usadas dentro de esta clase de dispositivos son: •GET_Report, permite enviar al host información. •SET_Report, permite recibir información del host. 2.8 URBs Los URBs son paquetes de datos donde se encapsula una petición USB, su propio nombre hace referencia a ello, ya que URB son las siglas de USB Request Block [12]. Esta estructura, propia de la implementación de USB en Linux, posee todos los valores relevantes para que una petición USB pueda llevarse a cabo, pudiendo entregar los datos y obtener un estado de respuesta encapsulado también en URB. Cuando se emplea la API asíncrona, cada vez que se envía uno de estos paquetes se produce una operación asíncrona, que tiene asociada una función callback que será ejecutada una vez finalice dicha operación. Además, cada dispositivo lógico soporta una cola de peticiones, por lo que el hardware puede ir llenando esta cola mientras que el driver atiende las peticiones más antiguas. En la API USB integrada en el kernel Linux los URBs se pueden manejar de manera muy natural, esto es posible gracias a la estructura que nos ofrecen, struct urb , que encapsula todos los datos que pueden llevar estos paquetes. Para la gestión de esta estructura, el kernel también nos ofrece funciones de las cuales podemos destacar las siguientes: •usb_alloc_urb() , permite reservar memoria donde alojar un URB. Esta memoria luego será compartida con el controlador USB hardware para que envíe el paquete al dispositivo. 16 •usb_fill_control_urb() / usb_fill_int_urb() / usb_fill_bulk_urb() , son las funciones que, dependiendo del tipo de endpoint, nos permiten rellenar los datos que viajarán en el paquete. •usb_submit_urb() , manda el URB a la controladora de host una vez está listo para ser transmitido. •usb_unlink_urb() , permite cancelar la transmisión de un URB que ya ha sido enviado. 2.9 API USB en Linux. Síncrona y asíncrona Dentro del USB Core que viene incorporado en el kernel Linux tenemos dos modelos de API para realizar llamadas USB. El primero de ellos es el más estandar, está soportado por todos los tipos de transferencia y es el modelo asíncrono. [13] La manera de funcionar del modelo asíncrono consiste en crear un URB, rellenarlo y posteriormente enviarlo a la controladora USB asociado a una función callback. Esta función será llamada una vez se complete la transferencia y será la encargada del siguiente paso. Construida por encima de esta API podemos encontrar la API síncrona, que principalmente hace uso de todas las funciones que nos ofrece la anterior pero además añade bloqueos una vez se envían los URBs para esperar a su respuesta. Esta API oculta al desarrollador todos los detalles de reserva de memoria y envío de los URBS, ya que ofrece funciones muy sencillas para enviar los datos necesarios. A pesar de que la API síncrona es más sencilla de usar también es mucho más ineficiente, ya que bloquea el programa en todos los puntos donde se envíen paquetes, quedando sin la posibilidad de ejecutar otro código durante el tiempo que transcurre hasta que se recibe la respuesta. En este proyecto, se abordan ambas APIs para comparar los métodos de uso y dotar a los usuarios del dispositivo de diferentes ejemplos de uso de estas interfaces de programación que nos ofrece el kernel Linux. 2.10 Blinkstick Strip El desarrollo de este TFG está muy enfocado a la docencia, ya que lo que se pretende es conseguir un dispositivo que sea barato de producir y muy versátil para poder desarrollar diversos tipos de drivers y aprender el funcionamiento de las diferentes partes que se involucran en este campo. Actualmente, en la asignatura optativa Arquitectura Interna de Linux y Android, se está usando un dispositivo llamado Blinkstick Strip [14] para impartir este tipo de lecciones 17 donde los alumnos aprenden a desarrollar drivers para el kernel Linux. Figura 2.3: Dispositivo Blinkstick usado actualmente en LIN [14] Pero este dispositivo tiene diferentes aspectos negativos que hacen de él una opción no del todo aconsejable, debido a que el precio de venta es demasiado elevado para lo que ofrece y la cantidad de material que hay que adquirir para un aula. También es demasiado sencillo, lo que limita en gran medida el tipo de drivers que los alumnos pueden desarrollar y la API que pueden usar, ya que este dispositivo solo admite transferencias de tipo CONTROL. Por ello, nuestro dispositivo ofrece significativas ventajas tanto en precio como en versatibilidad: • Comparado con el coste de adquirir Blinksticks, nuestro dispositivo es mucho más barato de producir. • Dispone de diferentes endpoints CONTROL yINTERRUPT lo que nos permite desarrollar diferentes tipos de drivers usando ambas APIs. • El Blinkstick solamente cuenta con unos cuántos diodos LEDs, mientras que nuestro dispositivo tiene ya implementados diversos periféricos de varios tipos con gran posibilidad de ampliación debido a la arquitectura del firmware y la disponibilidad de pines en el microcontrolador usado. 18 Capítulo 3 Proyecto V-USB V-USB es un framework que permite crear dispositivos USB con microcontroladores que no disponen de soporte hardware para este fin. Usa una implementación 100% software para emular la comunicación permitiendo así crear dispositivos con esta funcionalidad a un coste inferior y con menor complejidad que los chips que tienen esta característica implementada en hardware. En este capítulo, se describen sus detalles y su arquitectura interna, así como algunas alternativas que se podrían haber usado para la creación de la infraestructura diseñada. 3.1 Framework V-USB El proyecto V-USB [15] distribuye una implementación de uso libre que permite la creación de dispositivos USB low-speed a través de software, capaz de funcionar en los microcontroladores AVR de la empresa Atmel. De esta manera, nos brinda la posibilidad de construir dispositivos USB por muy poco coste ya que hace uso únicamente del hardware del microcontrolador sin ningún chip adicional, por lo que es una alternativa económica y sencilla frente a otras opciones que hay para construir este tipo de dispositivos. La manera en la que V-USB implementa esta funcionalidad es a través de la técnica Bit Banging [16]. Bit Banging es la manera de implementar en software la lógica de un protocolo usando pines genéricos de E/S, en lugar de usar un controlador hardware específicamente diseñado para ello. Estas señales son generadas activando o desactivando las líneas de datos y ajustando los tiempos de espera a los específicos del protocolo. Aunque es una técnica ampliamente usada, también presenta algunos incovenientes como la fiabilidad en la transmisión o la cantidad de procesamiento que conlleva lo que deriva en una pérdida de rendimiento. Aun así, es una buena alternativa en escenarios como en el que nos encontramos donde buscamos un microcontrolador de coste bajo y poco hardware complementario. 19 Las limitaciones que pone en los chips que soporta son extremandamente bajas, por lo que prácticamente cualquier microcontrolador Atmel será compatible. Algunas de las características que deben de tener los chips son: • 2kB de memoria flash • 128B de memoria RAM • Reloj de al menos 12MHz, ya que este parámetro es crítico para su funcionamiento. Soportando también las frecuencias: 12.8 MHz, 15MHz, 16MHz, 16.5MHz y 20MHz. Además, no requiere de ningún puerto UART, timer o hardware especial para funcionar. Esto supone una gran ventaja ya que en caso de que el microcontrolador elegido tenga estas características, quedan completamente disponibles para su uso. La libería está escrita en C junto con algunas partes en ensamblador y consta de diferentes ficheros donde se emula el comportamiento de un controlador USB hardware, así como diferentes ficheros de cabecera .h que permiten configurar algunos de los parámetros disponibles en este tipo de dispositivos, y activar o desactivar las funcionalidades que el framework implementa. En este proyecto, hemos hecho uso de la librería tanto en el microcontrolador ATTiny85 como en el ATMega328P con completo éxito, siendo este último mucho mejor opción debido a la frecuencia de reloj y el número de puertos E/S, así como del hardware adicional que trae, como timers o generadores de señales. 3.2 Arquitectura Interna La arquitectura interna de V-USB [17] se basa en una serie de ficheros que trabajan en conjunto para implementar la funcionalidad que ofrece el framework. Posee un diseño bastante modular que permite adaptarse a múltiples microcontroladores y herramientas de compilación con apenas unos cambios en ficheros de cabecera .h. En la parte de más bajo nivel de la librería podemos encontrar cuatro ficheros escritos en ensamblador que implementan rutinas para tareas específicas como la comprobación de errores en la comunicación o ajustes de temporización en base a la velocidad de reloj elegida. Estos ficheros son: •usbdrvasm.S : Este es el principal fichero con código ensamblador. Contiene las instrucciones más generales de la comunicación USB, además de la comprobación de errores mediante el mecanismo CRC. También se encarga de incluir la parte ensamblador dependiente de la velocidad de reloj elegida. •usbdrvasmX.inc : Con el framework se incluyen diferentes ficheros de este tipo, donde la Xdel nombre representa la velocidad del reloj, y contiene las rutinas necesarias para la comunicación y el control del dispositivo USB generado, así como manejo de interrupciones y otras funciones relacionadas con el envío y recepción de datos. 20 • esencialmente ajustan las instrucciones al tiempo del reloj escogido. El fichero adecuado a la velocidad escogida es incluido por el fichero anterior, usbdrvasm.S. •asmcommon.inc : Su función principal es procesar los paquetes USB cuando son recibidos. Aquí podemos encontrar mayormente instrucciones incluidas en otros ficheros, ya que el código que contiene se extrae aquí debido a que su funcionamiento es independiente de la velocidad de reloj elegida. •usbdrvasm.asm : Este fichero es mayormente un fichero de compatibilidad. Se necesita cuando el entorno de compilación utiliza IAR-C, otro compilador ampliamente conocido que sirve de alterntiva a GCC. Un poco más cercano al usuario y escrito en Cpodemos encontrar la API que nos permite incorporar V-USB a nuestro proyecto junto con algunas funciones de más alto nivel. En este nivel también nos encontramos con los ficheros de cabecera que permiten personalizar y ajustar el proyecto a nuestras necesidades hardware específicas. Los ficheros que conforman esta capa son: •usbdrv.h : En este fichero podemos encontrar la interfaz de la API que nos permite integrar la comunicación USB en nuestro proyecto. Aquí se encuentran definidas todas las funciones que posteriormente implementaremos, algunas de ellas de manera obligatoria y otras que podemos habilitar a través de macros para activar ciertas funcionalidades que nos ofrece V-USB. Ya que la implementación de estas funciones es completamente personal y dependiente del usuario de la libería, en el capítulo donde se describe el firmware 5 se pueden encontrar detalladas algunas de las funciones más importantes. •usbdrv.c : Contiene la implementación de las funciones más generales y de más alto nivel del driver. Este fichero es el que se tiene que enlazar desde nuestro código. •usbconfig.h : Otro fichero de configuración importante. Aquí podemos encontrar definidos los parámetros que V-USB nos permite configurar, entre los que podemos encontrar ajustes propios del protocolo USB y ajustes más cercanos a la parte del microcontrolador usado. Como su configuración también es muy dependiente del usuario, en el capítulo Firmware 5 se puede encontrar una explicación detallada de las partes más importantes. •usbportability.h : Aquí podemos encontrar mayormente sentencias que permiten ampliar la compatibilidad entre compiladores. Gracias a este fichero se puede elegir entre los entornos IAR-C yGCC. Por último, encontramos la utlidad de debug que nos proporciona el framework. Consiste en un módulo que usando comunicación por puerto serie habilita una interfaz de debug a la que podemos conectar un PC y ver la salida que enviemos por dicho canal. Consta de dos ficheros, oddebug.c y oddebug.h cuya explicación en detalle se puede encontrar en la sección 5.7. 21 3.3 Dificultades encontradas La elección de este framework para implementar el diposisito USB ha sido un gran acierto dadas todas las ventajas que ofrece, pero a pesar de ello, también hemos encontrado algunas dificultades durante su uso. La primera de ellas es la documentación. La mayor parte de información la podemos encontrar en los ficheros .h del proyecto en forma de comentarios y aunque son de gran valor, ya que describen con detalle las diferentes funciones, estructuras y macros, se necesita cierto conocimiento técnico sobre el protocolo USB y sobre microcontroladores para entender con claridad lo que describen. Hemos echado en falta una wiki más completa con mayor nivel de detalle que nos ayude a comprender algunos de los conceptos específicos del protocolo USB. Otro de los inconvenientes que hemos encontrado es que hay múltiples proyectos que implementan diferentes periféricos, de maneras dispares y algunos con prácticas no del todo correctas como la desactivación del Watchdog, mecanismo de control esencial para evitar que se cuelgue el dispositivo. Dado que no hemos encontrado proyectos que soporten diferentes periféricos al mismo tiempo en el firmware, la implementación de este soporte multidispositivo ha sido algo complicada de llevar a cabo. 3.4 Alternativas a V-USB V-USB es una excelente opción para los escenarios que se tratan en los apartados anteriores, esencialmente nos permite emular un dispositivo USB en microcontroladores con pocos recursos utilizando simplemente código C. Pero es posible que para otros proyectos necesitemos, por ejemplo, una mayor capacidad de procesamiento paralelo a la gestión de la comunicación USB, o mayor velocidad en la propia comunicación, ya que V-USB solo implementa dispositivos USB 1.0 o lo que es lo mismo Low Speed USB. Como alternativa para resolver los escenarios de ejemplo propuestos podríamos usar diferentes microcontroladores que incorporen una controladora hardware de USB. Gracias a esto, liberamos mucho a la CPU de la gestión de la comunicación, ya que todo este proceso se trata por un hardware a parte dedicado exclusivamente a ello. Dentro de esta gama de microcontroladores y sin salirnos de la familia ATMega, encontramos el ATMega32u4. Este microcontrolador ofrece, entre otras funcionalidades, la comunicación USB por hardware con las siguientes características: • Trabaja bajo el estandar USB 2.0 Full-speed/Low Speed y permite usar interrupciones • Soporta transferencias de datos de hasta 12Mb/s y 1.5Mb/s 22 • 6 Endpoints, programables a través de registros, tanto de tipo IN como OUT y funcionando en todos los modos • 832 bytes de memoria USB DPRAM completamente independiente para uso de los endpoints • Mejor eficiencia cuando se trabaja en modo Low Speed Saliendo de la marca ATmel, otra de las grandes alternativas debida a su potencia, y muy usada hoy en día, es el STM32 de la marca ST [18]. Este microcontrolador también nos ofrece la implementación USB por hardware con las siguientes características: • Trabaja con los estándares USB 2.0/1.0 • Permite velocidades de transmisión de hasta 480 Mb/s y 12 Mb/s • Tiene 6 endpoints completamente configurables • Tiene un cristal dedicado que trabaja a una frecuencia de 48MHz • Posee un DMA interno para USB A pesar de que los microcontroladores con hardware USB también son una alternativa muy extendida en el campo de desarrollo de dispositivos USB, podemos enumerar ciertas ventajas en usar una implementación por software que nos hace decantarnos por esta opción: • Los chips AVR son normalmente más fáciles de obtener que otros con soporte hardware para USB • La mayoría de los chips que cuentan con este hardware vienen en empaquetados SMD, es decir, son de un tamaño muy pequeño y no permiten el prototipado de manera sencilla debido a la dificultad de conectarnos sin ser soldados en una placa. • El entorno de desarrollo es extremadamente simple, basta con tener un compilador de tipo GCC y un editor de texto plano. • Son significantemente más baratos. Por poner un ejemplo, en el distribuidor español Mouser [19] podemos adquirir un ATMega328p por apenas 2,16€, mientras que el precio de un ATMega32u4 alcanza los 5,27€. A pesar de que al microcontrolador ATMega hay que añadirle algunos componentes extra para permitir el uso de un puerto USB, el precio de estos es prácticamente desprecible ya que basta con 4 resistencias de diferentes valores para tener esta configuración funcionando. 23 Para el prototipo final del proyecto, hacemos uso de la placa NANO que incorpora el microchip ATmega328p. Además de los 14 pines de E/S mencionados anteriormente, contamos con 8 pines de entrada analógica y 6 pines PWM. Cuenta también con un puerto USB utilizado en este proyecto como salida de puerto serie (para depuración del firmware), y un botón de reset para reiniciar la placa [31]. El uso de esta placa es una opción muy interesante para la creación de prototipos, ya que se puede integrar fácilmente en circuitos breadboard o perfboard [32]. La figura 4.4 muestra la disposición de los pines en la placa NANO. Figura 4.4: Pinout de la placa NANO [33] Para integrar el conector Micro USB en esta placa, es necesario montar un pequeño circuito con algunos componentes que permiten regular el voltaje de las líneas de datos al establecido para la comunicación USB por el estandar. En la figura 4.5 podemos ver el circuito al completo que consta de: • Conector MicroUSB • 1x Resistencia 1.5k • 1x Resistencia 1M • 2x Resistencias 68 Ohm 30 Figura 4.5: Esquema del circuito del conector MicroUSB 4.2 Periféricos soportados por el proyecto En este apartado se describen los distintos elementos hardware a los que se ha dado soporte en el proyecto. Se ha partido de una placa inicial, la placa Bee 2.0, utilizada en la asignatura LIN que cuenta con distintos periféricos, entre ellos un display 7 segmentos o un buzzer. También se ha hecho uso de un anillo led circular y de una pantalla OLED, externos a esta placa. 4.2.1 Placa Bee 2.0 Como punto de partida en el proyecto, se han estudiado los componentes y el funcionamiento de los distintos elementos hardware de la placa Bee 2.0, diseñada para la asignatura Arquitectura Interna de Linux y Android. La placa puede observarse en la figura 4.6. La idea de este proyecto es impulsar todo el potencial que ofrece esta placa, al utilizar un firmware de base que permite implementar cualquier funcionalidad utilizando todos los periféricos que la componen. Sus componentes son: un diodo LED RGB con sus correspondientes resistencias, tres pulsadores, tres diodos de color rojo (también interconectados mediante resistencias, para evitar posibles daños), un display 7 segmentos, un zumbador buzzer pasivo, conversores ADC (analógico-digital) y DAC (digital-analógico), y salida UART (puerto serie). Dispone de varios pines para poder conectar periféricos externos, así como dos salidas de voltaje a 3.3V y 5V. Originalmente, esta placa se utiliza junto a una Raspberry PI en la asignatura de LIN, utilizando para el desarrollo de sus prácticas los siguientes periféricos: Display 7 segmentos, zumbador buzzer, los tres diodos y un pulsador. El utilizar conjuntamente esta placa con nuestro firmware posibilita la elaboración de prácticas con cualquier periférico de los mencionados anteriormente, y no sólo limitando su funcionalidad a estos últimos. 31 Figura 4.6: Placa Bee 2.0 de la asignatura Arquitectura interna de Linux y Android [3] 32 4.2.2 Anillo de LEDs de colores Este periférico (externo a la placa Bee 2.0) cuenta con 12 LEDs conectados en serie dispuestos de forma circular, con comunicación unidireccional. La empresa encargada de su diseño es NeoPixel [34]. Dichos LEDs están compuestos microcontrolador y un registro asociado a cada uno, con colores RGB direccionables individualmente. Cada controlador está compuesto por un registro especial, que gestiona la configuración de color e intensidad de cada LED. El dispositivo, probado originalmente con ATTiny85, se puede observar en la figura 4.7: Figura 4.7: Tira de LEDs circular de NeoPixel Para transmitir información de color e intensidad a cada LED, se utiliza una sola línea de datos en todo el dispositivo. Esta línea de datos usa un protocolo de comunicación específico llamado One-Wire Protocol. Mediante este protocolo, se puede implementar funcionalidad para poder iluminar todos los LEDs del anillo a la vez, o crear una serie de patrones para que se vayan encendiendo en distinto orden y con distintos colores. La tira circular de LEDs se alimenta con una fuente de alimentación de 5V que proporciona la propia placa a la que está conectada. El dispositivo está gestionado por el microcontrolador de la placa a la que está conectado. 4.2.2.1 Smart Shift Register en los LEDs de NeoPixel Cada LED RGB individual contiene un un pequeño microcontrolador y un registro. Dicho microcontrolador es el responsable de controlar el color y el brillo, y de comunicarse con los otros LEDs de la cadena. Todos los chips que componen cada LED de este dispositivo utilizan el protocolo One-Wire para su comunicación, y tienen un componente clave llamado Smart Shift Register, que se explica a continuación. Cada microcontrolador se comporta como registro de desplazamiento, recibiendo los bits por el pin DIN y sacando los bits de acarreo por el pin DOUT. La arquitectura de 33 Figura 4.8: Arquitectura de cada LED [35] cada LED se puede observar en la figura 4.8. De este modo, al recibir datos por uno de los pines, los bits que salen son los transmitidos al siguiente controlador de la cadena. Cada registro Smart Shift Register está compuesto por los siguientes elementos: • Bit de control de datos: Este bit está dedicado a controlar la transferencia de datos. Mediante este bit, el controlador puede gestionar la información sobre configuración de colores e intensidad recibida. • Registros de color: Dentro de cada Smart Shift Register, existen subregistros dedicados a cada color (R-G-B). Su función es almacenar los valores de brillo y determinar el color resultante del LED. • Protocolo de comunicación: El registro está orquestado por un protocolo llamado 1-Wire (One-Wire), que rige una secuencia de bits específica para determinar los datos. • Sincronización: Su implementación permite operar con la señal de reloj del microcontrolador principal, para evitar fallos y desfases en la comunicación. • Multiplexación/Almacenamiento: Este registro también se encarga de transmitir los bits de cada LED secuencialmente en la cadena a la siguiente componente, almacenando la información y transmitiéndola en el instante adecuado. Cuando el smart shift register recibe la información en forma de bits, se encarga de ajustar los registros internos para poder establecer los colores del LED en cuestión. Este proceso se repite para los 12 LEDs que componen el anillo circular, como se puede observar en la figura 4.9. Cada registro en la cadena de LEDs cuenta con una posible configuración de 24 bits, agrupándose en grupos de 8 bits recogidos en 3 data latches o registros, que el controlador 34 Figura 4.9: Flujo de bits dentro del controlador de cada LED [35] interpreta para ajustar el color o brillo de cada LED (se utiliza un grupo de 8 bits para controlar el color rojo, otro grupo de 8 bits para el color verde y otro grupo de 8 bits para el color azul). Cuando se han recibido todos los datos, el microcontrolador de cada LED se encarga de excitarlo para iluminarlo. Cuando el registro de desplazamiento recibe la señal de reset, quiere decir que se pueden iluminar los LEDs al haber terminado el envío a toda la cadena. A partir de ese momento, se empieza otra secuencia de datos. [35] 4.2.2.2 Protocolo One-Wire El protocolo One-Wire (o también conocido como 1-Wire) permite la transmisión de 1’s o 0’s en cadena, pudiendo transmitir la información a cada LED contando con una única línea de datos. El funcionamiento de este protocolo es el siguiente: para poder determinar el dato, se mide los tiempos de transmisión entre el flanco de subida y bajada. Si el tiempo entre el flanco de subida y el flanco de bajada es mayor que el tiempo transcurrido hasta el siguiente flanco de subida, el resultado es un voltaje alto (TH) o un 1 lógico. Por el contrario, si el tiempo entre el primer flanco de subida y el flanco de bajada es menor que el tiempo transcurrido hasta el el siguiente flanco de subida, el resultado es un voltaje bajo (LW) o un 0 lógico. En la figura 4.10 se ilustra este funcionamiento. Figura 4.10: Cálculo de cada dato según los flancos de subida o bajada en 1-Wire [35] 35 4.2.3 Display 7 segmentos Este periférico se encuentra en la placa Bee 2.0, es capaz de representar un número del 0 al 9 mediante segmentos verticales y horizontales que, según el número a mostrar, se encienden o apagan mostrando el número en cuestión. También cuenta con un punto en la parte inferior derecha. La configuración hardware de este display es de cátodo común, es decir, cuando se escribe un 1 se enciende el segmento deseado, y cuando se escribe un 0, se apaga. Figura 4.11: Diagrama del circuito del display 7 segmentos de la placa Bee 2.0 [36] La figura 4.11 ilustra el circuito interno del display 7 segmentos. Como se puede observar, cuenta con dos registros, controlados por 3 pines de la placa Bee 2.0. Esto es una enorme ventaja, ya que si tuviéramos 8 pines de entrada para el display, limitaría la posible interacción con otros dispositivos de E/S, al tener que estar constantemente enviando un 1 lógico en aquellos pines correspondientes a los segmentos que se quieren encender. Para ello, el sistema emplea dos registros, uno de desplazamiento y otro de salida, que se explican a continuación. Entradas del registro de desplazamiento: • SDI (Serial Data Input): es la entrada serie del registro, de 8 bits. En cada ciclo de reloj se propaga un bit que controla el estado de un segmento distinto. El 36 bit menos significativo corresponde al punto, el más significativo al segmento horizontal superior. • SRCLK (Shift Register Clock): señal del reloj correspondiente al registro de desplazamiento. • Enable. Esta señal debe estar a 1 para que el sistema realice la acción de desplazamiento y carga paralela, mediante los dos registros. Entradas del registro de salida: •RCLK (Register Clock): señal del reloj correspondiente al registro de salida. • Load. Al igual que la señal de enable, tiene que estar a 1 para el sistema realice las acciones de desplazamiento y carga paralela. Para poder generar la salida deseada, con establecer un 1 en la señal de RCLK (que debe estar a 0 durante todo el proceso previo), se cargaría inmediatamente el número en el registro, y por lo tanto, en la salida de los segmentos del display. La alteración de las señales SDI, SRCLK y RCLK se realiza mediante bit banging, es decir, es la propia CPU la que establece el valor del pin de forma temporizada permitiendo que la señal se mantenga un cierto tiempo en el valor lógico deseado. [3] 4.2.4 Pantalla LCD OLED Este periférico es externo a la paca Bee 2.0, consta de una pantalla LCD tipo OLED (formado por un cristal líquido), en el que se permite mostrar cadenas de texto. Para poder ser gestionada mediante el microcontrolador principal, se utiliza el protocolo I2C [37]. El dispositivo en cuestión es el mostrado en la figura 4.12: Figura 4.12: Pantalla OLED utilizada en el proyecto El protocolo I2C permite la comunicación serie mediante dos cables para transferencias 37 entre el microcontrolador (o maestro) y el periférico (o esclavo). Para poder establecer la comunicación, es necesario tener en cuenta los siguientes puntos en la conexión y comunicación utilizando I2C: • Conexión: En nuestro caso, para permitir transferencias de información a la pantalla, se deben conectar las salidas I2C del dispositivo a la placa NANO, mediante las líneas SDA (Serial Data Line) y SCL (Serial Clock Lline). En nuestro caso, ATmega328p será el maestro, y la pantalla OLED será el esclavo. • Reloj (SCL): Esta línea se utiliza para sincronizar las transferencias entre el microcontrolador y el periférico, generando las correspondientes señales de reloj. Estas señales son gestionadas por el maestro, ya que es quién determina la velocidad de transferencia de datos. • Línea de datos (SDA): Los datos se transmiten de forma bidireccional entre maestro y esclavo (cada componente puede enviar y recibir). Las transmisiones se dividen en secuencias, formadas por paquetes de datos. • Dirección I2C del dispositivo: La pantalla OLED debe tener asignada una dirección única, para poder ser reconocido por el microcontrolador y comunicarse con él. • Comandos/datos: Para enviar información a la pantalla, se realiza a través de la línea SDA mediante comandos y datos. Estos comandos configuran, entre otras cosas, el brillo y contraste. Los datos sirven para transmitir información que la pantalla mostrará. • Flujo de datos en I2C: Para iniciar la comunicación, el maestro envía una señal de inicio o start junto a la dirección asignada a la pantalla. Después, se envían los comandos correspondientes y los datos al periférico. El esclavo, una vez recibida la información, responde con un ACK para confirmar la recepción. Una vez finalizada la comunicación, el maestro envía una señal de stop, indicando el fin de la transmisión. 4.2.5 Zumbador pasivo Buzzer Este periférico se encuentra en la placa Bee 2.0, utiliza una señal PWM para su funcionamiento. PWM es una técnica que requiere de la manipulación del ancho de una señal periódica, para gestionar la energía que se envía al dispositivo. Para poder hacer sonar el buzzer, se requiere enviar pulsos de señal a una frecuencia constante. El hecho de modificar la amplitud de las ondas crea diferentes niveles de volumen. En el microcontrolador, existen registros específicos para general la señal PWM. Se debe configurar un timer adecuado, estableciendo la frecuencia de conteo y el modo de funcionamiento. Aplicado al ATmega328p, existe un módulo en el chip que se encarga de generar estas señales PWM. El encargado de generar la señal periódica es el propio timer, para ello se debe configurar una frecuencia adecuada, ya que sino el volumen podría ser muy bajo o muy alto. Para hacer sonar el buzzer, en cada ciclo se comienza con un 1 en la salida PWM, y se conmuta a 0 cuando el valor del contador del timer baja de un determinado umbral. Configurando este umbral, se establece el duty cycle, 38 seleccionando así el volumen/tono emitido por el zumbador. Para conectar el buzzer de la placa Bee 2.0 a la placa NANO, se debe unir la salida positiva de la placa Bee 2.0 (PWM1) a uno de los pines PWM de la placa NANO, y las dos salidas de tierra (GND). 39 40 41 } 42 43 return 0;/* return no data back to host */ 44 } 5.3.2 Funciones usbFunctionRead yusbFunctionWrite Como se mencionaba en la sección anterior, estas funciones son llamadas por el core de V-USB cuando en la función usbFunctionSetup() se devuelve el valor USB_NO_MSG. Estas funciones se llaman con 8 bytes de datos que residen dentro del buffer que se envía como argumento junto con la longitud del mismo. La longitud total de los datos de la transferencia en curso se puede encontrar en el campo wLenght accesible desde la función de setup. De esta manera podemos calcular cuántos bytes nos quedan por leer y así acabar la transacción o si por el contrario tenemos que esperar más datos, esto es útil para lecturas de más de 8 bytes. Si durante la ejecución de la función se produce algún error debemos retornar el valor -1, que se convertirá en un paquete STALL, dicho paquete indica al host que ha habido un error del que no ha sido posible recuperarse durante la transferencia y se ha abortado. En la función de lectura, cuando se quieren seguir leyendo datos se debe devolver el valor 0, en caso de haber finalizado se devolverá 1. Algo similar se ha de hacer en la función de escritura, pero en este caso siempre se debe devolver 1 cuando la transferencia haya finalizado con éxito. El fragmento de código que viene a continuación, pertenece a la operación de lectura e ilustra de manera más clara como trabajan dichas funciones: 1uchar usbFunctionRead(uchar *data, uchar len) { 2if (reportId == 1) { 3if(len > bytesRemaining) 4len = bytesRemaining; 5 6if (currentAddress == 0) { 7memcpy(&data[1], &MSG[currentAddress], len); 8currentAddress += (len - 1); 9bytesRemaining -= (len - 1); 10 }else { 11 memcpy(data, &MSG[currentAddress], len); 12 currentAddress += len; 13 bytesRemaining -= len; 14 } 15 16 return len; 17 } 18 19 return 0; 20 } 46 Es importante recalcar que ambas funciones deben de ser previamente definidas por V-USB y para ello hay que activar dos macros que haran disponible la cabecera para su implementación, USB_CFG_IMPLEMENT_FN_WRITE yUSB_CFG_IMPLEMENT_FN_READ. 5.4 usbconfig.h El fichero usbconfig.h proviene del proyecto V-USB, es obligatorio incluirlo ya que contiene toda la configuración del dispositivo a emular. Cuando comenzamos la fase de codificación tuvimos que refactorizarlo de forma sustancial, ya que dependiendo de los valores definidos se creará un dispositivo diferente. Este fichero contiene numerosas macros que controlan diversos aspectos de la configuración hardware, ya que como comentamos en otro punto de esta memoria, V-USB soporta muchos tipos de microcontroladores y es ampliamente configurable, pero en esta sección repasaremos algunos de los más importantes. Un conector USB sencillo tiene 4 líneas, 2 encargadas de la alimentación y otras dos de los datos. En este fichero se definen el puerto y los pines del microcontrolador responsables de este control. •USB_CFG_IOPORTNAME , este parámetro define el puerto al que se conectan las líneas de datos. •USB_CFG_DMINUS_BIT , este parámetro define el pin del microcontrolador donde está conectada la línea negativa del conector. •USB_CFG_DPLUS_BIT , en este se define el pin donde se conecta la línea positiva del conector. 1#define USB_CFG_IOPORTNAME D 2#define USB_CFG_DMINUS_BIT 4 3#define USB_CFG_DPLUS_BIT 2 La frecuencia a la que trabaja el microcontrolador también es un parámetro clave, ya que esta implementación de USB es emulada por software la frecuencia debe de ser muy estricta para cumplir con los tiempos de la comunicación. Se define en la macro USB_CFG_CLOCK_KHZ. Como hemos visto anteriormente, el dispositivo que hemos implementado pertenece a la clase HID, que debe de cumplir algunos requisitos concretos. Estos requisitos los definimos en las siguientes macros: •USB_CFG_HAVE_INTRIN_ENDPOINT , como muestra la línea 1 del fragmento de código, esta macro nos permite activar un endpoint de tipo INTERRUPT IN, necesario en HID. •USB_CFG_INTERFACE_CLASS , otro punto muy importante es definir la clase del 47 dispositivo. Como en nuestro caso se trata de HID, esta macro llevará el valor 3. En la línea 6 del fragmento de código puede se muestra claramente como se define el tipo de clase. 1#define USB_CFG_HAVE_INTRIN_ENDPOINT 1 2/* 3Define this to 1 if you want to compile a version with two endpoints: 4The default control endpoint 0 and an interrupt-in endpoint ... 5*/ 6#define USB_CFG_INTERFACE_CLASS 3 7/* 8See USB specification ... 9HID class is 3, no subclass and protocol required (but may be useful!) 10 ... 11 */ Otros dos valores muy importantes en la implementación de un dispositivo USB son el ProductID y VendorID, ya que sin ellos sería imposible asociar un dispositivo con un driver. Por ello, dentro de este fichero podemos definirlos bajo las macros: USB_CFG_VENDOR_ID yUSB_CFG_DEVICE_ID. Por último, otros dos valores interesantes de configurar son el nombre del fabricante y del dispositivo, que posteriormente serán mostrados una vez el host reconozca el dispositivo. Podemos hacer esto dándole valor a las siguientes macros: USB_CFG_VENDOR_NAME y USB_CFG_DEVICE_NAME. 1/* -------------------------- Device Description --------------------------- */ 2#define USB_CFG_VENDOR_ID 0xA0, 0x20 3#define USB_CFG_DEVICE_ID 0xe5, 0x41 4#define USB_CFG_VENDOR_NAME 'x', 'G', 'G', 'C' 5#define USB_CFG_DEVICE_NAME 'p', 'w', 'n', 'e', 'd', 'D', 'e', 'v', 'i', 'c', 'e' 5.5 utils.h En este fichero se implementan diferentes funciones que nos permiten controlar los componentes más básicos como pueden ser los diodos leds y el buzzer. Aquí también se trabaja con los timers que nos ofrece el hardware en las funciones blinkPWM() y harwarePWMBeep() , que son las encargadas de empezar a parpadear el led con frecuencia de 1 segundo y de generar una señal PWM con la frecuencia indicada por parámetro, respectivamente. 5.6 Otras librerías usadas Durante el proyecto, hemos usado también dos librerías de la comunidad que nos han permitido integrar en el software tanto el anillo de leds como el display de tipo OLED. 48 Estas librerías son: •oled/ , se encarga de controlar el display OLED mediante el protocolo de comunicación i2c que solo usa dos líneas de datos para la transmisión de la información. [38] •light_ws2812/ , esta librería implementa el protocolo one-wire que nos permite controlar el anillo de led. [39] 5.7 Depuración del código del microcontrolador La depuración en microcontroladores de este tipo puede llegar a ser todo un reto. Tenemos que tener en cuenta que un chip de esta clase no es más que una diminuta compleja pieza de hardware que ejecuta el código que previamente le ha sido cargado en una memoria flash, siendo imposible trazarlo como se podría hacer en otro entorno más depurable como puede ser GDB. Por ello, hay que buscar maneras auxiliares de depuración. En los inicios utilizabamos el parpadeo de un LED para indicar que el procesador estaba ejecutando ciertas partes del código, pero por temas de temporización esta manera no era del todo efectiva, además de la poca información que daba. Con el cambio de chip al ATMega328p pudimos hacer uso de la interfaz UART que lleva la placa que elegimos y establecer una línea de comunicación por puerto serie con el dispositivo. V-USB nos ofrece una interfaz de depuración simple pero efectiva en el fichero oddebug.c con la que podemos aprovechar este hardware, esencialmente pone a funcionar un dispositivo serie por el que posteriormente expondremos cadenas de caracteres que usaremos para debuggear el código. A este fichero añadimos una función que nos resultó muy cómoda para poder imprimir cadenas de caracteres y es la función int odPrintf(char *fmt, ...) . Esta función nos permite escribir por el puerto serie cadenas de caracteres con formato, usando los mismos placeholders que printf() . De esta manera, pudimos incluir trazas distribuidas por el código para ver los diferentes flujos de ejecución, así como el valor de determinadas variables, de manera muy sencilla como muestra el siguiente código: 1odDebugInit(); 2odPrintf("V-USB device initializing... "); 3... 4odPrintf("OK!\n"); 49 50 Capítulo 6 Drivers y casos prácticos En este capítulo se describe el paquete de drivers desarrollados como ejemplo para este proyecto. Ya que este material tiene una finalidad docente y de aprendizaje, el funcionamiento de los drivers es sencillo, pero no por ello simple de implementar. Se ofrecen 5 drivers los cuales hacen uso de nuevas funciones y tipos de datos de la API que nos proporciona USB core, además de un driver específico que hace uso de la API asíncrona junto con transferencias tipo INTERRUPT IN. Con estos drivers se pretende que los usuarios puedan probar y experimentar el funcionamiento de las utilidades USB que nos ofrece el kernel Linux, trabajando con ambas APIs y usando diferentes tipos de transferencias. A continuación se describe a modo de introducción la API de USB del kernel de Linux, después se explica con detalle cada uno uno de ellos explicando su funcionamiento y arquitectura interna, con ejemplos de código. 6.1 Drivers USB en el kernel Linux Un componente muy importante en los drivers USB es la definición de la interfaz usb_driver [40], que debe implementar cualquier módulo para poder registrarse y administrar controladores que interactuen con periféricos USB. Esta estructura viene definida en <linux/usb.h> , y se deben implementar aquellos campos que requieran definir las funciones que interactúen con el dispositivo USB. Lo hemos definido como interfaz ya que muchos de sus campos son punteros a función. Estructuras similares son file_operations yproc_ops, de las que se hablarán más adelante. 1struct usb_driver { 2const char *name; 3int (*probe) (struct usb_interface *intf, 4const struct usb_device_id *id); 5void (*disconnect) (struct usb_interface *intf); 6int (*unlocked_ioctl) (struct usb_interface *intf, unsigned int code, 51 7void *buf); 8int (*suspend) (struct usb_interface *intf, pm_message_t message); 9int (*resume) (struct usb_interface *intf); 10 int (*reset_resume)(struct usb_interface *intf); 11 12 int (*pre_reset)(struct usb_interface *intf); 13 int (*post_reset)(struct usb_interface *intf); 14 15 const struct usb_device_id *id_table; 16 const struct attribute_group **dev_groups; 17 18 struct usb_dynids dynids; 19 struct usbdrv_wrap drvwrap; 20 unsigned int no_dynamic_id:1; 21 unsigned int supports_autosuspend:1; 22 unsigned int disable_hub_initiated_lpm:1; 23 unsigned int soft_unbind:1; 24 }; Como se puede observar en la estructura struct usb_driver , hay diversos campos para poder implementar funciones que doten de funcionalidad al driver, o simplemente para configuración. No es necesario implementar todas las entradas, solamente aquellas que se requieran para garantizar el funcionamiento mínimo del dispositivo (como ya ocurre con la gestión de las entradas en /proc ). Las entradas más importantes que los drivers USB deben implementar son las siguientes: •name, línea 2: Cadena de caracteres, representa el nombre del driver. •id_table , línea 15: Tabla de dispositivos compatibles con el driver, definidos mediante un vendor-ID yproduct-ID. •probe , línea 3: Este campo define la función encargada de ejecutarse cuando se detecta un dispositivo compatible con el controlador USB al conectarlo al host, se encarga de inicializarlo y prepararlo para su uso. Se pasa como parámetro a esta función el descriptor del dispositivo, de tipostruct usb_interface *. •disconnect , línea 5: Esta función se ejecuta cuando se desconecta el dispositivo USB que previamente ha sido gestionado por el driver. En nuestros drivers, definimos cuatro campos básicos de la interfaz usb_driver , como se muestra a continuación: 1static struct usb_driver pwnedDevice_driver = { 2.name = "pwnedDevice", 3.probe = pwnedDevice_probe, 4.disconnect = pwnedDevice_disconnect, 5.id_table = pwnedDevice_table, 6}; En la estructura usb_driver definimos punteros a función en .probe y .disconnect (lí52 neas 3 y 4) para implementar las funciones pwnedDevice_probe() y pwnedDevice_disconnect() : 1static int pwnedDevice_probe(struct usb_interface *interface, 2const struct usb_device_id *id) { 3struct usb_pwnedDevice *dev; 4int retval = -ENOMEM; 5 6/* 7* Allocate memory for a usb_pwnedDevice structure. 8* This structure represents the device state. 9* The driver assigns a separate structure to each pwnedDevicestick device 10 * 11 */ 12 dev = kmalloc(sizeof(struct usb_pwnedDevice), GFP_KERNEL); 13 14 if (!dev) { 15 dev_err(&interface->dev, "Out of memory\n"); 16 goto error; 17 } 18 19 /* Initialize the various fields in the usb_pwnedDevice structure */ 20 kref_init(&dev->kref); 21 dev->udev = usb_get_dev(interface_to_usbdev(interface)); 22 dev->interface = interface; 23 24 /* save our data pointer in this interface device */ 25 usb_set_intfdata(interface, dev); 26 27 /* we can register the device now, as it is ready */ 28 retval = usb_register_dev(interface, &pwnedDevice_class); 29 if (retval) { 30 /* something prevented us from registering this driver */ 31 dev_err(&interface->dev, 32 "Not able to get a minor for this device.\n"); 33 usb_set_intfdata(interface, NULL); 34 goto error; 35 } 36 37 /* let the user know what node this device is now attached to */ 38 dev_info(&interface->dev, 39 "PwnedDevice now available via pwnedDevice%d", 40 interface->minor); 41 return 0; 42 43 error: 44 if (dev) 45 /* this frees up allocated memory */ 46 kref_put(&dev->kref, pwnedDevice_delete); 47 return retval; 48 } Como hemos mencionado previamente, esta función es la encargada de reservar memoria 53 e inicializar los campos de la estructura del dispositivo, como podemos observar en el código anterior entre las líneas 12 y 22. Destacar que a esta función tiene como parámetro el descriptor del dispositivo, que se utilizará para poder registrarlo posteriormente. La función invocada cuando se desconecta el dispositivo previamente gestionado por el driver es la siguiente: 1static void pwnedDevice_disconnect(struct usb_interface *interface) { 2struct usb_pwnedDevice *dev; 3int minor = interface->minor; 4 5dev = usb_get_intfdata(interface); 6usb_set_intfdata(interface, NULL); 7 8/* give back our minor */ 9usb_deregister_dev(interface, &pwnedDevice_class); 10 11 /* prevent more I/O from starting */ 12 dev->interface = NULL; 13 14 /* decrement our usage count */ 15 kref_put(&dev->kref, pwnedDevice_delete); 16 17 dev_info(&interface->dev, "PwnedDevice device #%d has been disconnected", minor); 18 } En la última función, en la línea 15, se decrementa el contador de referencias del dispositivo. Puede llegar a eliminarlo si el valor de este contador es 0. Esta función también es encargada de invalidar la interfaz del dispositivo. A continuación se muestran las funciones encargadas de la inicialización del módulo pwnedDevicedrv_module_init() y de eliminar su registro mediante la función pwnedDevicedrv_module_cleanup(). 1/* Module initialization */ 2int pwnedDevicedrv_module_init(void) { 3return usb_register(&pwnedDevice_driver); 4} 5 6/* Module cleanup function */ 7void pwnedDevicedrv_module_cleanup(void) { 8usb_deregister(&pwnedDevice_driver); 9} 10 11 module_init(pwnedDevicedrv_module_init); 12 module_exit(pwnedDevicedrv_module_cleanup); Para llevar a cabo el registro del módulo, se invoca la función usb_register() en la línea 3, que acepta como parámetro un puntero a la interfaz usb_driver , definida mediante la variable pwnedDevice_driver . En el caso de que se produzca correctamente el registro, se devuelve 0. En caso contrario, un número negativo con el código de error. 54 Para la descarga del módulo, se ejecuta usb_deregister() en la línea 8, que se encarga de la limpieza del módulo, aceptando un puntero a la interfaz usb_driver. En la instanciación de usb_driver , a parte de la definición de los campos explicados previamente, se ha definido otro para los dispositivos compatibles ( .id_table = pwnedDevice_table , línea 5 en la estructura definida al comienzo de la sección). La implementación de esta tabla es la siguiente: 1/* Define these values to match your devices */ 2#define VENDOR_ID 0X20A0 3#define PRODUCT_ID 0X41E5 4 5/* table of devices that work with this driver */ 6static const struct usb_device_id pwnedDevice_table[] = { 7{ USB_DEVICE(VENDOR_ID, PRODUCT_ID) }, 8{ } /* Terminating entry */ 9}; 10 MODULE_DEVICE_TABLE(usb, pwnedDevice_table); La tabla pwnedDevice_table[] definida en la linea 6 del código anterior almacena todos los identificadores de dispositivos usb_device_id que el driver puede gestionar. Cada posición del array es un descriptor del dispositivo, que se inicializa mediante la macro USB_DEVICE() definida en la línea 7, cuyos parámetros son el identificador del fabricante VENDOR_ID y el identificador del producto PRODUCT_ID , definidos en las líneas 2 y 3. Cada vez que un dispositivo se conecta al host con los valores de VENDOR_ID y PRODUCT_ID definidos anteriormente, se consulta la tabla pwnedDevice_table[] que contiene esta información y posteriormente se ejecuta la función probe() del driver USB (en nuestro caso pwnedDevice_probe()). Otro de los campos importantes de un driver es la estructura de estado, ya que con esta estructura se permite la conexión simultánea de varios dispositivos del mismo tipo (asociando a cada uno una estructura que represente su estado). En nuestros drivers, hemos definido la siguiente estructura: 1/* Structure to hold all of our device specific stuff */ 2struct usb_pwnedDevice { 3struct usb_device *udev; /* the usb device for this device */ 4struct usb_interface *interface; /* the interface for this device */ 5struct kref kref; 6}; A continuación se explican los distintos campos de la estructura: •udev , línea 3: Es una referencia al descriptor del dispositivo físico USB, que es una estructura de datos utilizada por las funciones principales del USB para transferir datos entre el host y el dispositivo USB. Este puntero se encuentra en la mayoría de las estructuras de estado de los controladores USB y se obtiene durante la ejecución de la función probe() , donde se crea e inicializa la estructura de estado 55 ser una función no bloqueante no esperamos a que llegue. El resultado de esta solicitud de datos al dispositivo se retorna a través de la función de callback, que se invoca una vez el URB vuelve desde el dispositivo al host. La cabecera de la función es la siguiente: pwnedDevice_int_in_callback(struct urb *urb), función explicada más adelante. Como este driver está programado usando la API asíncrona que nos ofrece el kernel, toda la gestión de URBs, así como el descubrimiento de endpoints y todo el control de transferencia es gestionado por el driver. A continuación, veremos varios puntos relevantes del código que marcan una diferencia en el uso de esta API y del tipo de transferencia INTERRUPT. 6.2.1 Descubrimiento de endpoints El descubrimiento de endpoints es de lo primero que se hace cuando un driver ha sido asociado a un dispositivo, esta asociación se hace mediante el ProductID yVendorID. Cuando el USB Core asigna el driver al dispositivo lo primero que se ejecuta es la función pwnedDevice_probe() , que ejecuta todas las tareas necesarias de inicialización, entre ellas el descubrimiento de endpoints, que se realiza entre las líneas 28 y 47. 1static int pwnedDevice_probe(struct usb_interface *interface, 2const struct usb_device_id *id); Como argumento de esta función pwnedDevice_probe() recibimos una descripción de la interfaz que está siendo conectada con el driver, en ella podemos ver los diferentes tipos de descriptores USB que muestran las características del dispositivo. Dentro de estos descriptores podemos llegar a los descriptores de interfaces donde buscaremos un endpoint de tipo INTERRUPT y en sentido IN, en caso de no encontrarse el driver no puede funcionar y se aborta la inicialización. 6.2.2 Reserva de URBs Dentro de la función pwnedDevice_probe() también se realiza la reserva de memoria para el URB que se va a enviar al dispositivo para ser rellenado con información, tal y como viene definido entre las líneas 51 y 59 de dicha función. Esta acción se hace a través de la función usb_alloc_urb() (línea 51) que devuelve un puntero a una región de la memoria que se ha destinado al URB solicitado. Esta región será reutilizada y compartida con la controladora USB durante toda la vida del driver. Cabe destacar que es único por dispositivo, ya que el driver soporta varios dispositivos a la vez, cada uno de ellos reservará su propio URB. 6.2.3 Montaje de un URB Cuando el driver recibe una operación tipo read() , en la función de lectura pwnedDevice_read() se rellena el URB reservado previamente, y se lleva a la 62 controladora USB para que sea enviado al dispositivo, tal y como viene definido en el código de dicha función entre las líneas 13 y 21. Los datos introducidos al URB mediante la función que nos aporta el kernel usb_fill_int_urb() (línea 13), la cual tiene como parámetros la información que queremos hacerle llegar, así como otros diferentes pero obligatorios, y un buffer que rellenará el dispositivo con los datos que le solicitamos. Una vez todos los datos están correctos, tenemos que llamar a la función usb_submit_urb() (línea 21) para decirle a la controladora que ese URB está listo para ser enviado y proceder a ello. 6.2.4 Gestión de la respuesta Al usarse la API asíncrona después del envío que realiza el driver, no hay ningún bloqueo que sirva de espera a la respuesta del dispositivo, es por esto que la respuesta se gestiona desde una función externa que es llamada cuando el URB vuelve relleno. Esta función es pwnedDevice_int_in_callback(). Dentro de la función se realizan dos tareas sencillas. Lo primero es comprobar el estado de la transferencia, ya que en caso de haberse producido algún fallo los datos devueltos puede que no sean coherentes. Esta tarea se realiza entre las líneas 7 y 14 del siguiente código. En caso de que todo haya ido bien, se extraen los datos del buffer que previamente se reservó para rellenarse con información por el dispositivo, y se muestra por la salida de información del kernel usando printk(), tarea realizada en las líneas 16 y 17. 1/* 2* Callback function. Executed when INT IN URB returns back with data. 3*/ 4static void pwnedDevice_int_in_callback(struct urb *urb) { 5struct usb_pwnedDevice *dev = urb->context; 6 7if (urb->status) { 8if (urb->status == -ENOENT || 9urb->status == -ECONNRESET || 10 urb->status == -ESHUTDOWN) { 11 printk(KERN_INFO "Error on callback!"); 12 return; 13 } 14 } 15 16 if (urb->actual_length > 0) 17 printk(KERN_INFO "Transfered data: %s\n", dev->int_in_buffer); 18 } Este tipo de dispositivo tiene la limitación de que el tamaño máximo de transferencia mediante INTERRUPT es de 8 bytes, esta limitación la impone el estándar USB para dispositivos Low-Speed. 63 6.3 DisplaysDriver Este driver ha sido desarrollado para interactuar con los periféricos que muestran al usuario información a través de pantallas. En este momento están soportados por el firmware el display de 7 segmentos y la pantalla OLED, por lo que el driver es capaz de enviar información a ambos. El driver utiliza la API síncrona que nos ofrece USB Core y todas las transferencias van sobre el tipo CONTROL. Este driver y los posteriores comparten las mismas funciones de inicialización, cambiando las funciones para operaciones de read() ywrite(). Una vez cargado el módulo que hace de driver en el kernel, este expone un dispositivo de caracteres bajo la ruta /dev/usb/displaysx , donde xhace referencia al índice del dispositivo que se encuentra conectado, ya que el driver soporta más de un dispositivo a la vez. 6.3.1 Operación de lectura Cuando el dispositivo recibe una llamada read() , independientemente del origen, se ejecuta la función displays_read() . Como el perfil DISPLAYS no implementa ninguna información útil de lectura sobre sus periféricos, cuando una operación de este tipo llega al dispositivo devuelve una frase de 33 bytes a modo de respuesta. El mensaje llega a través del URB que se recibe en la función usb_control_msg_recv() de la línea 23, copiándose al espacio de usuario de forma segura mediante la llamada copy_to_user() de la línea 41. 1static ssize_t pwnedDevice_read(struct file *file, char *user_buffer, 2size_t count, loff_t *ppos) { 3struct usb_pwnedDevice *dev; 4int retval = 0; 5unsigned char* message; 6int nr_bytes = 0; 7unsigned int wValue = 0; 8int msgSize = 0; 9 10 dev = file->private_data; 11 12 if (*ppos>0) 13 return 0; 14 15 message = kmalloc(MAX_LEN_MESSAGE, GFP_DMA); 16 17 /* zero fill*/ 18 memset(message, 0, MAX_LEN_MESSAGE); 19 20 wValue = 0x1; 21 msgSize = 33; 22 64 23 retval = usb_control_msg_recv(dev->udev, 24 0, 25 USB_REQ_CLEAR_FEATURE, 26 USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE, 27 wValue, /* wValue = Descriptor index */ 28 0x0,/* wIndex = Endpoint # */ 29 message, /* Pointer to the message */ 30 msgSize, /* message's size in bytes */ 31 0,/* Dale lo necesario*/ 32 GFP_DMA); 33 34 if (retval) 35 goto out_error; 36 37 message[33]='\n'; 38 message[34]='\0'; 39 nr_bytes = strlen(message + 1); // Remove reportID 40 41 if (copy_to_user(user_buffer, message + 1, strlen(message + 1))){ 42 retval=-EFAULT; 43 goto out_error; 44 } 45 46 kfree(message); 47 (*ppos) += nr_bytes; 48 49 return nr_bytes; 50 51 out_error: 52 if (message) 53 kfree(message); 54 55 return retval; 56 } A continuación se muestra la salida de la shell cuando se ejecuta la operación de lectura sobre el fichero en /dev ~$ cat /dev/usb/displays0 Hello, World! I'm pwnedDevice ;) 6.3.2 Operación de escritura Las operaciones de escritura se ejecutan cuando el dispositivo recibe una llamada write() , esto dispara dentro del driver la ejecución de la función displays_write() que puede funcionar de diferente manera según los argumentos que reciba. En caso de recibir como datos el comando oled seguido de una frase, esta frase será enviada al dispositivo mediante el ReportID 3 y automáticamente será mostrada en la pantalla OLED (línea 28). Por el contrario, si como datos de la llamada viene el 65 comando 7s seguido de un dígito en hexadecimal, este será enviado al display de 7 segmentos (línea 32). 1static ssize_t pwnedDevice_write(struct file *file, const char *user_buffer, 2size_t count, loff_t *ppos) { 3 4struct usb_pwnedDevice *dev; 5int retval = 0; 6unsigned int digit = 0; 7char* strcfg = NULL; 8unsigned int wValue = 0; 9int dataSize = 0; 10 char *buffer; 11 12 dev = file->private_data; 13 14 if ((strcfg=kmalloc(count + 1, GFP_KERNEL)) == NULL) 15 return -ENOMEM; 16 17 if (copy_from_user(strcfg, user_buffer, count)){ 18 retval=-EFAULT; 19 goto out_error; 20 } 21 strcfg[count] = '\0'; 22 23 if ((buffer = kmalloc(MAX_LEN_MESSAGE, GFP_KERNEL)) == NULL) { 24 retval = -ENOMEM; 25 goto out_error; 26 } 27 28 if (strstr(strcfg, "oled ") != NULL) { 29 strcpy(buffer, strcfg + 5); // Remove oled command 30 dataSize = count - 5; 31 wValue = 3; 32 }else if (sscanf(strcfg,"7s %x", &digit) == 1) { 33 buffer[0] = 4;// ReportID 34 buffer[1] = digit; 35 dataSize = 2; 36 wValue = 4; 37 } 38 39 if (wValue != 0) { 40 retval = usb_control_msg(dev->udev, 41 usb_sndctrlpipe(dev->udev, 00), /* Specify endpoint #0 */ 42 USB_REQ_SET_CONFIGURATION, 43 USB_DIR_OUT| USB_TYPE_CLASS | USB_RECIP_INTERFACE, 44 wValue, /* wValue */ 45 0,/* wIndex=Endpoint # */ 46 buffer, /* Pointer to the message */ 47 dataSize, /* message's size in bytes */ 48 0); 49 } 66 50 51 if (retval < 0&& retval != -EPIPE){ 52 printk(KERN_ALERT "Executed with retval=%d\n",retval); 53 goto out_error; 54 } 55 56 goto ok_path; 57 58 ok_path: 59 kfree(strcfg); 60 kfree(buffer); 61 (*ppos)+=count; 62 return count; 63 64 out_error: 65 if (strcfg) 66 kfree(strcfg); 67 if (buffer) 68 kfree(buffer); 69 return retval; 70 } 6.4 LedPartyDriver LedPartyDriver es el driver USB encargado de la comunicación con los periféricos cuando el dispositivo se encuentra flasheado con el perfil LED_PARTY. Permite enviar comandos para controlar el anillo de led, ya sea el anillo completo o un led concreto. 6.4.1 Operación de lectura Cuando este driver recibe una operación de lectura, lanza una transferencia de tipo CONTROL al ReportID 2 del dispositivo y devuelve la configuración actual de colores del anillo LED. El dispositivo transfiere un buffer de 4 bytes de datos donde los 3 últimos son el color del anillo codificado en RGB. Con esta información, el driver construye la cadena, R: valor G: valor B: valor, y la devuelve al usuario (mediante la función sprintf() de la línea 38). 1static ssize_t ledParty_read(struct file *file, char *user_buffer, 2size_t count, loff_t *ppos) { 3 4struct usb_pwnedDevice *dev; 5int retval = 0; 6unsigned char* message; 7int nr_bytes = 0; 67 8unsigned int wValue = 0; 9int msgSize = 0; 10 11 dev = file->private_data; 12 13 if (*ppos>0) 14 return 0; 15 16 message = kmalloc(MAX_LEN_MESSAGE, GFP_DMA); 17 18 /* zero fill*/ 19 memset(message, 0, MAX_LEN_MESSAGE); 20 21 wValue = 0x2; 22 msgSize = 4; 23 24 retval = usb_control_msg_recv(dev->udev, 25 0, 26 USB_REQ_CLEAR_FEATURE, 27 USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE, 28 wValue, /* wValue = Descriptor index */ 29 0x0,/* wIndex = Endpoint # */ 30 message, /* Pointer to the message */ 31 msgSize, /* message's size in bytes */ 32 0,/* Dale lo necesario*/ 33 GFP_DMA); 34 35 if (retval) 36 goto out_error; 37 38 sprintf(message, "R: %d G: %d B: %d \n", message[1], message[2], message[3]); 39 nr_bytes = strlen(message); 40 41 if (copy_to_user(user_buffer, message, strlen(message))){ 42 retval=-EFAULT; 43 goto out_error; 44 } 45 46 kfree(message); 47 (*ppos) += nr_bytes; 48 49 return nr_bytes; 50 51 out_error: 52 if (message) 53 kfree(message); 54 55 return retval; 56 } Cuando se ejecuta la operación de lectura sobre el fichero en /dev , la salida que se 68 muestra es la siguiente: $ cat /dev/usb/ledParty0 R:0G:0B:0 6.4.2 Operación de escritura Este driver soporta dos comandos de escritura diferentes a través de la función write() . Cuando la función ledParty_write() es ejecutada con el comando setFullColor seguido de una combinación de color separada por dos puntos, R:G:B, se envía una transferencia de tipo CONTROL con el color indicado al ReportID 1 y el anillo cambia de color por completo, puede observarse esta implementación entre las líneas 28 y 35. Otro de los comandos disponibles es setLedColor (líneas de la 36 a la 44), seguido del número de LED a cambiar y el color, nLed:R:G:B. Este comando envía la información al ReportID 2, que cambia el led indicado por el color que recibe en la transferencia. 1static ssize_t ledParty_write(struct file *file, const char *user_buffer, 2size_t count, loff_t *ppos) { 3 4struct usb_pwnedDevice *dev; 5int retval = 0; 6int r,g,b,led = 0; 7char* strcfg = NULL; 8unsigned int wValue = 0; 9int dataSize = 0; 10 char *buffer; 11 12 dev = file->private_data; 13 14 if ((strcfg=kmalloc(count + 1, GFP_KERNEL)) == NULL) 15 return -ENOMEM; 16 17 if (copy_from_user(strcfg, user_buffer, count)){ 18 retval=-EFAULT; 19 goto out_error; 20 } 21 strcfg[count] = '\0'; 22 23 if ((buffer = kmalloc(MAX_LEN_MESSAGE, GFP_KERNEL)) == NULL) { 24 retval = -ENOMEM; 25 goto out_error; 26 } 27 28 if (sscanf(strcfg,"setFullColor %d:%d:%d", &r, &g, &b) == 3 29 && (r >= 0&& r <= 255) && (g >= 0&& g <= 255) && (b >= 0&& b <= 255)) { 30 buffer[0] = 1;// ReportID 31 buffer[1] = r; 32 buffer[2] = g; 33 buffer[3] = b; 69 34 dataSize = 4; 35 wValue = 1; 36 }else if (sscanf(strcfg,"setLedColor %d:%d:%d:%d", &led, &r, &g, &b) == 4 37 && (r >= 0&& r <= 255) && (g >= 0&& g <= 255) && (b >= 0&& b <= 255) && (led >= 0&& led <= LED_SIZE)) { 38 buffer[0] = 2;// ReportID 39 buffer[1] = led; 40 buffer[2] = r; 41 buffer[3] = g; 42 buffer[4] = b; 43 dataSize = 5; 44 wValue = 2; 45 } 46 47 if (wValue != 0) { 48 retval = usb_control_msg(dev->udev, 49 usb_sndctrlpipe(dev->udev, 00), /* Specify endpoint #0 */ 50 USB_REQ_SET_CONFIGURATION, 51 USB_DIR_OUT| USB_TYPE_CLASS | USB_RECIP_INTERFACE, 52 wValue, /* wValue */ 53 0,/* wIndex=Endpoint # */ 54 buffer, /* Pointer to the message */ 55 dataSize, /* message's size in bytes */ 56 0); 57 } 58 59 if (retval < 0&& retval != -EPIPE){ 60 printk(KERN_ALERT "Executed with retval=%d\n",retval); 61 goto out_error; 62 } 63 64 goto ok_path; 65 66 ok_path: 67 kfree(strcfg); 68 kfree(buffer); 69 (*ppos)+=count; 70 return count; 71 72 out_error: 73 if (strcfg) 74 kfree(strcfg); 75 if (buffer) 76 kfree(buffer); 77 return retval; 78 } 70 6.5 PWMDriver Este driver es compatible cuando el dispositivo se encuentra flasheado con la plantilla PWM. Esta plantilla expone un led y un zumbador capaces de ser controlados mediante señales PWM generadas por el microcontrolador, y el driver permite comunicar el host con estos dispositivos. 6.5.1 Operación de lectura Al igual que en DisplaysDriver, este módulo no dispone de información relevante de lectura desde el microcontrolador. Por ello cuando llega una operación read() a la función de pwm_read() , se envía una transferencia de tipo CONTROL al dispositivo que devuelve una cadena a modo de respuesta (tal y como se puede observar en la línea 24). 1static ssize_t pwm_read(struct file *file, char *user_buffer, 2size_t count, loff_t *ppos) { 3 4struct usb_pwnedDevice *dev; 5int retval = 0; 6unsigned char* message; 7int nr_bytes = 0; 8unsigned int wValue = 0; 9int msgSize = 0; 10 11 dev = file->private_data; 12 13 if (*ppos>0) 14 return 0; 15 16 message = kmalloc(MAX_LEN_MESSAGE, GFP_DMA); 17 18 /* zero fill*/ 19 memset(message, 0, MAX_LEN_MESSAGE); 20 21 wValue = 0x1; 22 msgSize = 33; 23 24 retval = usb_control_msg_recv(dev->udev, 25 0, 26 USB_REQ_CLEAR_FEATURE, 27 USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE, 28 wValue, /* wValue = Descriptor index */ 29 0x0,/* wIndex = Endpoint # */ 30 message, /* Pointer to the message */ 31 msgSize, /* message's size in bytes */ 32 0,/* Dale lo necesario*/ 33 GFP_DMA); 34 35 if (retval) 36 goto out_error; 71 91 buffer, /* Pointer to the message */ 92 dataSize, /* message's size in bytes */ 93 0); 94 } 95 96 if (retval < 0&& retval != -EPIPE){ 97 printk(KERN_ALERT "Executed with retval=%d\n",retval); 98 goto out_error; 99 } 100 101 goto ok_path; 102 103 ok_path: 104 kfree(strcfg); 105 kfree(buffer); 106 (*ppos)+=count; 107 return count; 108 109 out_error: 110 if (strcfg) 111 kfree(strcfg); 112 if (buffer) 113 kfree(buffer); 114 return retval; 115 } 78 Capítulo 7 Conclusiones El objetivo principal de este proyecto ha radicado en la construcción de una infraestructura hardware-software de bajo coste, entorno a unos 5€ por dispositivo, orientada al prototipado de dispositivos USB y al desarrollo de drivers para dichos dispositivos. Esta infraestructura hace uso de microcontroladores AVR de Atmel y está basada en el proyecto V-USB, el cual proporciona un firmware genérico para la implementación del protocolo USB por software. Cabe destacar, no obstante, que nuestra iniciativa va más allá al extender las capacidades de V-USB para facilitar la creación de firmware USB capaz de gestionar múltiples periféricos simultáneamente. Aunque existen alternativas como el uso de microcontroladores con soporte de USB por hardware, nuestra infraestructura no solo destaca por su bajo coste, sino también por su gran versatilidad para posibilitar el desarrollo de software a bajo nivel. En particular, brinda la oportunidad de experimentar tanto con el desarrollo de drivers USB en GNU/Linux, así como con la implementación de firmware USB para la gestión de hardware, empleando programación “bare metal”. Otro objetivo fundamental de este proyecto ha sido proporcionar un conjunto de drivers USB que ejemplifiquen la interacción con dispositivos de diversa naturaleza, como conjuntos de LEDs RGB, pantallas OLED, displays 7 segmentos, zumbadores y sensores de temperatura. Para tal fin, la infraestructura propuesta se complementa con una colección de controladores que hacen uso de una amplia gama de funciones de la API provista por el kernel Linux para el desarrollo de controladores USB. Estos controladores se implementan como módulos cargables del kernel de Linux, con el propósito de sentar las bases para futuras prácticas en asignaturas que aborden el desarrollo de drivers en Linux, como la asignatura optativa de grado Arquitectura Interna de Linux y Android (LIN) impartida en la UCM. Cabe mencionar que, si bien el desarrollo de controladores software USB en espacio de usuario utilizando libusb queda fuera del alcance de este proyecto, los drivers del kernel pueden emplearse como base para la creación de controladores empleando esta biblioteca. 79 Por último, se ha explorado el potencial que ofrece la placa Bee [2] como bloque básico de la infraestructura propuesta para facilitar el prototipado de dispositivos USB. El uso de esta placa está motivado por su gran flexibilidad para creación de entornos de prácticas de hardware, y por el hecho de que ya se ha realizado una inversión sustancial para adquirir un número considerable de ellas en las Facultades de Ciencias Físicas y de Informática, para la realización de laboratorios de distintas asignaturas. 7.1 Extensión natural de la infraestructura para la asignatura LIN Actualmente en la asignatura optativa LIN se continúa usando el dispositivo USB Blinkstick Strip. Este dispositivo, descrito en detalle en la sección 2.10 de esta memoria, presenta diversas limitaciones que impiden explorar al completo el potencial de USB. La infraestructura creada en nuestro proyecto va a suponer una gran oportunidad para extender las prácticas de la asignatura, permitiendo emplear nuevos dispositivos USB, y con ello estudiar más profundamente las características de esta tecnología. Entre estos nuevos contenidos que podrán impartirse, podemos destacar la posibilidad de experimentar con modalidades adicionales de transferencias de datos en USB, y realizar mediante las prácticas una mayor cobertura de la API del kernel para drivers, permitiendo con ello el desarrollo de controladores USB más complejos. Además, esto puede lograrse con un coste reducido debido al bajo precio de los componentes de la infraestructura, y a la reutilización de la placa Bee, ya disponible para su uso en los laboratorios. Esta placa permite ampliar con diversos perféricos el dispositivo USB creado. Gracias a que el firmware de la infraestructura desarrollada funciona con leves modificaciones en diversas placas con microcontroladores AVR, se está valorando la posibilidad de crear un simple adaptador que permita interconectar de forma compacta la placa que integra el microcontrolador (como Nano 4.1.3) , la placa Bee y el circuito que integra el puerto USB para conexión al host. Otra barrera que eliminaría esta infraestructura es el impedimento que supone que el dispositivo Blinkstick Strip (versión adquirida comercialmente) no permite modificar el firmware de forma sencilla. Nótese que el microcontrolador usado por Blinkstick Strip se encuentra soldado en una placa diseñada a medida, y su programación para realizar cualquier modificación es relativamente compleja, ya que esta placa no expone los pines para programar el dispositivo de forma directa. 7.2 Uso en otras asignaturas La infraestructura desarrollada en este proyecto no se limita a la ampliación del material de prácticas de la asignatura LIN. Su finalidad educativa puede ir mucho más allá, ya que esta infraestructura es extensible (tanto a nivel de software como de hardware) para 80 ser utilizada en otras asignaturas donde se imparten algunos de los conocimientos afines a las tecnologías empleadas durante el proyecto. Una muy buena opción podría ser integrar el desarrollo del firmware del dispositivo como práctica de aquellas asignaturas en las que se trabaje con sistemas empotrados, desarrollo a bajo nivel en C o microcontroladores en general. De igual manera ocurre con el hardware, ya que la plataforma es ampliamente extensible, permitiendo trasladar las posibles extensiones a prácticas donde los alumnos puedan construir nuevos dispositivos agregando periféricos que puedan funcionar con el microcontrolador elegido. 7.3 Valoración del TFG El proyecto que hemos afrontado ha sido un gran reto personal para nosotros debido a la amplitud y complejidad del mismo. Partíamos de una placa básica de desarrollo –Digispark con microcontrolador ATTiny85–; el tiempo reveló importantes limitaciones en su uso como componente básico de la infraestructura. Esto nos llevó a tener que elegir otra alternativa de desarrollo, y su proceso búsqueda fue muy enriquecedor para nosotros desde el punto de vista del aprendizaje. De igual manera, la parte software de la infraestructura tampoco ha sido sencilla de implementar, ya que USB es un protocolo inherentemente complejo. Esta complejidad nos ha llevado a tener que comprender muy bien todas sus características, para poder explotar correctamente el firmware proporcionado por el framework V-USB. La investigación que hemos realizado, junto todo el trabajo que se ha llevado a cabo para crear la infraestructura nos ha hecho comprender de una manera mucho más clara el funcionamiento de USB, así como su integración con el kernel Linux y el desarrollo de prototipos con microcontroladores. Dentro de los aspectos que más trabajo han demandado podríamos destacar lo siguiente: • El aprendizaje para programar un firmware completamente funcional en C, utilizando todo el marco de herramientas que nos proporciona Atmel para la compilación del código y posterior subida a un microcontrolador AVR. • El estudio profundo del stack USB en Linux y más concretamente de la API USB que integra, destacando su parte síncrona y asíncrona, junto con múltiples funciones y estucturas que permiten realizar transferencias de diversos tipos. • El desarrollo de hardware utilizando microcontroladores, y los diferentes componentes electrónicos que se necesitan para que este chip funcione con el puerto micro USB. En definitiva, consideramos que ha sido un proyecto bastante completo ya que se ha trabajado tanto con software como con hardware, creando una infraestructura que esperamos aporte mucho valor al aprendizaje de USB. 81 7.4 Trabajo futuro Gracias a la flexibilidad que ofrece la infraestructura desarrollada, el trabajo que se puede realizar en un futuro es realmente extenso. A continuación se enumeran algunas de las líneas de trabajo futuro: 1. Ampliar periféricos soportados. Ya que la infraestructura permite la rápida integración de nuevos periféricos, esta sería una muy buena opción para extender nuestro trabajo. Se podrían integrar dispositivos que utilicen protocolos o transferencias diferentes para comunicarse con el host y así ampliar el espectro de prácticas que los docentes pueden elaborar usando el dispositivo. 2. Desarrollo de controladores más avanzados. Al igual que con los periféricos, también se pueden seguir desarrollando drivers USB más avanzados. Nótese que los drivers proporcionados con la infraestructura propuesta están pensados para un público que se está iniciando en el mundo del desarrollo de drivers y su objetivo es ilustrar de manera sencilla el funcionamiento de la API USB del kernel Linux. 3. Integrar nuevos estándares USB. Otro campo donde se podría avanzar es creando nuevo hardware que permita soportar estándares USB más recientes como los USB 3.X o incluso USB 4.0. Esto permitiría desarrollar controladores que manejen transferencias masivas de datos ya que la velocidad de estos estándares maneja sin problemas este gran flujo de bytes. 4. Experimentar con microcontroladores con soporte USB por hardware. Para dar soporte a otros estándares de USB se podrían utilizar microcontroladores con soporte de USB por hardware. Esto garantizaría un mayor rendimiento y capacidad de procesamiento, ya que estos microcontroladores liberan a la CPU principal de la mayor parte de la gestión de la comunicación USB. 5. Mejorar la configuración de perfiles. A pesar de que la configuración de perfiles de hardware (selección de periféricos activos) ya es extremadamente sencilla, podrían pensarse nuevas formas de configurarlos, pero sin requerir conocimientos profundos de C y de la estructura del firmware. Por ejemplo, podría desarrollarse una aplicación con interfaz gráfica de usuario (GUI), que permita elegir cómodamente qué dispositivos se han de gestionar por el firmware, y se pueda compilar y cargar automáticamente el firmware con la configuración escogida en la plataforma USB. 82 Apéndice A Contribuciones Aportación de Guillermo Gascón Celdrán Durante mi tercer año de carrera cursé la optativa de Arquitectura Interna de Linux y Android y cuando se acercaba el final de la asignatura Juan Carlos nos presentó diferentes propuestas de Trabajos de Fin de Grado relacionados con los conceptos aprendidos, entre las cuáles se encontraba esta. Desde aquél entonces le confirmé mi interés por realizar este proyecto ya que me motivaba mucho el desarrollar un dispositivo que posteriormente se pudiera usar para que otros alumnos, como yo lo fuí, aprendieran lo máximo posible sobre USB. Cuando recibí la confirmación por parte de Juan Carlos de que podía realizar dicho TFG comencé a informarme sobre el protocolo USB, siguiendo todo lo que habíamos aprendido en clase y profundizando más con lecturas de libros y documentos. Este periodo de lectura inicial que hice me ha servido para entender los nuevos conceptos a los que nos hemos ido enfrentando durante el desarrollo de este trabajo. Posteriormente, una vez finalizado el curso 2021-2022, Juan Carlos nos ofreció prestarnos algo de material antes de verano para comenzar a investigar sobre los periféricos que ibamos a utilizar, así como con el entorno de desarrollo y el framework V-USB. Sin dudarlo acepté y durante los meses de verano estuve aprendiendo a usar tanto el anillo de LED circular, como un LED RGB individual que hace uso de un protocolo diferente para funcionar, además aprendí en profundidad como funciona el entorno de programación AVR y conseguí utilizar las diferentes herramientas que nos proporciona para compilar y subir nuevo código al microcontrolador con éxito. Ya más entrados en el curso 2022-2023, comencé a investigar sobre cómo estaba construido el dispositivo que tomamos de referencia, el Blinkstick Strip, consiguiendo comprender su firmware al completo y familiarizándome también con su construcción hardware. Esto me permitió crear una primera versión de prueba del firmware con la que pude hacer que el ordenador reconociera el microcontrolador bajo el nombre de PwnedDevice y con 83 los parámetros definidos por mí. Después de esto, comencé a investigar sobre cómo podíamos incluir dentro del firmware de prueba que ya teníamos el código necesario para utilizar el anillo de LED. Finalmente, tras realizar algunos ajustes de configuración y modificar alguna función, conseguí incluir con éxito una librería que nos permitía controlar el anillo por completo. Posteriormente, mejoré la eficiencia cuando se usa este periférico guardando ciertos datos sobre él que permitían ahorrar tiempo y cálculos. Una vez implementado el anillo, me puse con la función de lectura. Esta función es la que se ejecuta cuando el ordenador pide al dispositivo un cierto número de datos y tuve que aprender cómo funciona V-USB por dentro y cómo gestiona este tipo de llamadas para finalmente implementar con éxito la función y hacer que cuando el dispositivo reciba una lectura devuelva la cadena Hello World! I’m pwnedDevice ;). Después de la función de lectura empecé a implementar la integración en el firmware de un sensor de temperatura, esta investigación me llevo mucho tiempo y finalmente me fue imposible integrar este periférico dentro de V-USB. Actualmente desconocemos el problema que ocasiona mezclar el sensor de temperatura con el framework V-USB, ya que sin este último, el sensor funciona como se espera. Al mismo tiempo, ayudé a Javier a integrar una pantalla OLED en el proyecto, realizando las modificaciones necesarias en el firmware para dotar de funcionalidad a esta pequeña pantalla. A continuación, estuve aprendiendo a usar los timers internos del microcontrolador que terminamos usando, el ATMega328p. Esto me llevó un tiempo puesto que era mi primera vez trabajando con hardware de este tipo, pero finalmente conseguí generar diferentes señales PWM con las que pude controlar el buzzer y el diodo LED, haciendo sonar incluso una canción de cumpleaños feliz. Por último dentro del desarrollo del firmware, integré el endpoint de tipo INTERRUPT IN e implementé con éxito la devolución de un buffer de 8 bytes relleno de información cada vez que se recibe una transferencia de este tipo. Durante todo el desarrollo del firmware, he creado también diferentes utilidades que hacen uso de la librería libusb en Cy en Python, que me permitieron probar los nuevos periféricos de una manera rápida debido a la simplicidad con la que se creaban los mensajes para interactuar con el microcontrolador. A su vez, también estuve desarrollando los drivers que vienen dentro del paquete de drivers de ejemplo que ofrece este proyecto. Con ayuda de Juan Carlos, hemos conseguido exprimir las diferentes partes de la API que ofrece USB Core quedando un conjunto de ejemplo ideal para alumnos que se introducen en este campo. Como último retoque, una vez tuvimos el prototipo hardware funcionando, cree una pequeña placa prototipo con los componentes soldados quedando un dispositivo USB más compacto que cuando se encontraba todo conectado a la placa de desarrollo. 84 En cuanto al desarrollo de esta memoria, mis principales contribuciones han sido los capítulos que tratan sobre Tecnología USB, Proyecto V-USB y Firmware, junto con algunas aportaciones tanto en la introducción como en las conclusiones, además de los apéndices que se ilustran de breves manuales sobre aspectos relavantes con los que hemos tenido que trabajar. Y estas han sido mis aportaciones al proyecto, junto con mucho tiempo de investigacion que ha acompañado a todas las etapas del desarrollo tanto software como hardware. Aportación de Javier Rodríguez-Avello Tapias Una de las asignaturas que más me han llamado la atención de la carrera ha sido Sistemas Operativos, asignatura que cursé en el año 2021-2022 con Juan Carlos. La programación a bajo nivel en C, y todas las posibles funcionalidades y arquitectura del sistema operativo Linux que se imparten en esta asignatura despertó mi interés en poder realizar un Trabajo de fin de Grado relacionado con este área, además del enfoque de mi titulación enfocada más a la parte Hardware. Es por ello por lo que decidí consultar a Juan Carlos sobre los posibles proyectos relacionados con este área de programación. Al hacerlo, Juan Carlos me recomendó cursar la asignatura Arquitectura Interna de Linux y Android que él impartía, para profundizar en aspectos de programación a nivel de Kernel que Sistemas Operativos no cubría, matriculándome en ella al siguiente año. Juan Carlos me propuso este proyecto al estar relacionado con el desarrollo de un firmware puramente en C para un dispositivo similar al Blinkstick de la asignatura LIN, teniendo para ello que profundizar en protocolos más avanzados como USB y desarrollo de drivers más complejos. En la asignatura de LIN estudiaría la comunicación USB mediante endpoints de tipo control, necesarios para la comunicación con el dispositivo Blinkstick. Este proyecto tiene como uno de los objetos el estudio de otros tipos drivers, como los que utilizan endpoints Interrupt IN, fuera del ámbito de estudio de la asignatura. En ese mismo verano, tras haber tenido una primera reunión con Guillermo y los tutores del proyecto, decidimos estudiar a fondo la placa Digispark. Para ello, necesité adquirir conocimientos sobre USB, con especial foco en la parte relacionada con las clases USB y haciendo hincapié en la clase HID que posteriormente haríamos uso en este proyecto. Por otra parte, invertí tiempo en estudiar cómo se realiza la comunicación con el chip que alberga la placa Digispark, el ATTiny85. Para ello, y teniendo en mis manos esta la placa con el anillo de LEDs de Neopixel, me puse a buscar documentación sobre estos dispositivos, características técnicas y posibles proyectos con funcionalidades similares. Tras realizar diversas búsquedas, tanto ejemplos de la propia librería de NeoPixel como en otras plataformas como GitHub, me di cuenta de que este dispositivo tiene multitud de aplicaciones, muchas de ellas desarrolladas con el entorno de Arduino. Por ello, decidí instalar el entorno en mi equipo para poder probar código de ejemplo, y estudiar su funcionamiento con la tira de LEDs de NeoPixel y un LED RGB. Me di cuenta de que para poder flashear firmware en la placa, se requiere tener el programador de 85 Micronucleus en el entorno de AVR, una utilidad que incorpora Arduino, ventaja si se depende de este entorno para desarrollar software, pero que no era nuestro objetivo depender de Arduino. Posteriormente, empezamos a estudiar el framework de V-USB, su funcionamiento y arquitectura interna. Para poder testear la funcionalidad de este firmware, tuve varias reuniones con Guillermo, en las que estuvimos realizando pruebas con un firmware de ejemplo para ver cómo funciona este framework. Posteriormente, conseguimos implementar la funcionalidad para V-USB que controla el LED circular de NeoPixel, desarrollando un programa de usuario basado en la API HID de USB que establece todos los colores de la cadena de color verde, rojo y azul; y posteriormente cambia el color de cada LED de forma individual mediante un bucle. De esta forma, conseguí aprender el protocolo que usa la tira de LEDs y estudiar en profundidad el funcionamiento y comunicación mediante report-IDs de V-USB, necesaria para el siguiente periférico que implementé. Mientras Guillermo se encargó de documentarse y desarrollar el código correspondiente al sensor de temperatura, yo me encargué de estudiar el desarrollo correspondiente a una pantalla LCD 2x16 con un módulo I2C. Tras buscar distintos ejemplos y librerías que hagan uso de esta pantalla, y hacer diversas pruebas y modificaciones con estos proyectos de ejemplo, resultó complejo implementar la funcionalidad para mostrar cadenas de texto utilizando el ATTiny85, ya que todos estos proyectos utilizaban otros microchips de AVR con implementaciones basadas en V-USB, pero con mucha configuración adaptada a cada hardware y funcionalidad específica, lo que aumentaba su complejidad a la hora de adaptarlo a nuestro firmware. Gran parte de mi tiempo se invirtió en poder desarrollar la funcionalidad correspondiente a la gestión de esta pantalla, utilizando para ello distintas librerías de I2C. Finalmente, por incompatibilidades del propio firmware y del protocolo I2C, no fui capaz de integrarla. Basándome en otro proyecto que hacía uso de una pantalla LCD tipo OLED en ATmega328p (en ese momento migramos el firmware a este microchip), decidí investigar sobre la librería que dotaba de funcionalidad a este tipo de pantalla. Esta nueva pantalla difería de la original al ser más grande y tener incorporado el protocolo I2C de fábrica, a diferencia de la pantalla original a la que tuve que soldar este módulo. Finalmente, con ayuda de Guillermo para su integración en el firmware, conseguí incorporar este periférico al proyecto, cuya funcionalidad inicial sería como output para hacer debug, aunque posteriormente, gracias a la ayuda de Cristian, incorporamos una función de depuración a modo de printk() para mostrar mensajes por puerto serie, ya que la placa NANO incorpora este componente. Otra contribución al proyecto fue la creación de distintos perfiles. En una primera implementación, se crearon dos perfiles para el ATmega328p y para el ATTiny85, cada uno con configuración específica de cada chip, aunque finalmente, al establecerse como prototipo final la placa NANO que incorpora el ATmega328p (al ser más útil por las ventajas que incorpora frente a ATTiny85), se decidió implementar una configuración de perfiles para que los periféricos actuasen según una determinada funcionalidad definida en cada perfil. Para ello, desarrollamos un fichero deviceconfig.h donde, mediante 86 macros, se activan los distintos perfiles. Como último punto, se ha desarrollado un driver para gestionar la pantalla OLED, haciendo uso de endpoints tipo control, en el que se escriben cadenas de texto a la pantalla mediante un fichero especial de caracteres en /dev . Para este desarrollo, gran parte de la ayuda se la debo a Guillermo, ya que en los últimos meses, por temas laborales, he estado muy ajustado de tiempo, al igual que en la implementación del driver que hace uso de endpoints tipo Interrupt IN, con una arquitectura muy interesante (no se explica en LIN), del que se ha encargado del desarrollo en su totalidad Guillermo. Para la redacción de esta memoria, me he centrado en los capítulos de Hardware y Drivers desarrollados en el proyecto, habiendo hecho otras aportaciones en el resto de capítulos de esta memoria. 87 C.3 Seleccionamos la interfaz usbmonX correcta Una vez que hayamos abierto Wireshark, veremos algunas interfaces usbmon desde las que podemos monitorear. Para seleccionar la interfaz correcta, debemos verificar a qué bus está conectado nuestro dispositivo USB. Podemos usar lsusb para esa tarea. $ lsusb Bus 001 Device 067: ID 16c0:05df Van Ooijen Technische Informatica HID device En este caso, elegiremos usbmon1 ya que nuestro dispositivo está conectado al bus 001. Además, podemos ver que el DeviceID es el número 67, esto será útil más adelante para el filtro de visualización. C.4 Empezamos a capturar tráfico Para comenzar a capturar tráfico, solo necesitamos hacer doble clic en la interfaz usbmon correcta y automáticamente comenzará a mostrar líneas con los paquetes en tránsito. La mayor parte del tráfico que se muestra no nos será útil, por lo que lo mejor ahora es aplicar algunos filtros para mostrar solo el tráfico relacionado con nuestro dispositivo USB. Figura C.1: Captura mostrando el tráfico USB de Wireshark capturado 94 C.5 Filtrar el tráfico mostrado Como se ha mencionado previamente, la mejor manera de mostrar el tráfico que nos interesa es usar filtros de visualización. La sintaxis es muy simple, por lo que podemos escribirla nosotros mismos o hacer clic derecho en un paquete desde nuestro dispositivo y prepararlo como un filtro. En la línea de filtros, aplicamos lo siguiente: usb.src == "1.67.0" || usb.dst == "1.67.0" Donde el primer número corresponde al bus, el segundo al device ID y el tercero al endpoint ID. 95 96 Apéndice D Introduction This introductory chapter presents the motivation for this project, and describes the objectives and the planning of the tasks to carry it out. D.1 Motivation The USB protocol is widely used throughout the world for interaction with peripherals of a very diverse nature. Any user can connect a device to his computer. When doing so, the corresponding driver installed on the system recognizes the device and allows it to perform the functions for which it has been developed. To achieve this simplicity of use, the developer of the driver and the manufacturer of the USB device have to deal with the complexity of the USB protocol. In current Computer Science degrees, the limited time that can be spent on describing low-level input-output aspects coupled with the complexity of the USB technology and specification makes it difficult for students to acquire knowledge about USB device design and driver development. In order to develop practical content on these aspects, it is also necessary to have sufficiently simple, versatile and low-cost USB hardware, which can be a significant challenge. In the Faculty of Computer Science of the Complutense University of Madrid, one of the few subjects that deals with the development of USB drivers is the subject Internal Architecture of Linux and Android, a common elective offered to students of different degree courses. In this report we will refer to this subject as “LIN”, since this is the short name used administratively in the Faculty of Computer Science to refer to it. In the LIN lab practices we make use of the Blinkstick Strip device [1], a simple USB device with 8 colored LEDs whose state can be altered individually. The simplicity of this device allows to elaborate practices to become familiar with the development of USB drivers, especially those implemented at the operating system kernel level. 97 Blinkstick Strip, however, has several limitations. First, this device is so simple that it prevents you from exploring the potential of USB in depth. After all, it only allows altering the state of individual LEDs by sending simple control messages to the device. Consequently, the corresponding practices do not allow experimentation with other modes of data transfer covered by the USB specification, and thus use a very small subset of the API calls for USB driver development. Secondly, their limited versatility does not justify their price (around 20€ per unit) to enable a large-scale adoption of the device in different subjects and undergraduate degrees at the faculty. Finally, although the Blinkstick Strip firmware is freely available, its manufacturer does not provide documentation on how to modify it using the commercial hardware platform. This is a barrier to using the device for firmware design practices, where bare metal programming is required. Regardless of the input-output protocol or technology used, having a sufficiently flexible hardware platform is a key issue in laboratory practices focused on interaction with peripherals. In particular, the ability to add new input-output devices to the system without requiring a lot of additional cabling allows a large number of lab practices to be developed to meet the needs of different subjects. This also makes it possible to introduce small variations in the practical content of the same subject over time. These factors make it possible to extend the life cycle of a teaching-oriented hardware platform, thereby substantially reducing the cost of laboratory equipment. The Bee board [2], [3] developed by Prof. Christian Tenllado is a clear example of a system designed for the construction of flexible hardware platforms. It is an expansion board for systems based on the pinout of the Raspberry Pi mini PC (v1-v4) that is provided with a set of simple peripheral devices, and allows the expansion of these by means of generic bias circuits. This board, used in its different versions in undergraduate courses at the Faculties of Computer Science and Physical Sciences at UCM, despite not providing support for USB, has been a great inspiration for the realization of this TFG. D.2 Objectives The main objective of this project has been to build a low-cost hardware-software infrastructure that allows prototyping USB devices and developing drivers for these devices. Our infrastructure is based on AVR microcontrollers from Atmel [4], and on the V-USB project [5], which provides a generic firmware to implement software USB support. Our project extends V-USB to facilitate the creation of USB firmware that manages multiple peripherals simultaneously. Although there are other alternatives to using the V-USB framework (e.g., the use of microcontrollers with hardware USB support), the proposed infrastructure is not only low-cost, but also offers great versatility for low-level software development. In particular, it allows experimenting with both the development of USB drivers in systems provided with operating system, as well as the implementation of USB firmware for hardware management, using bare metal 98 programming. In this TFG, development has been carried out in both areas. Another objective of the project is to provide a set of USB drivers that illustrate the interaction with devices of different nature, such as RGB LED arrays, LCD displays, 7-segment displays, buzzers or temperature sensors. For this purpose the proposed infrastructure is accompanied by a collection of drivers that employ a wide collection of functions of the API provided by the Linux kernel for the development of USB drivers. These drivers are implemented as loadable modules of the Linux kernel, in order to serve as a basis for future practices of the LIN subject. Although the development of user-space USB drivers with libusb [6] is outside the scope of this TFG, the kernel drivers provided serve as a basis for the creation of user-space drivers developed with this library. Finally, given the flexibility provided by the Bee board [2], [3], this project has explored ways to use this board as a basis for implementing USB devices. Note that the Faculty of Computer Science has already acquired a good number of these boards for different subjects, so the reuse of this hardware as a basic block of the USB infrastructure allows further amortization of the investment made. D.3 Work plan Two hardware prototypes of the infrastructure have been developed in this project. The first prototype is based on the ATTiny85 microcontroller–same chip used by the Blinkstick Strip device. The second prototype overcomes the limitations of the previous one (described in detail in chapter 4), and uses the ATMega328p microcontroller. During the project it has been necessary to become familiar with programming on the two aforementioned microcontrollers–embedded in the Digispark [7] and Nano [8] boards, respectively–as well as with a large collection of input-output devices used in the prototypes. These devices included a circular ring of RGB LEDs, a buzzer (or buzzer ), an OLED display, and a 7-segment display (integrated on the Bee board [2]). With this in mind, a work plan was carried out, consisting of the following tasks (see Fig D.1): T1 Study of the Digispark board and analysis of the control of the different peripherals under Arduino environment. T2 Study of the Blinkstick Strip device and its open source firmware. T3 In-depth analysis of the firmware of the V-USB project. T4 Development of the first version of the V-USB firmware on the Digispark board (without full peripherals support) T5 Study of the I/O functions using the ATTiny85 microcontroller registers. 99 Figura D.1: Project Gantt Chart T6 Extension of the functionality of the V-USB firmware developed in T4 with basic device control support. T7 Development of register level control (without using Arduino environment) of the LED diode of the Digispark board. T8 Development of the register level control of the circular LED ring. T9 Development of the register level control of the buzzer. T10 Development of the register level control of the 7-segment display. T11 Study of design alternatives for extra circuitry associated with the micro USB connector required by the second prototype. T12 Design of the hardware prototype based on ATMega328p T13 Integration of Nano board (with ATMega328p) with micro USB connector circuitry T14 Firmware modification to work with ATMega328p chip T15 Adaptation of debugging tool to our prototype T16 Development of register level control of the OLED screen T17 Study of the timers included in the ATMega328p T18 Study of PWM signals and their generation through timers T19 Refactoring the V-USB firmware for the use of profiles. T20 Implementation of hardware profiles using macros T21 Migration of LED driver implementation to PWM T22 Migration of buzzer driver implementation to PWM T23 Assembly of the final prototype (based on ATMega328p) on punched plate 100 T24 Writing of the TFG report During the development of the project, several meetings have been held with the TFG directors. The frequency of these meetings has been gradually increasing as the development of the project has progressed. These meetings have served to solve design problems, raise the different phases and solve multiple doubts and limitations that have arisen throughout the project. For the management of both the code and the sources of the project memory, shared repositories have been used in the GitHub platform. The links to both repositories are: •Source code. https://github.com/ggasconn/TFG_USB •Memory. https://github.com/ggasconn/MemoriaTFG-USB/ D.4 Memory organization The remainder of this memory has been divided into the following chapters: • Chapter 2 describes the low-level features of the USB protocol, as well as its main abstractions, such as descriptors, endpoints, or URBs. • Chapter 3 provides a general introduction to the V-USB project and discusses the source code structure of the firmware provided. This chapter also discusses possible alternatives to the use of V-USB for building hardware software infrastructures such as the one proposed in this project. • Chapter 4 presents the characteristics of the various microcontrollers used in the project, the technical details of the boards used in various phases of the project, and the hardware aspects of the peripherals supported by our prototype. • Chapter 5 analyzes in detail all the elements and functionalities implemented in the firmware of the prototype, which constitutes an extension of the V-USB project. • Chapter 6 describes in detail the example USB drivers that manage the peripheral devices supported in the firmware. • Chapter 7 contains the main conclusions of the dissertation and lists the lines of future work. The following five appendices can also be found at the end of this report: • Appendix A lists the contributions made by each member of this Final Degree Project to the project. • Appendix B describes the instructions for analyzing USB URB packets with the Wireshark software. 101 • Appendix C provides a guide detailing the process for flashing the firmware on the prototype board. • Finally, Appendices D and E contain the English translation of this introductory chapter as well as the conclusions. 102 Apéndice E Conclusions The main objective of this project has been the construction of a low-cost hardwaresoftware infrastructure, around 5€ per device, oriented to the prototyping of USB devices and the development of drivers for those devices. This infrastructure makes use of AVR microcontrollers from Atmel and is based on the V-USB project, which provides a generic firmware for the implementation of the USB protocol by software. It should be noted, however, that our initiative goes further by extending the capabilities of V-USB to facilitate the creation of USB firmware capable of managing multiple peripherals simultaneously. Although there are alternatives such as the use of microcontrollers with hardware USB support, our infrastructure stands out not only for its low cost but also for its great versatility in enabling low-level software development. In particular, it provides the opportunity to experiment both with the development of USB drivers in GNU/Linux, as well as with the implementation of USB firmware for hardware management, using “bare metal” programming. Another fundamental objective of this project has been to provide a set of USB drivers that exemplify the interaction with devices of diverse nature, such as RGB LED arrays, OLED displays, 7-segment displays, buzzers, and temperature sensors. To this end, the proposed infrastructure is complemented with a collection of drivers that make use of a wide range of functions of the API provided by the Linux kernel for the development of USB drivers. These drivers are implemented as loadable modules of the Linux kernel, with the purpose of laying the groundwork for future practices in subjects dealing with Linux driver development, such as the undergraduate elective Linux and Android Internal Architecture (LIN) taught at UCM. It is worth mentioning that, although the development of USB software drivers in user space using libusb is outside the scope of this project, the kernel drivers can be used as a basis for the creation of drivers using this library. Finally, the potential offered by the Bee board [2] as a basic building block of the 103