scieee AI-readable full text Open interactive document viewer

Comunicación Ethernet entre un ordenador y una FPGA empleando un procesador Teensy

Serna Bravo, Pablo

Abstract

Departamento de Tecnología Electrónica

Full text

UNIVERSIDAD DE VALLADOLID ESCUELA DE INGENIERIAS INDUSTRIALES GRADO EN INGENIERÍA EN TECNOLOGÍAS INDUSTRIALES COMUNICACIÓN ETHERNET ENTRE UN ORDENADOR Y UNA FPGA EMPLEANDO UN PROCESADOR TEENSY Autor: Serna Bravo, Pablo Tutor: de Pablo Gómez, Santiago Departamento de Tecnología Electrónica Valladolid, julio de 2022 I AGRADECIMIENTOS A Santiago de Pablo, por haberme dado la oportunidad de descubrir un campo prácticamente nuevo para mí, y por su esfuerzo y dedicación para que aprendiese con este trabajo. A los profesores y personas de la Universidad de Valladolid que me han enseñado a pensar como un ingeniero y gracias a los cuales he podido enfrentarme a todos los retos de realizar este trabajo. A mi familia, por estar siempre en los momentos más duros y darme un apoyo incondicional en esta etapa. Y a Ane, por compartir este camino conmigo y por el gran equipo que hemos formado. II III RESUMEN Y PALABRAS CLAVE En este trabajo se ha implementado un enlace Ethernet entre un ordenador y un dispositivo reconfigurable tipo FPGA. Se ha empleado un microcontrolador Teensy 4.1 con una conexión Ethernet de 100 Mbps que se comunica con el ordenador a través de tramas TCP. Entre este dispositivo y la FPGA se ha implementado una interfaz paralela que permite comunicarse con múltiples componentes internos de la FPGA. Se ha empleado el software de Qt y de Arduino para crear programas en ambos dispositivos. Se describe el código utilizado y se obtienen resultados de la velocidad de comunicación. Palabras clave: Ethernet, FPGA, Teensy, Qt, Arduino. IV V ABSTRACT & KEYWORDS In this paper an Ethernet link has been implemented between a computer and a reconfigurable FPGA device. A Teensy 4.1 microcontroller has been used with a 100 Mbps Ethernet connection that communicates with the computer through TCP frames. A parallel interface has been implemented between this device and the FPGA to communicate with multiple internal components on the FPGA. Qt and Arduino software have been used to create programs on those devices. The used code is described and communication speed results are obtained. Keywords: Ethernet, FPGA, Teensy, Qt, Arduino. VI I ÍNDICE CAPÍTULO 1 INTRODUCCIÓN ............................................................................... 1 1.1 CONTEXTO ...................................................................................................................... 1 1.2 OBJETIVOS Y MOTIVACIÓN .............................................................................................. 2 1.3 ESQUEMA DEL PROYECTO .............................................................................................. 3 CAPÍTULO 2 ANTECEDENTES Y DESARROLLO TEÓRICO .................................... 5 2.1 MICROCONTROLADOR TEENSY 4.1 ................................................................................ 5 2.1.1 Procesador ................................................................................................................... 6 2.1.2 Pines ............................................................................................................................. 6 2.1.3 Comunicación .............................................................................................................. 8 2.1.4 Memoria ....................................................................................................................... 8 2.1.5 Software y programación ............................................................................................ 9 2.2 COMUNICACIÓN ETHERNET .......................................................................................... 10 2.3 PROTOCOLO TCP/IP ...................................................................................................... 12 2.4 ARDUINO IDE Y TEENSYDUINO ..................................................................................... 15 2.5 ENTORNO DE DESARROLLO Qt ..................................................................................... 17 2.5.1 Descarga e instalación .............................................................................................. 17 2.5.2 Estructura de programación ..................................................................................... 19 2.5.3 Interfaz de usuario ..................................................................................................... 20 2.5.4 Estructura externa ..................................................................................................... 21 2.6 ARQUITECTURA DE LA FPGA ......................................................................................... 21 2.6.1 Escribir una dirección ................................................................................................ 24 2.6.2 Leer una dirección ..................................................................................................... 24 2.6.3 Escribir datos ............................................................................................................. 25 2.6.4 Leer datos .................................................................................................................. 25 CAPÍTULO 3 DESARROLLO DEL PROYECTO ..................................................... 29 3.1 ESTRUCTURA DE LA COMUNICACIÓN ........................................................................... 29 3.2 PASOS PREVIOS ............................................................................................................ 30 3.2.1 Puerto Ethernet .......................................................................................................... 30 3.2.2 Dirección MAC ............................................................................................................ 31 3.3.3 Direcciones IP ............................................................................................................ 32 3.3.4 Puerto de enlace ........................................................................................................ 34 3.3.5 Codificación de caracteres ........................................................................................ 34 VIII CAPÍTULO 1 INTRODUCCIÓN PABLO SERNA BRAVO 1 CAPÍTULO 1 INTRODUCCIÓN 1.1 CONTEXTO El mundo actual gira alrededor de los dispositivos electrónicos. La información, las comunicaciones, la tecnología y, en general, todos los elementos de nuestro día a día, incorporan algún tipo de procesador que permite al dispositivo tomar decisiones relativamente complejas y facilitar la vida del usuario. Sin embargo, el desarrollo de estos dispositivos ha sido un camino largo, con líneas de trabajo muy diferentes que intentaban alcanzar nuevos logros. En este sentido, se han diferenciado desde el principio dos vertientes de avance claras: una basada en el hardware, que centra sus esfuerzos en crear nuevos dispositivos capaces de optimizar tareas concretas, como los ASIC (Application Specific Integrated Circuit); y otra que avanza centrándose en el software, con procesadores más potentes de propósito general que ofrezcan gran flexibilidad de cara a crear y utilizar programas de más alto nivel. En la década de los 80 empiezan a aparecer dispositivos que intentan aunar las ventajas de ambas corrientes, buscando tener un hardware capaz de optimizarse y utilizarse en tareas específicas sin perder la flexibilidad de un dispositivo programable. Aparecen los CPLD (Complex Programmable Logic Device), circuitos basados en puertas lógicas capaces de interconectarse entre sí para formar estructuras electrónicas complejas, pero diseñados para poderse programar y borrar para cambiar las estructuras que contienen. Finalmente evolucionan hasta las FPGA (Field-Programmable Gate Array), formadas por bloques lógicos que se pueden programar internamente para definir su comportamiento e interconectar entre sí para definir estructuras complejas, como por ejemplo procesadores [1]. Con una FPGA podemos comprobar diseños de procesadores de forma más fiable que con simulaciones con modelos matemáticos y, además, permiten cambiar partes concretas de la estructura interna sin afectar al resto del circuito, con lo que el diseño de procesadores se vuelve mucho más rápido y cómodo. Actualmente, se utilizan tanto para la verificación de circuitos electrónicos de todo tipo como para funcionar como procesadores variables y actualizables en aplicaciones específicas o para la docencia. CAPÍTULO 1 INTRODUCCIÓN PABLO SERNA BRAVO 2 1.2 OBJETIVOS Y MOTIVACIÓN En el Departamento de Tecnología Electrónica de la Universidad de Valladolid se trabaja con simulaciones de procesadores distintos para que los alumnos de las diferentes asignaturas que se imparten puedan observar y experimentar con el comportamiento de estos. Actualmente se utilizan modelos matemáticos hechos con Simulink, una extensión del programa Matlab, una plataforma para programación y cálculo numérico capaz de analizar datos y crear modelos matemáticos precisos. Sin embargo, no deja de ser una simulación aproximada por un modelo matemático, que muchas veces difiere de la realidad y no nos permite observar los fenómenos que pueden aparecer en un dispositivo real. Para solucionarlo, se están desarrollando varias líneas de trabajo que buscan implementar los procesadores en una FPGA que sea capaz de interactuar e intercambiar datos a tiempo real con el usuario en el laboratorio. Para ello, hace falta conectar un ordenador que sea capaz de programar el circuito interno de la FPGA, pero también dar órdenes e intercambiar información para poder usarla una vez programada. La idea global es conectar un ordenador a un microcontrolador intermedio para que procese las órdenes que se le envíen y las traduzca para poder controlar la FPGA. Se está trabajando en opciones de conexión a través de USB, WiFi y Ethernet, cada uno con microcontroladores distintos especializados en cada tipo de conexión. En este trabajo, nos centraremos en la conexión a través de Ethernet. El objetivo es conectar un ordenador con una FPGA de manera que el PC actúe como master enviando las órdenes y la FPGA como slave, ejecutando lo que le llegue. Para ello, crearemos un programa en el ordenador con el que podamos elegir qué queremos que haga la FPGA, que se encargará de mandar mensajes a un procesador intermedio a través de un cable ethernet con el protocolo TCP; el procesador intermedio, en nuestro caso el Teensy 4.1, se encargará de procesar las órdenes que le lleguen y enviarlas a la FPGA, a la que estará conectado a través de sus pines. Este trabajo se centra en conseguir que el programa comunique con la FPGA y pueda controlar algunos espacios de memoria. No son objetivos prioritarios la velocidad ni el flujo de datos que se puedan intercambiar, si no que se intentará conseguir el mejor posible para que se puedan optimizar con trabajos posteriores partiendo de un diseño que funcione y sea robusto. CAPÍTULO 1 INTRODUCCIÓN PABLO SERNA BRAVO 3 1.3 ESQUEMA DEL PROYECTO A continuación, se define brevemente el contenido de los capítulos en los que se estructura el proyecto. o Capítulo 1: Introducción Es el desarrollado hasta ahora, que plantea el contexto de este trabajo, los objetivos y motivación y la estructura de este. o Capítulo 2: Antecedentes y desarrollo teórico Desarrolla el estado de la técnica y explica los conocimientos teóricos necesarios para el proyecto. Se describe el Teensy 4.1, la conexión Ethernet, el protocolo TCP/IP, los softwares de programación y la FPGA. o Capítulo 3: Desarrollo del proyecto Define al completo los diferentes elementos que componen el proyecto y cómo se conectan y comunican entre sí. Explica primero el esquema general y los pasos previos al diseño de programas y después describe los programas creados tanto para el PC como para el Teensy. o Capítulo 4: Resultado final, testeo y verificación Comprueba el funcionamiento del proyecto creado, mostrando los resultados prácticos y muestra pruebas realizadas para comprobar la velocidad de comunicación. o Capítulo 5: Conclusiones Presenta los objetivos conseguidos y las conclusiones obtenidas del proyecto y plantea las líneas futuras. CAPÍTULO 1 INTRODUCCIÓN PABLO SERNA BRAVO 4 CAPÍTULO 2 ANTECEDENTES Y DESARROLLO TEÓRICO PABLO SERNA BRAVO 5 CAPÍTULO 2 ANTECEDENTES Y DESARROLLO TEÓRICO En este apartado se revisará el estado de la técnica y se explicarán los conocimientos teóricos previos necesarios para el entendimiento de este trabajo, así como la descripción del material, herramientas y estructuras utilizadas en su desarrollo. Lo abordaremos por el lado del hardware, en el que hablaremos del procesador utilizado, los puertos y las conexiones; y por el lado de software, en el que hablaremos del lenguaje de programación y librerías, programas y herramientas de desarrollo. Desarrollaremos por separado la explicación relativa a la FPGA, ya que no es el objetivo principal del proyecto, pero será necesaria para la definición de elementos del mismo. 2.1 MICROCONTROLADOR TEENSY 4.1 Los Teensy son una familia de microcontroladores basados en USB capaces de implementar muchos tipos de proyectos. Han sido desarrollados por dos estadounidenses que se agrupan con el nombre de PJRC.COM. El Teensy 4.1 es el último modelo desarrollado de esta familia [2]. El microcontrolador Teensy se ha ido desarrollando y actualizando hasta llegar a la versión actual 4.1, por lo que no hay proyectos recientes que utilicen este diseño, pero sí otros que utilizan diseños anteriores y que pueden darnos información útil para el proyecto actual. Sin embargo, como suele ocurrir con estos controladores, son un medio que se utiliza en proyectos más grandes y no se describe demasiado el código utilizado para el procesador, ni su funcionalidad. El Teensy 4.0 se ha utilizado transmitiendo datos a través de USB en un proyecto más complejo para medir el consumo de una placa de desarrollo ligera [3]. Otros proyectos utilizan el Teensy 3.6 como elemento auxiliar para validar el comportamiento de una FPGA, en este caso para la generación de ondas sonoras [4]. También podemos encontrar un proyecto similar al nuestro que se centra en la FPGA para que pueda ser programada utilizando el Teensy, que está a su vez conectado al PC por USB. Es un proyecto muy completo pero que define muy poco la parte del Teensy, por lo que tan solo aportará un ejemplo cercano para este trabajo [5]. CAPÍTULO 2 ANTECEDENTES Y DESARROLLO TEÓRICO PABLO SERNA BRAVO 6 En este proyecto se elige el Teensy 4.1 por encima del resto de microcontroladores de esta familia por ser el único que incorpora conexión a través de Ethernet. Destaca frente a otros microcontroladores por ser capaz de alcanzar los 100 Mbits por segundo y, además, puede funcionar a 600 MHz, por lo que su velocidad no será limitante para nuestro diseño; e incorpora todas las funcionalidades necesarias para el desarrollo del proyecto. Las especificaciones del Teensy 4.1 que más nos interesan son las siguientes: • Procesador ARM Cortex-M7 a 600 MHz. • Unidad matemática de coma flotante de 64 y 32 bits. • 7936K de memoria Flash, 1024K de RAM y 4K de EEPROM. • Conexión USB a 480 Mbit/s. • 55 pines digitales de entrada y salida. • 8 puertos serie, 3 SPI y 3 I2C. • 1 puerto SDIO (4 bit) para la tarjeta SD incorporada. • Ethernet de 10/100 Mbit/s con el módulo DP83825 PHY. • 32 canales DMA de propósito general 2.1.1 Procesador El ARM Cortex-M7 es un procesador de 32 bits que funciona a 600 MHz y que es capaz de ejecutar dos instrucciones en un mismo ciclo de reloj entorno al 50% de las veces con un código optimizado. Es capaz de acceder por separado a la memoria de programa y de datos, pudiendo hacer varios accesos en el mismo ciclo, y utiliza lo que se denomina “branch prediction”, una estructura capaz de predecir y simplificar las instrucciones cuando se repiten en un bucle. 2.1.2 Pines El microcontrolador cuenta con pines analógicos y digitales de entrada y salida. Podemos configurar estos pines como pullup o pulldown para utilizar las resistencias internas que nos permiten controlar el paso de corriente desde el circuito exterior. CAPÍTULO 2 ANTECEDENTES Y DESARROLLO TEÓRICO PABLO SERNA BRAVO 7 La disposición de los pines en el Teensy 4.1 es la siguiente: Figura 1 Conectores laterales Teensy 4.1 [2] Figura 2 Conectores adicionales Teensy 4.1 [2] En este proyecto utilizaremos los pines de Ethernet para conectar el microcontrolador al PC y algunos de los pines de entrada y salida para conectarlo con la FPGA. CAPÍTULO 2 ANTECEDENTES Y DESARROLLO TEÓRICO PABLO SERNA BRAVO 8 2.1.3 Comunicación La principal vía de comunicación del Teensy es el puerto USB, que permite transferir el programa desde el PC e intercambiar datos con el protocolo serie. Se transmiten bytes en ambas direcciones y, con el software propio del Teensy, se pueden enviar mensajes y visualizarlos en el PC. Además, sirve como canal de alimentación para el microcontrolador. Tiene otros puertos de comunicación (serie, SPI, I2C) que no se utilizan en este proyecto, pero podrían llegar a utilizarse en líneas futuras para enviar otro tipo de instrucciones a la FPGA o intercambiar información con otros dispositivos. Nuestro Teensy 4.1 recibe instrucciones e intercambia datos con el PC a través de Ethernet. Tiene un controlador y un módulo DP83825 PHY que permiten gestionar las comunicaciones por un cable Ethernet sin necesidad de un Ethernet Shield (módulos SPI o I2C que permiten conexión a Ethernet a otros microcontroladores). El fabricante recomienda un kit de conexión para un cabezal que permite conectar el cable Ethernet y ofrece funcionalidades relacionadas con el software. En este proyecto, por ser un prototipo, utilizaremos un cabezal simple conectado directamente del que hablaremos más adelante. 2.1.4 Memoria El Teensy 4.1 puede almacenar 8 Mbytes de código de programa y variables de solo lectura en la memoria flash. Tiene una memoria RAM de 1024K, de la cual a la mitad se accede como “tightly coupled memory” o memoria fuertemente acoplada, lo que permite accesos rápidos para un mejor desempeño. Parte de la memoria flash se puede utilizar para emular una memoria EEPROM. Además, permite hacer expansiones de memoria e incorpora una tarjeta SD. CAPÍTULO 2 ANTECEDENTES Y DESARROLLO TEÓRICO PABLO SERNA BRAVO 9 Figura 3 Esquema de memoria Teensy 4.1 [2] 2.1.5 Software y programación El Teensy 4.1 se puede programar con diferentes herramientas. Nosotros vamos a utilizar la opción del entorno de desarrollo integrado (IDE) de Arduino junto con la ampliación específica Teensyduino, que nos permite desarrollar programas para nuestro microcontrolador. Además, incluye librerías especialmente optimizadas para el Teensy. Una segunda opción sería utilizar la expansión Visual Micro para el software de Microsoft Visual Studio si quisiésemos programar tanto para el PC como para el Teensy en un mismo entorno de desarrollo. El Teensy incorpora un software automático que entra en el modo de programación y que se indica con un LED rojo sobre la placa. Para entrar en este modo de forma manual se puede utilizar también el botón que vemos en la Figura 5. CAPÍTULO 2 ANTECEDENTES Y DESARROLLO TEÓRICO PABLO SERNA BRAVO 16 Figura 13 Elección de la placa en Arduino En esta misma pestaña podemos seleccionar el tipo de entrada que nos llegará por el USB, ya que el Teensy puede enviar señales como distintos periféricos. Lo mantendremos en “Serial” para poder enviar mensajes de control al monitor serie. Nos permite también elegir la velocidad de procesamiento de la CPU, que en este caso está a 600 MHz. También permite definir el tipo de optimización, que mantendremos en faster y la disposición del teclado si quisiesemos usar el Teensy como tal. Con estos entornos instalados podemos programar para el Teensy utilizando los lenguajes de C y C ++ y podemos utilizar las librerías que incorporan tanto Arduino como el Teensy. Para poder transferir nuestro programa al Teensy debemos descargar también el Teensy Loader desde la página del fabricante (www.pjrc.com/teensy/ loader.html). Elegimos el sistema operativo que estemos utilizando y descargamos el archivo. No es necesaria instalación, simplemente con ejecutarlo debemos ver una ventana similar a la Figura 14. CAPÍTULO 2 ANTECEDENTES Y DESARROLLO TEÓRICO PABLO SERNA BRAVO 17 Figura 14 Teensy Loader desconectado Figura 15 Teensy Loader conectado Cuando conectamos nuestro Teensy el programa lo detecta automáticamente y aparece una imagen del Teensy que hayamos conectado, en nuestro caso el Teensy 4.1. Cuando pulsamos el boton señalado en el microcontrolador entramos en el modo de programación y podemos cargar el programa que hayamos realizado desde Arduino. Debajo de la imagen nos señala el nombre del achivo de Arduino que contiene el programa que está cargado en el Teensy. Una vez cargado el programa se mantiene en el Teensy hasta que lo sobrescribamos, aunque no esté conectado a la fuente de energía. Si está conectado y no está en el modo de programación estará ejecutando el programa que tenga cargado. 2.5 ENTORNO DE DESARROLLO Qt Qt es un entorno de trabajo o framework que se utiliza para desarrollar todo tipo de programas. Es muy potente ya que tiene una estructura de código propia y permite utilizarla en distintas plataformas. Tiene muchas facilidades para desarrollar interfaces gráficas de usuario (GUI) y es por eso que lo hemos elegido para programar el software que controlará la FPGA desde el PC, ya que necesitará una interfaz desde donde lo podamos manejar [9]. 2.5.1 Descarga e instalación La empresa ofrece una licencia de pago y otra de código abierto gratuita. Tienen una filosofía de sofware comunitario y libre y puesta en común de conocimientos que se reflejan en sus condiciones de compartir libremente el software que se genere con este tipo de licencias de código abierto. Puesto que nosotros no tenemos intención de monetizar este proyecto ni estamos trabajando para una empresa elegiremos este tipo de licencia. CAPÍTULO 2 ANTECEDENTES Y DESARROLLO TEÓRICO PABLO SERNA BRAVO 18 Para descargarla se accede a su página web (www.qt.io/download) y se selecciona la licencia de código abierto. Detecta automáticamente el sistema operativo y ofrece la descarga del instalador correspondiente, aunque da la opción de elegir otra si fuese necesario. Si se ejecuta, el instalador pedirá crear una cuenta gratuita en Qt simplemente utilizando un correo electónico y verificará que conocemos las condiciones de la licencia que estamos utilizando, así como que no estamos utilizandola para ninguna empresa. Más adelante deberemos señalar el directorio en el que queremos instalar Qt y el tipo de instalación. Es recomendable realizar una instalación personalizada y elegir únicamente la versión y los módulos que se vayan a utilizar en el proyecto, ya que son archivos que ocupan mucho espacio de almacenamiento. Para este proyecto hemos descargado la versión 6.2.4, la más actualizada en el momento de la descarga, y hemos excluido los módulos de desarrollo de aplicaciones para Android y desarrollo 3D. Es recomendable guardar el instalador ejecutable ya que nos permitirá modificar los módulos instalados sin necesidad de reinstalar el programa. Para que Qt funcione correctamente una vez instalado hace falta dar un último paso [24]. Debemos buscar “Editar las variables de entorno del sistema” en nuestro buscador de aplicaciones del PC, lo que abriría una pestaña similar a la Figura 16. Figura 16 Entorno de propiedades del sistema de Windows 10 CAPÍTULO 2 ANTECEDENTES Y DESARROLLO TEÓRICO PABLO SERNA BRAVO 19 Si pinchamos en variables de entorno nos aparece una nueva ventana en la que debemos crear una nueva variable del sistema, llamarla QTDIR y conectarla al dirrectorio donde está instalado Qt, como se puede ver en la Figura 17. Figura 17 Nueva variable del sistema para Qt Una vez hecho esto podremos utilizar el entorno de Qt para nuestro proyecto. 2.5.2 Estructura de programación El entorno de Qt está diseñado para programar en C++, aunque también permite otros lenguajes. Desarrolla una estructura de programación orientada a objetos, es decir, crea elementos a los que se asocian atributos concretos y comportamientos u operaciones con los que podemos trabajar. Estos objetos se agrupan en clases. Las clases son estructuras que definen el estado y comportamiento de un grupo de objetos. Una clase contiene una función constructora que nos permite crear nuevos objetos de la clase. Las clases pueden definir variables (atributos) y funciones (operaciones) para los objetos, lo que nos permite caracterizarlos y trabajar con los mismos. Además, los clasifica en públicos y privados, permitiendonos decidir si se pueden utilizar fuera de la clase o solo dentro de ella, respectivamente. Una clase puede heredar de otras clases estos elementos para poder utilizarlos dentro de la misma [10]. El corazón de la estructura de Qt es su clase QObject. La caracrerística principal de su modelo de objetos es la comunicación directa ente objetos que permiten las signals o señales y los slots o ranuras. Esta característica permite unir objetos entre sí sin necesidad de que estén relacionados [11]. Las señales se emiten cuando cambia el estado de un objeto de alguna forma que pueda ser útil conocer. Las señales se pueden emitir desde cualquier punto de nuestro programa. Las funciones slot son prácticamente iguales a las funciones miembro de una clase de C++, por lo que se las puede llamar directamente. Sin embargo, las funciones slots pueden estar conectadas a señales. Una señal puede llamar a CAPÍTULO 2 ANTECEDENTES Y DESARROLLO TEÓRICO PABLO SERNA BRAVO 20 un slot independientemente de si este es público o privado y de la clase a la que pertenezca. Para conectar señales y slots utilizamos la función “connect”. Esta función tiene cuatro argumentos: • Puntero al objeto al que pertenece la señal (emisor) • Señal del objeto • Puntero al objeto al que pertenece el slot (receptor) • Slot al que está conectada la señal Y por tanto se define como connect(*emisor, señal, *receptor, slot). Así, cuando se emite la señal en el emisor, el slot del receptor se ejecuta instantáneamente. Una misma señal puede estar conectada a diferentes slots y un slot puede estar conectado también a diferentes señales. Incluso pueden conectarse señales entre sí para que se emita la segunda cuando se emite la primera. También pueden desconectarse elementos, pero no suele ser necesario ya que la conexión desaparece cuando se destruye alguno de los objetos implicados. Para que un objeto pueda utilizar las señales y los slots debemos incluir la macroinstrucción Q_OBJECT en la definición de su clase, que define la estructura gracias al Sistema de Meta-Objetos de Qt [12]. 2.5.3 Interfaz de usuario En Qt se genera una interfaz gráfica de usuario gracias a la clase QGuiApplication, que gestiona y controla esta interfaz y sus interacciones. Por debajo de este nivel aparecen los widgets, o “Windows gadgets”, que son todos los elementos que conforman la interfaz gráfica. Los widgets se estructuran como “padres” e “hijos” según la relación de pertenencia. Cuando se genera un widget hijo (por ejemplo, un botón) sobre un widget padre (por ejemplo, una ventana) aparece también una relación de dependencia que hace que el hijo solo exista mientras lo haga el padre. Con las diferentes clases de widgets y sus atributos y relaciones se puede construir la interfaz gráfica de usuario al completo. CAPÍTULO 2 ANTECEDENTES Y DESARROLLO TEÓRICO PABLO SERNA BRAVO 21 2.5.4 Estructura externa Al igual que otros entornos de desarrollo de C++, Qt nos permite crear proyectos completos que divide en los siguientes archivos: • Archivo de proyecto (.pro): Define los módulos de Qt que queremos incluir en el proyecto, así como el estandar de C++ y el resto de archivos que contiene. • Archivos de cabecera (.h): Contiene las definiciones de variables, funciones y clases que se utilizan en el proyecto. • Archivos de código (.cpp): Contienen el desarrollo del código del proyecto. • Archivos de interfaz de usuario (.ui): Son únicos de la herramienta de desarrollo Qt Creator y albergan los widgets y la disposición de la GUI en fórma de código. La herramienta Qt Creator nos permite crear widgets y posicionarlos en una interfaz cómoda y visual. Además, permite nombrarlos y generar las señales que nos interesen de cada widget, lo que hace mucho más sencilla la gestión de la interfaz. Al ser el primer proyecto en Qt realizado se ha decidido utilizar esta herramienta por ser más intuitiva. 2.6 ARQUITECTURA DE LA FPGA Las FPGA o dispositivos lógicos programables en campo son dispositivos electrónicos basados en semiconductores que pueden ser programados para contener cualquier circuito o sistema. Su principal característica es la capacidad para ser reprogramados en cualquier momento y de forma rápida para cambiar el circuito de su interior [1], [13], [14]. Funcionan con bloques lógicos configurables (CLB) que implementan funciones lógicas, lo que nos permite reproducir cualquier estructura de puertas lógicas. Hay de distintos tipos para desempeñar funciones distintas, por ejemplo, de lógica general, de memoria y multiplicadores. Estos bloques se estructuran en una matriz bidimensional que supera habitualmente los 100.000 bloques lógicos. Los bloques se interconectan entre sí mediante recursos de interconexión programables que conectan las funciones lógicas formando una estructura mayor capaz de reproducir cualquier dispositivo electrónico complejo. Finalmente, los bloques de entrada/salida, que se conectan al resto del circuito y permiten conectar la FPGA con elementos externos. CAPÍTULO 2 ANTECEDENTES Y DESARROLLO TEÓRICO PABLO SERNA BRAVO 22 Figura 18 Estructura general de una FPGA [1] La lógica de los bloques y las interconexiones solo se mantiene mientras la FPGA está alimentada, por lo que en muchos casos incorpora una memoria SRAM que almacena la configuración y la carga cada vez que encendemos la FPGA. Para este proyecto empleamos una FPGA de XILINX modelo Spartan 3E-100 [15] integrada en una placa Basys 2 de DILIGENT [16]. El diseño interno de la FPGA y el protocolo de comunicación no forma parte de este trabajo, sino que ha sido realizado por otro equipo de trabajo que nos ha dado las especificaciones necesarias para que podamos comunicarnos con la FPGA desde el Teensy. La FPGA se comportará como una máquina de estados, manteniéndose inicialmente en un estado inactivo, pero siempre atenta a las señales que le llegan. Cuando le lleguen instrucciones, pasará a un estado distinto que procesará las órdenes del Teensy y volverá al estado inicial cuando termine. Para comunicarnos con la FPGA conectaremos seis cables a sus pines de entrada/salida que nos servirán para enviar las instrucciones y los datos a través de “bit banging”, que consiste en controlar el valor (1 ó 0) de pines conectados entre sí y detectar los cambios que se producen para enviar mensajes. Estos pines estarán divididos en dos pines de control llamados WrA y RdD que recibirán las instrucciones y cuatro pines de datos (D0 a D3) que intercambiarán cuatro bits cada vez. Además, la FPGA tendrá que estar CAPÍTULO 2 ANTECEDENTES Y DESARROLLO TEÓRICO PABLO SERNA BRAVO 23 conectada a la tierra del Teensy a través de alguno de los pines GND. En la Figura 19 podemos ver representados los cables con los colores que hemos utilizado en realidad y el nombre que les corresponde. Figura 19 Conexiones a la FPGA Como hemos visto, intercambiar datos con la FPGA cuando está en funcionamiento es esecialmente leer y escribir en espacios de memoria, por lo tanto, en nuestro caso está programada para que podamos leer y escribir una dirección de memoria y leer y escribir datos en la dirección que está guardada. La dirección de memoria tendrá 32 bits, de los cuales los 8 más significativos corresponderan con el “device” o dispositivo y los otros 24 con la dirección dentro de ese dispositivo. Al ser el objetivo inicial establecer una comunicación fiable y robusta por ahora solo está programada con dos dispositivos: • Un registro de 4 bits que enciende los LEDs del 0 al 3 (ver Figura 19) que corresponde con el device 0. • Una memoria de 4 Kbytes que corresponde con el device 5. La información se intercambiará en grupos de 4 bits, tomando el orden de los menos significativos a los más siginificativos. CAPÍTULO 2 ANTECEDENTES Y DESARROLLO TEÓRICO PABLO SERNA BRAVO 24 Los pines WrA y RdD los controlará el Teensy y estarán normalmente a ‘1’. Cuando queramos hacer una escritura pasará a ‘0’ primero WrA y después RdD, y el orden será el contrario en las lecturas. Una vez definido si es una lectura o escritura se enviarán pulsos de WrA para intercambiar bits de dirección o pulsos de RdD para intercambiar bits de datos, transfiriendo 4 bits por flanco ascendente y descendente. Para finalizar el acceso, ambas señales subirán a la vez y quedarán en el estado inactivo a ‘1’. Representaremos el valor de los pines como una línea que sube al estado ‘1’ y baja al estado ‘0’, y los pines de datos como bloques que intercambian 4 bits a través de los pines D0 a D3. Las cuatro instrucciones admitidas por la interfaz son las siguientes: 2.6.1 Escribir una dirección Bajará primero WrA y después RdD. Para intercambiar bits cambiará de estado WrA. Por cada flanco de subida o bajada se escribirán 4 bits de la dirección, por lo que tendrá que realizar 4 pulsos de WrA para escribir la dirección completa. Finalmente, se detecta el fin de la escritucra cuando tanto WrA como RdD ascienden al estado inactivo ‘1’. Figura 20 Bit banging de la escritura de dirección 2.6.2 Leer una dirección Bajará primero RdD y después WrA. Para intercambiar bits cambiará de estado WrA. Por cada flanco de subida o bajada se leerán 4 bits de la dirección, por lo que tendrá que realizar 4 pulsos de WrA para leer la dirección completa. Finalmente, se detecta el fin de la lectura cuando tanto WrA como RdD ascienden al estado inactivo ‘1’. CAPÍTULO 2 ANTECEDENTES Y DESARROLLO TEÓRICO PABLO SERNA BRAVO 25 Figura 21 Bit banging de la lectura de dirección 2.6.3 Escribir datos Bajará primero WrA y después RdD. Para intercambiar bits cambiará de estado RdD. Por cada flanco de subida o bajada se escribirán 4 bits de datos, por lo que se realizarán tantos pulsos de RdD como bytes a escribir. Finalmente, se detecta el fin de la escritucra cuando tanto WrA como RdD ascienden al estado inactivo ‘1’. La dirección se autoincrementa con cada byte escrito. Figura 22 Bit banging de la escritura de datos 2.6.4 Leer datos Bajará primero RdD y después WrA. Para intercambiar bits cambiará de estado RdD. Por cada flanco de subida o bajada se leerán 4 bits de datos, por lo que CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 32 Figura 27 Fragmento de código para conocer la dirección MAC Este fragmento de código extrae la dirección y la guarda en un vector de 6 bytes para después sacarla por pantalla como vemos en la Figura 28. Figura 28 Salida por pantalla de la dirección MAC del Teensy 4.1 Nos entrega la dirección MAC en hexadecimal con los bytes separados por dos puntos. 3.3.3 Direcciones IP También debemos decidir las direcciones IP que se le asignarán tanto al Teensy como al PC. En nuestro caso, podemos dejar que sea el router el que asigne automáticamente las direcciones cuando se conecten los dispositivos, pero para ganar mayor control sobre el proceso y orientado hacia la conexión objetivo que será sin router, fijaremos nosotros estas direcciones. En este caso la forma más sencilla de hacerlo es desde el propio router. Si accedemos a la primera dirección de la red (en este caso la 192.168.1.1) nos dirige a la interfaz del router y podemos modificar la dirección IP que asigna a cada dispositivo. Normalmente solo es necesario iniciar sesión con la contraseña del router y acceder a la configuración avanzada. Asignaremos al PC como dirección estática la dirección que ya le asignaba el router para asegurarnos de que no pertenece a ningún otro dispositivo. En este caso es la 192.168.1.150. En el caso objetivo en el que no hay un router de por medio podemos hacer que el PC sea quien se asigne una dirección IP a sí mismo. Para ello, una vez conectado el cable Ethernet, debemos acceder a Panel de control -> Redes y configuración -> Centro de redes y recursos compartidos y hacer clic sobre la conexión Ethernet. Esto nos abrirá una pestaña de estado de Ethernet en la que deberemos clicar en el botón “propiedades”. Finalmente, buscaremos el protocolo de internet versión 4 (TCP/IP v4) y de nuevo clicaremos en sus propiedades. Debería aparecernos una ventana similar a la Figura 29 en la que podremos fijar los valores que necesitamos. CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 33 Figura 29 Propiedades de la dirección IP v4 Windows 10 En esta ventanda podremos fijar la dirección IP que queremos que el PC se asigne, así como la máscara de subred y la puerta de enlace (debemos dejar las predeterminadas). La máscara de esta red local es la 255.255.255.0, la puerta de enlace la 192.168.1.1 y el servidor DNS el 192.168.1.1. No son relevantes ya que vamos a funcionar siempre dentro de la red local, pero de nuevo las fijaremos para tener control sobre el proceso. En el caso del Teensy, fijaremos la dirección IP que se fija él mismo por defecto, la 192.168.1.177. Se ha verificado la validez de la IP y el buen funcionamiento de la conexión Ethernet empleando los ejemplos que incorpora Teensyduino sobre la clase NativeEthernet, en particular WebServer y WebClient, a los que se puede acceder desde el entorno de Arduino como aparece en la Figura 30. CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 34 Figura 30 Ejemplos Ethernet Teensyduino 3.3.4 Puerto de enlace Tanto el Teensy como el PC deben de utilizar el mismo puerto virtual por el que intercambian información para que puedan comunicarse. En este caso se ha elegido el puerto 35550, por ser del orden de los puertos que elige el PC al ejecutar los ejemplos de Qt que emplean las clases de comunicación Ethernet, fortuneclient y fortuneserver, que podemos encontrar en la carpeta Examples -> network de Qt. 3.3.5 Codificación de caracteres Aunque entre el Teensy y el PC solo se van a intercambiar bytes, para nosotros puede que quieran representar caracteres y por lo tanto tienen que poder codificarlos en el mismo lenguaje. Arduino utiliza por defecto el lenguaje de codificación UTF-8, por lo que mantendremos ese lenguaje y utilizaremos la clase de Qt QTextStream, que CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 35 traduce correctamente los caracteres a este estándar y gestiona también las lecturas independientemente del lenguaje utilizado. 3.4 CLIENTE EN Qt Nuestro programa en Qt se estructura alrededor de la clase Teensy que hemos creado, que envía los mensajes al microcontrolador. Para crearla y poder gestionar la conexión TCP utilizamos la clase QTcpSocket que nos aporta Qt, que permite crear un objeto que gestiona la entrada y salida de mensajes TCP (socket). Para crear un proyecto en el que podamos diseñar una interfaz de usuario tipo ventana con la clase QWidget creamos un proyecto nuevo desde File -> New Project… y nos aparece una ventana similar a la Figura 31. Figura 31 Nuevo proyecto Qt Creator En esta ventana seleccionamos un proyecto tipo aplicación y con una interfaz de usuario (Qt Widgets Application). Deberemos definir el nombre y la localización del proyecto, así como el sistema de compilación (built system), del que debemos escoger qmake. Escogeremos ahora el tipo de clase que queremos crear para manejar la interfaz de usuario, en este caso QWidget. Dejamos el resto de configuraciones por defecto y finalizamos. Antes de empezar el proyecto debemos añadir los módulos de Qt que vamos a utilizar en el archivo de proyecto (.pro), en nuestro caso hemos utilizado core CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 36 para las clases de la aplicación, gui para las de la interfaz de usuario y network para el resto. 3.4.1 Clase Teensy El proceso para crear una nueva clase en el proyecto es similar. En este caso debemos crear un archivo nuevo desde File -> New Project… como en la Figura 32. Figura 32 Nuevo archivo Qt Creator Escogemos una clase de C++ con un archivo de cabecera y uno de código. Para esta clase escogeremos que se base en QObject y añadiremos la macro de Q_OBJECT que nos permite utilizar signals y slots como explicamos en el capítulo 2.5.2. Qt nos crea los archivos, la clase y el constructor, e incluye la herencia de QObject. En el archivo de cabecera incluimos la clase QTcpSocket y declaramos las siguientes funciones como públicas: Figura 33 Funciones de la clase Teensy CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 37 Las funciones de lectura tienen como argumentos la dirección de la que leer y la longitud de datos, así como la variable donde se devolverán los datos pasada por referencia. Las de escritura tienen solo la dirección y los datos a escribir. Ambas funciones están definidas para recibir los datos como vectores de bytes (QByteArray) o como cadenas de caracteres (QString), y todas devuelven una confirmación de la acción como variable booleana. La función de escritura de vectores de bytes necesita argumentos que no sean constantes para poder gestionar mensajes grandes. Además, declaramos también las funciones para conectar y desconectar nuestro programa del PC delTeensy remoto. En la parte privada de la clase creamos un puntero al socket que utilizaremos para gestionar la comunicación y declaramos una variable de control que utilizaremos para esperar la respuesta en una lectura. Figura 34 Variables privadas de la clase Teensy En el archivo de código incluimos también las clases QDataStream, QTextStream y QNetwork, que vamos a utilizar para manejar vectores de bytes, cadenas de caracteres y eventos en la interfaz, respectivamente. En el constructor de la clase creamos un nuevo objeto de la clase QTcpSocket y se lo asignamos al puntero que habíamos creado. Además, en esta aplicación queremos que nada más crearse el socket se establezca la conexión, así que llamamos a la función que lo conecta al Teensy. Figura 35 Definición del constructor de la clase Teensy CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 38 También podemos ver que conectamos la señal de nuestro socket “readyRead”, que se emite cuando llega un nuevo mensaje al socket, a una función definida en línea que simplemente cambia nuestra variable de control “respondido” a verdadero. Así podemos detectar cuando llega una respuesta. Las funciones de conexión y desconexión utilizan funciones de la clase QTcpSocket, como podemos ver en la Figura 36. Figura 36 Funciones de conexión y desconexión de la clase Teensy La función “connectToHost” intenta conectar a un servidor TCP con una IP y un puerto determinados, en este caso los del Teensy. Con “waitForConnected” nos aseguramos de que esperamos a que el socket esté conectado y devolvemos una confirmación de si ha sido capaz o no de conectarse. Para la desconexión, la función “disconnectFromHost” espera a que el socket se vacíe para desconectarlo, por lo que no hace falta ninguna otra función. La función de lectura definida para una cadena de caracteres llama primero a la definición principal de la función, que nos entrega un vector de bytes, y después aprovecha la clase QTextStream para para convertir ese vector en una cadena de caracteres. Figura 37 Función de lectura para cadenas de caracteres de la clase Teensy Definiendo un objeto de la clase (en nuestro caso lo hemos llamado “TextStream data”) que maneje la variable que almacena la cadena de caracteres, podemos simplemente utilizar el operador “<<” para convertir CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 39 nuestro vector “ByteArray data” en la cadena “String data”. Mantenemos la variable booleana para comprobar que se ha ejecutado correctamente. La definición de la función de lectura para un vector de bytes comienza poniendo la variable de control de la respuesta a “falso” para poder saber cuando llega una respuesta nueva, y comprobando que la longitud que se pide no es mayor a 3 bytes. Figura 38 Función de lectura para un vector de bytes de la clase Teensy Para componer el mensaje utiliza la clase QDataStream, que funciona de forma similar a QTextStream. Primero creamos un vector de bytes (“mensaje”) y se lo asignamos a un objeto de la clase (“out”), en este caso con la condición de que solo vamos a escribir en él. Con el operador “<<” metemos en nuestro vector primero los bytes de control, asegurándonos que entran como caracteres de 8 bits. Después vamos a ir metiendo bytes de la dirección y la longitud desde el menos significativo al más significativo, empleando el “and” lógico (&) para coger solo un byte y el operador de desplazamiento de bits (>>) para desplazar los bytes y coger el que nos interesa. Introducimos la dirección de 4 bytes y 3 bytes de la longitud (ya que hemos comprobado que no es mayor de 3 bytes). CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 40 Una vez compuesto el mensaje se utilizan las funciones “write” y “flush” de la clase QTcpSocket para ordenar que se envíe el mensaje y esperar a que se envíe, respectivamente. En este punto hemos decidido que la función se quedará esperando a la respuesta (metodología “blocking”), por lo que tenemos un bucle que ejecuta la función “processEvents” de QCoreApplication, que permite a la aplicación ejecutar otros eventos de otras aplicaciones (por ejemplo, movimiento, interacción o cierre de la interfaz de usuario) para que no se quede “congelada” mientas espera a la respuesta. Cuando llega la respuesta, cambia la variable “respondido” y sale del bucle. Las funciones del Teensy tienen una limitación para enviar mensajes de 1460 bytes, por lo que tendremos que fragmentar la respuesta y enviarla al PC en varios paquetes. Desde Qt intentamos leer la longitud pedida con la función “read” de la clase QTcpSocket. Con la función “length” comprobamos cuántos bytes llevamos leídos y, si son menos de los totales a leer, intentamos leer los que nos faltan y los concatenamos con la función “append”. Para esperar a recibir nuevos paquetes volvemos a llamar a la función “processEvents”. Una vez hemos recibido la respuesta completa volvemos de la llamada de la función, confirmando que hemos leído los datos. Ahora definiremos de forma análoga las funciónes de escritura. Figura 39 Función de escritura para cadenas de caracteres de la clase Teensy En este caso el paso es de una cadena de caracteres a un vector de bytes, con la diferencia de que la función principal se llama después de hacer el cambio y por lo tanto debemos llamar antes a la función “flush” de QTextStream, ya que necesitamos que se haga la transformación sin esperar a volver de la función. En la función para el vector de bytes nos encontramos inicialmente con una limitación del tamaño de los mensajes. El Teensy no recibe correctamente mensajes de más de 12 Kbytes, por lo que debemos fragmentar nuestros mensajes a este tamaño. CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 41 Figura 40 Función de escritura para un vector de bytes de la clase Teensy Comenzamos la función comprobando si el mensaje ocupa más de 12 Kbytes con la función “length” del vector de bytes. Si es el caso, tomamos los primeros 12 Kbytes con la función “first” y los almacenamos en “data frag” para llamar de nuevo a la función de escritura. En este caso “data frag” cumple con el tamaño adecuado, por lo que no volverá a entrar en el bucle. Tras cada fragmentación, incrementamos las posiciones necesarias en la dirección y elminamos los datos que ya se han enviado con la función “remove”, por lo que quedarían ambas variables listas para continuar con la escritura en el siguiente mensaje. Esto nos permite enviar los bytes que queramos en una sola petición y que sea la clase la que se encargue de gestionar las limitaciones del Teensy. El resto de la función compone un mensaje similar al de la lectura y concatena el vector de datos al final de este con la función “append” como vemos en la Figura 40. En este caso no hace falta esperar a la respuesta. 3.4.2 Interfaz de usuario Si vamos a la barra de herramientas vertical de la derecha en el entrono de desarrollo de Qt podemos ver un apartado que se denomina “Design”. En él, hemos creado nuestro diseño para la interfaz de usuario que se puede ver esquematizado en la Figura 41. CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 48 3.5 SERVIDOR CON TEENSYDUINO El programa para el Teensy utiliza la librería que incorpora el software de Teensyduino NativeEthernet, desarrollada por un usuario e incorporada como parte del producto por el fabricante del microcontrolador [26]. Para la comunicación con la FPGA utiliza sus pines de entrada y salida a base de “bit banging” como explicamos en el capítulo 2.6. Para crear un nuevo proyecto en Arduino pinchamos en la pestaña de Archivo -> Nuevo. Los proyectos en Arduino se estructuran como una función “setup” que se ejecuta al alimentar al Teensy con corriente, en la que configuramos los elementos que queramos utilizar y hacemos las comprobaciones previas necesarias; y la función “loop”, un bucle que se repite constantemente mientras el microcontrolador esté conectado. Nuestro proyecto se dividirá en las declaraciones previas, la función setup, la función loop y las funciones de lectura y escritura en la FPGA. 3.5.1 Declaraciones previas Lo primero que debemos hacer es incluir el archivo de cabecera de la librería, NativeEthernet.h, para poder utilizar las clases que contiene. Después, declaramos las diferentes variables que vamos a utilizar para la conexión TCP, la gestión de mensajes y el bit banging. Figura 49 Declaración de las direcciones necesarias para el servidor Lo primero que declaramos es la dirección MAC de nuestro microcontrolador en forma de vector de bytes y después las direcciones IP del servidor, del servidor DNS, la puerta de enlace y la máscara de subred, de las que hablamos en el capítulo 3.2 de pasos previos. Para declararlas aprovechamos el tipo de variable “IPAddress” que nos permite definir direcciones IP de 4 bytes. Finalmente creamos un objeto de la clase EthernetServer que llamaremos “server”, que será el servidor encargado de escuchar los mensajes del cliente gracias a un socket por el puerto que hemos designado. CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 49 A continuación, declaramos las variables que vamos a utilizar para el loop, que podemos ver en la Figura 50. Figura 50 Declaración de las variables de gestión de mensajes en el Teensy Las variables son las siguientes: • Un vector de 4 bytes llamado “buffer” que sirve de registro intermedio para la dirección y la longitud. • Una variable tipo byte llamada “c” para leer byte a byte la información que va llegando hasta encontrar los caracteres de control “A” y “T”. • Otra variable tipo byte llamada “data” para gestionar los envíos a la FPGA. • Un vector de bytes de llamado “datos” que almacenan los datos que se escriben en la FPGA. Puede almacenar 12 Kbytes de datos ya que es el máximo que puede recibir la librería NativeEthernet. • Un vector de bytes llamado “respuesta” que almacena hasta 100 Kbytes de datos que se leen de la FPGA. Este es el límite de datos que hemos decidido que se puedan leer de una vez ya que es el óptimo en cuanto a desempeño. • Un vector de bytes llamado “envío” de 1460 bytes ya que es el máximo que puede enviar la librería. • Una variable de 32 bits llamada “bytes por enviar” que utilizaremos para dividir los datos de lectura en bloques de 1460 bytes. • Tres variables de 32 bits para almacenar la dirección, la comprobación de la dirección y la longitud. Debemos ahora decidir qué pines del Teensy se van a conectar a la FPGA. Es necesario que los pines se puedan utilizar a la vez que Ethernet, lo cual no será un problema ya que este tiene sus pines propios en el centro de la placa. Además, es conveniente que estén cerca de los pines de tierra para poder conectar las tierras de la FPGA. CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 50 En nuestro caso hemos elegido los pines juntos, de tal manera que se puedan cambiar varios pines a la vez escribiendo en la variable interna de los puertos que tiene el Teensy. En este proyecto la velocidad no es un objetivo, por lo que no se utilizará esta característica, sino una función ya preparada de Arduino, sin embargo, queremos que el prototipo pueda estar listo para versiones futuras. Se han elegido los pines 0 y 1 para las señales WrA y RdD y del 2 al 5 para D0 – D3 por estar juntos, poderse controlar desde un mismo puerto (ver anexo VII) y estar junto al pin de tierra. Se conectarán también los dos pines de tierra de la parte del Teensy cercana a su puerto USB. Se puede ver la definición de las variables de los pines en la Figura 51 y un esquema de la conexión en la Figura 52. Figura 51 Definición de los pines del Teensy Figura 52 Esquema de conexión Teensy-FPGA CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 51 Por último, declaramos las funciones que controlan la FPGA. Figura 53 Declaración de las funciones de control de la FPGA Las cuatro funciones se corresponden con los cuatro tipos de entradas a la FPGA, de lectura/escritura de direcciones o datos, que vimos en el capítulo 2.6. Las que manejan direcciones tienen como argumento una variable de 32 bits de la que leen o en la que escriben la dirección (por referencia). Las que manejan datos reciben un puntero a un vector de bytes de datos del que leen o en el que escriben los datos y la longitud a escribir o leer de la FPGA. 3.5.2 Función setup Lo primero que debe hacer el Teensy es configurar los pines conectados a la FPGA para evitar que ambos escriban en estos pines y provoquen un cortocircuito, así como para dar el estado inicial a partir del cual se intercambian los mensajes. Para ello utilizamos las funciones de Arduino “pinMode” y “digitalWrite”, que nos permiten establecer el modo de los pines (entrada/salida) y escribir en ellos. Figura 54 Valor inicial de los pines del Teensy Los pines WrA y RdD los definimos como salidas (OUTPUT) e inicialmente estarán a ‘1’ o en “HIGH”. Los pines de DATA los definimos como entradas de bajo consumo (INPUT_PULLUP) para que no intenten escribir a la vez que la FPGA, pero tampoco demanden corriente de ella. CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 52 En este proyecto vamos a utilizar el monitor serie de Arduino para enviarnos mensajes de control desde el Teensy, aprovechando que el propio cable de alimentación nos ofrece la conexión serie. Esto nos permite conocer las conexiones y peticiones del cliente sin necesidad de ejecutarlo en el mismo PC. Para una fase más avanzada del proyecto en la que no se alimente al Teensy desde un ordenador, se puede simplemente eliminar el control serie sin perjudicar al resto del código. Figura 55 Inicialización de la comunicación con el monitor serie La comunicación con el monitor serie de Arduino se establece a través de la clase Serial. Para iniciar la comunicación utilizamos la función “begin” en el puerto que Arduino utiliza para esta conexión, el 9600. Después, esperamos a que se establezca la conexión con un bucle que comprueba el estado y cuando se establece la conexión sacamos por pantalla el nombre del programa. Para sacar tanto texto como variables por el monitor serie utilizamos las funciones “print” y “println”, diferenciándose esta segunda únicamente en que añade un salto de línea al final de la escritura. Por último, para comenzar la comunicación con el cliente, debemos definir el tamaño del socket que utilizamos y comenzar la comunicación TCP y el servidor. Figura 56 Inicialización de la comunicación TCP Para ello hemos utilizado la clase Ethernet de la librería NativeEthernet, que nos permite fijar el tamaño de nuestro socket en el límite de 12K con la función “setSocketSize” y comenzar la conexión TCP/IP con la función “begin”, utilizando las direcciones IP que hemos definido anteriormente. CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 53 Para iniciar el servidor utilizamos el objeto de la clase EthernetClient que hemos creado y la función “begin”. Finalmente, sacamos por pantalla un aviso con la IP que estamos utilizando para saber que el servidor está conectado. 3.5.3 Función loop En el loop tenemos la parte principal del código, que se encarga de detectar cuándo llegan mensajes del PC y gestionarlos según sean lecturas o escrituras. Debido a que el Teensy se queda ejecutando esta parte del código indefinidamente debemos entregarle algo de tiempo para que pueda hacer labores internas del microcontrolador como mantenimiento, atención a entradas y salidas, etc. Para ello vamos a utilizar la función “delay” de Arduino que nos permite ceder el control al procesador durante una cantidad de milisegundos. Inicialmente vamos a dejar tiempos grandes para que el programa funcione y sea robusto, que se podrán ir reduciendo cuando se quiera mejorar el programa en velocidad. Inicialmente vamos a esperar a que se conecte algún cliente, por lo que intentaremos crear un nuevo objeto de la clase EthernetClient con la función “accept” de nuestro servidor. Figura 57 Espera al cliente en la función loop Entre intento de crear clientes o entre clientes reales dejamos 100 milisegundos al procesador. Cuando se crea un nuevo cliente, nos avisa por el monitor serie y lo identifica sacando su IP por pantalla con la función “remote IP”. Una vez conectado un cliente, estaremos atentos únicamente a él hasta que este se desconecte, ya que el programa está orientado a un solo cliente en el PC. Comprobaremos que sigue conectado entre mensaje y mensaje con la función “connected” que podemos ver en la Figura 58. Solo interpretaremos los mensajes cuando nos hayan enviado al menos 10 bytes, que corresponden CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 54 a los bytes de control, la dirección y la longitud. Esto se comprueba con la función “available”. Figura 58 Comprobación de cliente conectado e instrucción recibida Ahora utilizamos la variable “c” y la función “read”, que lee la información que llega del PC byte a byte, para comprobar cada byte que nos va enviando el cliente. Si detectamos nuestros bytes de control (AT) significa que comienza una nueva instrucción, por lo que el siguiente byte indica si es una instrucción de lectura (R) o de escritura (W). Avisamos del nuevo mensaje por el monitor serie y distinguimos estos casos utilizando una estructura switch. El primer caso que hemos desarrollado es el de escritura, que tiene que leer del PC la dirección, llamar a la función de escritura de dirección en la FPGA, leer la longitud, leer los datos y llamar a la función de escritura de datos en la FPGA. Por último, comprobará la dirección en la que ha acabado la FPGA con la función correspondiente para asegurarse de que ha escrito la longitud indicada. Figura 59 Lectura de dirección del mensaje del PC y escritura a la FPGA CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 55 Comenzamos avisando de la lectura por el monitor serie y leyendo cuatro bytes del mensaje en el buffer auxiliar que vamos a utilizar para componer la dirección. En este caso utilizamos la función “read” pasando como argumento el lugar donde guardar los bytes que se lean y el número de bytes a leer. Sabiendo que los mensajes envían primero el byte menos significativo y el último el más significativo, vamos a ir leyendo del más al menos significativo y desplazando 8 bits y sumando en cada lectura para componer la dirección tras cuatro iteraciones. Llamamos a la función de escribir dirección en la FPGA y comprobamos la respuesta. Si la lectura ha salido bien avisamos en el monitor serie de la dirección (los tres bytes menos significativos) y el device (byte más significativo) en el que se va a escribir. Si sale mal avisamos del error. No se ha programado en esta etapa del proyecto una gestión de errores con retroalimentación o que repita el código fallido ya que por cómo está programada la FPGA no podrá detectar errores en su propia ejecución. En una fase más avanzada de la línea de trabajo a la que pertenece el proyecto, si se programa la FPGA para detectar estos errores, se debería añadir la gestión de errores en vez de los avisos por el monitor serie. La lectura de la longitud es similar a la de dirección pero de tres bytes, como podemos ver en la Figura 60. Figura 60 Lectura de longitud del mensaje del PC A continuación, debemos esperar a haber recibido el número de bytes que indica la longitud para poder leerlos. Para ello utilizamos la función “available” y la longitud en forma de entero con signo. Cedemos el control al procesador mientras se espera para poder recibir los bytes. Finalmente, leemos la cantidad de bytes de datos que nos indica la longitud y llamamos a la función de escritura en la FPGA. CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 56 Figura 61 Lectura de datos del PC, escritura en la FPGA y comprobación Para comprobar que se han escrito el número de bytes que indica la longitud llamamos a la función de lectura de dirección de la FPGA para saber en qué dirección ha acabado con la variable “comprobar dirección” y le restamos la dirección final. Si la resta no coincide con la longitud, emitimos un mensaje de error. El caso de lectura utiliza el mismo código, con la excepción de que no necesita leer datos del mensaje si no que llama a la función de lectura de datos de la FPGA solo con el argumento de la longitud a leer y la variable respuesta en la que queremos guardar esos datos. La comprobación de la dirección final es idéntica. Sin embargo, es necesario añadir un fragmento de código que divida la respuesta en mensajes de 1460 bytes para poder enviarlos al cliente. CAPÍTULO 3 DESARROLLO DEL PROYECTO PABLO SERNA BRAVO 57 Figura 62 Envío de la respuesta dividida en paquetes Comenzamos inicialmente fijando los bytes por enviar en la longitud total de datos pedidos. Creamos una variable auxiliar “i” que utilizaremos como puntero del vector de bytes “respuesta”. Queremos tomar 1460 bytes de la respuesta y almacenarlos en envío mientras queden más de 1460 bytes por enviar. Con la función “write” de nuestro objeto client enviamos los 1460 bytes almacenados en “envío”, restamos esos bytes de los que quedan por enviar y desplazamos esas posiciones nuestro puntero “i”. Cuando quedan menos de 1460 bytes enviamos los que queden. Por último, cuando el cliente se desconecta de nuestro servidor, salimos de los bucles y desconectamos el cliente con la función “stop”, como vemos en la Figura 63. Figura 63 Desconexión del cliente El procesador quedaría entonces esperando a un nuevo cliente para volver a empezar el proceso. 3.5.4 Funciones de comunicación con la FPGA Basándonos en las instrucciones que la FPGA es capaz de interpretar que describimos en el capítulo 2.6 vamos a utilizar el bit banging para enviarlas CAPÍTULO 4 RESULTADO FINAL, TESTEO Y VERIFICACIÓN PABLO SERNA BRAVO 64 monitor serie de Arduino e introducir las entradas necesarias para comprobar que el proyecto funciona. Por ahora, la FPGA nos permite escribir en el device 0 para acceder a los LEDs y al device 5 para acceder a una pequeña memoria, como explicamos en el capítulo 2.6. Vamos a acceder primero al registro del device 0 y escribir un número para observarlo en los LEDs. Figura 72 Verificación del programa escribiendo en los LEDs Figura 73 Reacción a la escritura en los LEDs de la FPGA Como podemos ver en la Figura 73, se encienden los leds LD0 y LD2, mientras que el LD1 y el LD3 se quedan apagados, como corresponde al número ‘0101’ en binario, es decir, el número 5 en decimal. (Por cómo está programada la interfaz el programa está escribiendo el código UTF-8 del número 5 en 8 bits, cuyos 4 bits menos significativos corresponden al número 5 en binario). CAPÍTULO 4 RESULTADO FINAL, TESTEO Y VERIFICACIÓN PABLO SERNA BRAVO 65 Mientras tanto, el monitor serie de Arduino nos avisa de la conexión y del mensaje de escritura (Figura 74). Figura 74 Respuesta del monitor serie a la escritura en los LEDs Ahora escribiremos en la primera dirección de memoria del device 5 un mensaje y lo leeremos de vuelta. Figura 75 Escritura en la memoria Figura 76 Lectura de la memoria CAPÍTULO 4 RESULTADO FINAL, TESTEO Y VERIFICACIÓN PABLO SERNA BRAVO 66 Figura 77 Respuesta del monitor serie a la escritura y lectura en la memoria En el monitor serie podemos ver los avisos de conexión, escritura de 11 bytes y lectura de estos en la dirección 1 del device 5. En general, se han probado los programas para todos los valores que se pueden leer y escribir en un byte y para todas las posiciones de memoria a las que se tiene acceso en la FPGA, con resultados favorables y estables. 4.3 TESTEO DE VELOCIDAD Y FLUJO DE DATOS Para poder conocer las limitaciones del proyecto y ser capaces de definir las líneas futuras necesitamos conocer qué cantidad de bytes puede transmitir y sobre todo a qué velocidad. Hemos creado un nuevo proyecto en Qt que compone un mensaje de la longitud que elijamos y lo envía tantas veces como queramos al Teensy. Al comenzar a transmitir se conecta al servidor y al terminar se desconecta para poder medir en el Teensy el tiempo que tarda. Vemos la función “main” en la Figura 78. CAPÍTULO 4 RESULTADO FINAL, TESTEO Y VERIFICACIÓN PABLO SERNA BRAVO 67 Figura 78 Código de testeo de velocidad de la comunicación Incluimos nuestra clase Teensy y la clase QThread, que utilizaremos para introducir retardos. Definimos un objeto de nuestra clase, un tamaño de paquetes y un número de repeticiones para el test. Escribiremos en el device 5 desde la primera dirección de memoria, por lo que le asignamos la dirección correspondiente en hexadecimal. Vamos a manejar más bytes de los que caben en la memoria programada en la FPGA, por lo que en la realidad solo se escribirán los primeros 4 Kbytes, sin embargo, nos dará una idea del tiempo que tarda el proceso de escritura y lectura del Teensy. Para componer el mensaje declaramos un vector de bytes y lo inicializamos con un byte cualquiera (en este caso el número 1F en hexadecimal) con un bucle for. Comprobaremos la escritura y la lectura por separado, por lo que la función que no estemos utilizando la comentaremos (en este caso la función read). Se repetirá la lectura o la escritura el número de veces que hayamos fijado. Hemos observado que al enviar varios paquetes seguidos el Teensy se satura, por lo que se añade un retardo en milisegundos con la función “msleep” de la clase QThread para ralentizar el flujo de mensajes que envía Qt, con el objetivo CAPÍTULO 4 RESULTADO FINAL, TESTEO Y VERIFICACIÓN PABLO SERNA BRAVO 68 de mejorar el desempeño del Teensy recibiendo y gestionando los mensajes. Se ha añadido este mismo retardo en la fragmentación en paquetes de 12 Kbytes que hace la función de escritura de la clase Teensy. Finalmente nos desconectamos del servidor para que este pueda medir el tiempo que ha durado el intercambio. En el código de Arduino hemos añadido algunas variables para detectar si ha sido una lectura o escritura, medir el tiempo que tarda el intercambio de datos y el número de bytes que se han intercambiado para poder hallar la velocidad (Figura 79). Figura 79 Variables añadidas para medir la velocidad en el Teensy Cuando se conecta un nuevo cliente esperamos a que mande el primer mensaje y comenzamos a contar el tiempo y los bytes que se van enviando. Para medir el tiempo utilizamos la función “micros”, que nos devuelve los microsegundos que han pasado desde que se inició el Teensy. Tomaremos la medida al comenzar la conexión y al desconectarse para calcular la duración con la diferencia. Figura 80 Inicialización del tiempo y los bytes Con cada mensaje recibido aumentaremos los bytes totales en la longitud de bytes escritos o leídos y sacaremos por pantalla un punto para asegurarnos de que el programa está funcionando correctamente. Finalmente calcularemos la velocidad de transmisión de datos en Kbytes por segundo y sacaremos un mensaje por pantalla según el modo que se haya probado, como podemos ver en la Figura 81. CAPÍTULO 4 RESULTADO FINAL, TESTEO Y VERIFICACIÓN PABLO SERNA BRAVO 69 Figura 81 Cálculo de velocidad de intercambio de datos Si ejecutamos el programa para un mensaje de 10 Kbytes tanto para la escritura como para la lectura la respuesta del monitor serie del Teensy es la que observamos en la Figura 82. Figura 82 Salida del Teensy para el caso de escritura y lectura de 100 Kbytes CAPÍTULO 4 RESULTADO FINAL, TESTEO Y VERIFICACIÓN PABLO SERNA BRAVO 70 Como podemos ver, el cliente en Qt se conecta y envía el mensaje de escritura. Cuando se desconecta nos indica que la velocidad de escritura ha sido de 214,94 Kbytes/s. Hace después otra conexión de lectura. En este caso la velocidad de lectura es de 141,82 Kbytes/s. Vamos a utilizar el programa para enviar diferente número de bytes y mensajes para comprobar los límites de velocidad que alcanza y sacar conclusiones. Hemos presentado los resultados en las Tabla 2 y Tabla 3. Tabla 2 Velocidad de escritura en Kbytes/s según el número de bytes y mensajes Velocidad de escritura (Kbytes/s) Número de bytes 1 10 100 1K 10K 100K Número de mensajes 1 0,09 0,9 8,69 64,1 214,94 1,44 10 0,09 0,9 8,7 64,16 2,51 ≤ 1,44 100 0,09 0,9 8,7 64,14 ≤ 2,51 ≤ 1,44 1K 0,09 0,66 6,54 58,32 ≤ 2,51 ≤ 1,44 10K 0,07 0,65 6,49 21,56 ≤ 2,51 ≤ 1,44 100K ≤ 0,07 ≤ 0,65 ≤ 6,49 ≤ 21,56 ≤ 2,51 ≤ 1,44 Como podemos ver, la velocidad de escritura de datos aumenta cuantos más bytes escribimos en un mismo mensaje, pero observamos una saturación del Teensy cuando mandamos demasiados mensajes seguidos y por tanto una velocidad menor. Esto ocurre cuando mandamos más de 1.000 mensajes pequeños, pero sobre todo cuando mandamos varios mensajes de 10 Kbytes, o al enviar un mensaje de 100 Kbytes, ya que el programa en PC lo tiene que dividir en mensajes de 12 Kbytes. Se han estimado algunos de los datos en los que la duración del test era excesiva. Tabla 3 Velocidad de lectura en Kbytes/s según el número de bytes y mensajes Velocidad de lectura (Kbytes/s) Número de bytes 1 10 100 1K 10K 100K Número de mensajes 1 0,02 0,31 7,17 55,41 141,82 255,94 10 0,03 0,5 2,85 37,61 196,1 324,5 100 0,05 0,52 4,84 35,31 194,16 339,25 1K 0,04 0,47 4,59 36,64 187,5 - 10K 0,04 0,47 3,74 35,72 ≤ 187,5 - 100K ≤ 0,04 ≤ 0,47 ≤ 3,74 ≤ 35,72 ≤ 187,5 - CAPÍTULO 4 RESULTADO FINAL, TESTEO Y VERIFICACIÓN PABLO SERNA BRAVO 71 En el caso de la lectura los datos son mucho más variables ya que dependen en mayor medida de la FPGA, sin embargo, los datos obtenidos nos servirán para hacernos una idea de la tendencia que toma. Podemos observar que para un numero pequeño de bytes las lecturas son más lentas que las escrituras y que de nuevo se vuelven algo más lentas cuantos más mensajes seguidos enviamos. Sin embargo, la saturación del Teensy casi no ocurre para los mensajes de lectura. La gran diferencia con la escritura es que es capaz de leer mensajes de 100 Kbytes mucho más rápido, debido a que el Teensy no tiene la limitación de tener que recibir varios mensajes y, aunque tenga que fragmentar la respuesta, los datos nos indican que puede hacerlo a una buena velocidad. Estos datos nos hacen pensar también que los mensajes de 100 Kbytes son los óptimos en cuanto a velocidad. CAPÍTULO 4 RESULTADO FINAL, TESTEO Y VERIFICACIÓN PABLO SERNA BRAVO 72 PABLO SERNA BRAVO 73 CAPÍTULO 5 CONCLUSIONES 5.1 CONCLUSIONES GENERALES Y TRANSVERSALES Una vez completado el proyecto, comprobado su funcionamiento y obtenidos unos resultados de velocidad, vamos a recapitular los objetivos que nos habíamos marcado y los avances que hemos logrado para sacar unas conclusiones que puedan aportar a otros proyectos y permitan continuar la línea de trabajo. Enunciaremos los objetivos que hemos logrado y compararemos con los que nos propusimos al comienzo del trabajo: • Hemos conseguido conectar entre sí el PC con el Teensy y este a su vez con la FPGA. • Hemos establecido una comunicación TCP funcional entre una aplicación en el PC con Qt y un programa en el Teensy con Arduino. • Hemos podido leer y escribir en espacios de memoria de una FPGA enviando mensajes desde el programa en PC y traduciéndolos con el Teensy. • Conseguimos un flujo de datos estable y fiable que nos permitirá programar la FPGA e interactuar con elementos programados en ella. • Conseguimos velocidades estables de hasta 215 Kbytes/s para escritura en la FPGA y hasta 339 Kbytes/s para lectura. Nuestro principal objetivo era establecer la conexión y lograr que se pudiera acceder a la memoria de la FPGA desde el programa en PC, por lo que lo hemos cumplido y verificado. Queríamos funcionalidad y fiabilidad ya que el proyecto forma parte de una línea de trabajo y hemos hecho que las clases y funciones creadas sean flexibles para poder utilizarlas con otras interfaces de usuarios o incluso en conexiones diferentes. Las funciones de bit banging con la FPGA del Teensy son independientes de la conexión TCP y se podrán utilizar para conexiones con WiFi o USB. El código de conexión TCP es flexible y podrá añadir nuevas instrucciones, por ejemplo, para configuración de la FPGA. Buscábamos también que el proyecto fuese robusto, y para ello hemos tenido que solventar las limitaciones de las librerías del Teensy con fragmentación de los datos tanto en la escritura como en el envío de la respuesta de escritura. Hemos preparado los programas para poder modificar o eliminar esas modificaciones si se resolvieran las limitaciones. Para conseguir esta robustez