scieee AI-readable full text Open interactive document viewer

Implementación de un Robot Móvil Omniwheel para la inspección de la contaminación

Rodríguez Prieto, Alejandro

Abstract

E n este proyecto se ha llevado a cabo la construcción y puesta en marcha de una plataforma de pruebas para embarcaciones autónomas inteligentes en forma de un vehículo terrestre. A lo largo del documento se explican todos los componentes utilizados y sus respectivas características, ademas de su función dentro del esquema global. También se incluye una sección dedicada al desarrollo del software realizado para el control y funcionamiento del sistema, junto con los entornos de desarrollo utilizados. Al final se incluyen las diferentes pruebas de funcionamiento realizadas sobre el prototipo terminado.

Full text

Proyecto Fin de Carrera Ingeniería de Telecomunicación Formato de Publicación de la Escuela Técnica Superior de Ingeniería Autor: F. Javier Payán Somet Tutor: Juan José Murillo Fuentes Dep. Teoría de la Señal y Comunicaciones Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2013 Trabajo Fin de Grado Grado en Ingeniería Electrónica, Robótica y Mecatrónica Implementación de un Robot Móvil Omniwheel para la inspección de la contaminación Autor: Alejandro Rodríguez Prieto Tutores: Sergio Toral Marin y Samuel Yanes Luis Dpto. Ingeniería Electrónica Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2024 Trabajo Fin de Grado Grado en Ingeniería Electrónica, Robótica y Mecatrónica Implementación de un Robot Móvil Omniwheel para la inspección de la contaminación Autor: Alejandro Rodríguez Prieto Tutores: Sergio Toral Marin Catedrático Samuel Yanes Luis Investigador Predoctoral Dpto. Ingeniería Electrónica Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2024 Trabajo Fin de Grado: Implementación de un Robot Móvil Omniwheel para la inspección de la contaminación Autor: Alejandro Rodríguez Prieto Tutores: Sergio Toral Marin Samuel Yanes Luis El tribunal nombrado para juzgar el trabajo arriba indicado, compuesto por los siguientes profesores: Presidente: Vocal/es: Secretario: acuerdan otorgarle la calificación de: El Secretario del Tribunal Fecha: Agradecimientos C on la culminación de este Trabajo de Fin de Grado, cierro un capítulo significativo en mi vida académica y personal. Este proyecto no solo ha sido un desafío técnico, sino también una oportunidad para crecer y aprender. Quiero expresar mi más profundo agradecimiento a mi tutor Samuel, cuya guía, paciencia y conocimientos fueron fundamentales en cada etapa del desarrollo de este proyecto. Su apoyo y consejos han sido invaluables. También quiero reconocer a mis compañeros y amigos, cuyo apoyo y compañía han sido un pilar importante durante estos años. Gracias por estar siempre ahí, por las discusiones técnicas, los momentos de desconexión y las risas que hicieron este viaje mucho más ameno. No puedo terminar sin mencionar a mi familia, cuyo amor, apoyo y confianza en mí nunca han flaqueado. Gracias por ser mi motivación y por creer en mí incluso cuando las cosas se ponían difíciles. Finalmente, agradezco a la comunidad de desarrolladores de ROS, Arduino y a todos aquellos que, directa o indirectamente, contribuyeron a este proyecto. Sus recursos, tutoriales y foros han sido una fuente invaluable de información y aprendizaje. Este TFG no solo representa el fin de un ciclo, sino también el comienzo de nuevos desafíos y oportunidades. Gracias a todos por ser parte de este importante capítulo de mi vida. Alejandro Rodriguez Prieto Almería, 2024 I Resumen E n este proyecto se ha llevado a cabo la construcción y puesta en marcha de una plataforma de pruebas para embarcaciones autónomas inteligentes en forma de un vehículo terrestre. A lo largo del documento se explican todos los componentes utilizados y sus respectivas características, ademas de su función dentro del esquema global. También se incluye una sección dedicada al desarrollo del software realizado para el control y funcionamiento del sistema, junto con los entornos de desarrollo utilizados. Al final se incluyen las diferentes pruebas de funcionamiento realizadas sobre el prototipo terminado. III XÍndice 5.1 Control PID 25 5.2 Entornos de desarrollo 27 5.2.1 Arduino IDE 27 5.2.2 Visual Studio Code 27 5.3 Programación en Arduino 28 5.3.1 Clase PID 28 5.3.2 Programa principal 29 5.4 ROS 32 5.4.1 Introducción a ROS 32 5.4.2 Inicializacion del workspace 33 5.4.3 Creación de un paquete de ROS 34 6 Cambio de morfología - Omnidireccional 37 6.1 Cambios en el hardware 37 6.2 Cambios en el software 38 6.2.1 Fundamentos del modo omnidireccional 38 6.2.2 Cambios en el código 39 7 Pruebas de funcionamiento 41 7.1 Pruebas de los motores 41 7.1.1 Punto muerto 41 7.1.2 Control PID 41 7.2 Funcionamiento del nodo 47 7.3 Prueba experimental completa 48 8 Conclusiones 53 8.1 Resultados obtenidos 53 8.2 Lineas futuras de trabajo 54 Apéndice A Códigos de Arduino 55 A.1 Código principal de arduino - main.cpp 55 A.2 Clase PID 63 A.3 Objeto PID 64 Apéndice B Códigos de ROS 67 B.1 Código del nodo 67 B.2 Código del archivo launch 69 Índice de Figuras 71 Índice de Tablas 73 Índice de Códigos 75 Bibliografía 77 Notación TFG Trabajo de fin de grado RPM Revoluciones por minuto MOSFET Metal Oxide Semiconductor Field-Effect Transistor PWM Pulse-width modulation ARM Advanced RISC Machine RISC Reduced Instruction Set Computer UART Universal Asynchronous Receiver/Transmitter SPI Serial peripheral interface USB Universal serial bus VSCode Visual Studio Code SRAM static random access memory EEPROM Electrically erasable programmable read-only memory IA Inteligencia artificial E/S Entradas/Salidas HDMI High-Definition Multimedia Interface GPIO General Purpose Input/Output CSI Camera serial interface ROS Robot operating system DC Direct current IDE Integrated development environment LCD Liquid Crystal Display GUI Graphical User Interface V Voltios Hz Hercios A Amperios Kg Kilogramo cm Centimetro mm Milimetro A4WD All-time 4-Wheel Drive XI 1 Introducción, Motivación y Objetivos E l capitulo que tiene lugar a continuación sirve como una breve introducción del tfg que se va a desarrollar a lo largo de este documento. Se aprovechará para definir las motivaciones y objetivos buscados con este trabajo. 1.1 Introducción Este TFG busca la creación de una plataforma de pruebas para barcos autónomos, que se encargaran de monitorizar los agentes contaminantes presentes en nuestros mares. Esta plataforma de pruebas se creará para poder funcionar de forma terrestre, luego pudiéndose trasladar de forma segura los sistemas desarrollados a una embarcación. Hay que observar que los barcos están diseñados para operar en entornos acuáticos, lo que implica tener en cuenta factores como la flotabilidad, la resistencia del agua y las condiciones climáticas marítimas. Esto provoca que la robótica marina sea significativamente mas compleja que la terrestre. Esta ultima cuenta con un entorno de operación mas estable, ya que no opera en un medio fluido que cambia constantemente. Aun con todo se ha optado por la realización de una plataforma de pruebas terrestre. Esto es porque la embarcación marina cuenta con un modelo de funcionamiento diferencial al encontrarse los propulsores a los lados del casco. Diseñando nuestra plataforma de pruebas de igual forma, tenemos una ecuaciones cinemáticas iguales para ambos sistemas permitiendo una mayor traslación. Así, podemos realizar todas las pruebas y experimentos necesarios en un entorno menos hostil y con mayor rapidez. Una vez se tenga un sistema los suficientemente robusto, se puede llevar a un prototipo real. El modelo diferencial es una herramienta útil para controlar robots diferenciales, ya que permite planificar y ejecutar movimientos precisos y específicos. Sin embargo, es importante tener en cuenta que es una simplificación y no tiene en cuenta factores como el deslizamiento o deriva, la fricción con el agua o las irregularidades presentes en una superficie acuática. Por lo tanto, para la embarcación real deberá requerirse modelos más sofisticados para lograr un control preciso. Otra configuración de movimiento que nos puede ser de utilidad es la omnidireccional. En el modo omnidireccional el robot tiene la su capacidad para moverse en múltiples direcciones sin tener que cambiar su orientación. Esto se logra mediante la combinación de ruedas especiales que le permiten moverse hacia adelante, hacia atrás, hacia los lados y rotar sobre su propio eje, todo al mismo tiempo, lo que le brinda una gran agilidad y versatilidad en su movimiento. Una de las desventajas presentes en nuestra aplicación experimental es la deficiente tracción de las ruedas, necesitando de una superficie optima para el movimiento. Esto se debe a que las ruedas están conformadas por rodillos de forma diagonal, que presentan una baja superficie de contacto. Si se estimase oportuno, la embarcación podría optar por esta configuración de de movimiento, ya que 1 2Capítulo 1. Introducción, Motivación y Objetivos en un medio fluido como es el agua es perfectamente factible, así que habilitaremos un selector para cambiar entre ambos modos de funcionamiento. 1.2 Motivación La actividad humana tiene una serie de consecuencias significativas en la Tierra, que abarcan desde impactos ambientales y cambios climáticos hasta la pérdida de biodiversidad y la degradación de los recursos naturales. Estas consecuencias son el resultado de prácticas insostenibles y decisiones que afectan negativamente a los ecosistemas y al equilibrio del planeta. Es por eso por lo que, en los últimos años, han surgido diversas alternativas respetuosas con el medio y la necesidad de promover la sostenibilidad. Estas alternativas buscan reducir el impacto negativo de las actividades humanas y fomentar un estilo de vida más ecoamigable. Las principales áreas de atención son la contaminación del aire y la del agua, ya que son dos formas importantes de contaminación que tienen graves impactos en el medio ambiente y en la salud humana. Centrándonos en la contaminación marina[ 5 ], para observar la evolución que presenta este medio respecto a las medidas tomadas por las instituciones gubernamentales se disponen de varios métodos principales, como muestreos periódicos de agua marina y sedimentos en diferentes ubicaciones para recopilar muestras. Estos análisis pueden incluir la detección de contaminantes químicos, como metales pesados, compuestos orgánicos y nutrientes en exceso. También se pueden evaluar parámetros físico-químicos, como la temperatura, el pH, la salinidad y la concentración de oxígeno disuelto. Los océanos y mares cubren en torno al 70% de la superficie del planeta. Estas pruebas las suelen llevar a cabo embarcaciones científicas tripuladas y boyas de monitoreo que, como mucho, pueden hacer unas estimaciones generales de la situación del océano en la región en intervalos de tiempo relativamente largos. Es aquí donde encaja la necesidad de este TFG. Creando una serie de barcos autónomos centrados en la toma de muestras marinas de forma constante se podría tener una imagen mas precisa de el estado actual de los océanos, incluso en las zonas más remotas. Como un primer paso hacia este objetivo, es necesaria la creación de una plataforma de pruebas, ya que el océano puede llegar a ser un entorno hostil que requiere de mucha preparación. Una vez comprobados todos los sistemas incluidos, se podrá pasar al desarrollo de un prototipo marino. 1.3 Objetivos El objetivo principal de este TFG es, como ya se comentó anteriormente, la creación de una plataforma de pruebas o ‘Testbed’ terrestre en forma de un coche manejado de forma remota para la posterior implementación en una embarcación real. Se procederá a explicar los componentes incluidos y el proceso de montaje, así como la codificación del software que permite el funcionamiento del vehículo. Se realizarán test funcionales para comprobar el funcionamiento del sistema. Para el correcto desarrollo se plantean los siguientes objetivos que marcaran el proceso de creación: • Montaje de un motor, control PID y monitorización mediante arduino: Se plantea un montaje inicial donde se comprueba el estado de los motores y se programa el sistema de control y telemetría. • Ensamblaje del chasis, instalación de los motores, preparación del conexionado: Una vez confirmado el correcto funcionamiento de los motores se procede al ensamblaje del chasis. También se atornillan los motores en su posición final y se hace el conexionado necesario para que puedan funcionar. 1.3 Objetivos 3 • Ampliación código arduino, procesamiento de mensajes serial, gestión de todos los motores: Se adapta el código para que funcione con todos los motores a la vez. Ademas, se añade una sección en el código que se encargue de gestionar toda la información entrante por serial, para luego hacer uso de ella. • Inicialización de Jetson nano, creación del nodo de comunicación: Se prepara la jetson, instalandole todas las dependencias necesarias y los programas para desarrollar el software. Se crea el nodo de ROS capaz de comunicarse con el arduino mediante puerto serie. • Finalización del montaje, ubicación de todos los elementos, soldaje de cables: Se añaden los últimos elementos. Se instala la jetson junto al arduino en la placa superior del robot, con sus correspondiente conexionado. Se coloca la batería en su ubicación final. • Pruebas de funcionamiento iniciales. Se realizan las primeras pruebas con el montaje terminado, se recogen datos de operación. • Cambio de ruedas a omnidireccionales, añadir cambio de modo de funcionamiento. Al comprobar el estado funcional del prototipo se procede a añadir la posibilidad de un cambio de modelo de movimiento a omnidireccional. • Pruebas de funcionamiento final. Se realizan las ultimas pruebas con este ultimo montaje, se recogen los últimos datos y se realizan los últimos ajustes. 2 Estado del Arte E l ser humano siempre ha intentado concebir creaciones que le permitieran facilitar todas las tareas que debía realizar. La robótica nace como respuesta a la necesidad de librarse de las tareas menos estimulantes, que requieren mucho tiempo o que exponen al ser humano a peligros. Pero, ¿Que es exactamente un robot? Un robot puede definirse como un sistema electromecánico reconfigurable que lleva a cabo las tareas establecidas de forma automática y precisa. Y aunque sea una ciencia relativamente joven, ha visto una evolución significativa en las ultimas décadas. Hay una amplia variedad de formas y aplicaciones para los robots, que dependen de la tarea que deben llevar a cabo. Por lo tanto, se pueden utilizar diferentes criterios para clasificarlos, como su arquitectura, generación, nivel de inteligencia y nivel de control. Dentro de su tipo de arquitectura, se encuentran los robots móviles, que desarrollaremos mas en profundidad en los siguientes párrafos. 2.1 ¿Que es un robot móvil? Un robot móvil es un sistema electromecánico que se destaca por su capacidad de moverse de forma autónoma sin estar sujeto a un punto fijo. Estos robots están equipados con sensores que les permiten monitorear constantemente su posición en relación a su origen y destino. Su movimiento se logra mediante diversos dispositivos de locomoción. La clasificación de los robots móviles se basa en el tipo de desplazamiento que utilizan. En general, existen tres formas de movimiento: ruedas, patas y orugas. Aunque se ha investigado ampliamente el uso de patas y orugas, la mayoría de los avances se han centrado en los robots móviles con ruedas. Esto se debe a las ventajas que ofrecen las ruedas en comparación con el resto de tecnologías. Además, para nuestra aplicación marina, es el sistema de movimiento mas parecido al uso de motores acuáticos, por eso profundizaremos en ellos. Los robots móviles con ruedas se destacan por su eficiencia energética en superficies lisas y firmes, además de no causar desgaste en la superficie por donde se desplazan. Estos robots requieren menos componentes, que suelen ser menos complejos en comparación con los otros sistemas de movimiento. Esto facilita su construcción y los hace más fáciles de diseñar y fabricar. Hay diversas configuraciones cinemáticas para los robots con ruedas, las cuales dependen principalmente de la aplicación a la que se dirige el robot. Sin embargo, en términos generales, se pueden identificar las siguientes configuraciones: Ackerman, triciclo clásico, tracción diferencial, skid steer, síncrona y tracción omnidireccional. Cabe destacar que según esta configuración cinemática los robots dispondrán de distintos tipos de ruedas, como son las omnidireccionales, convencionales, giratorias y ruedas de bolas, visibles en la Figura 2.2. 5 6Capítulo 2. Estado del Arte Figura 2.1 Morfologias de robots de superficie. Figura 2.2 Distintos tipos de ruedas disponibles. 2.2 Morfologias 7 2.2 Morfologias 2.2.1 Ackerman En esta configuración, el robot utiliza dos ruedas motrices en el eje delantero, que están conectadas a un mecanismo de dirección. A su vez, las ruedas traseras son ruedas libres, es decir, no están conectadas al mecanismo de dirección y solo proporcionan apoyo y estabilidad al robot. El mecanismo de dirección de las ruedas delanteras está diseñado de tal manera que permita al robot realizar giros mediante la variación de los ángulos de dirección de cada rueda. Cuando el robot gira, la rueda interior (la que está en el lado opuesto de la dirección del giro) gira con un ángulo mayor que la rueda exterior. Esta diferencia en los ángulos de dirección permite que el robot realice giros suaves y precisos. Figura 2.3 Configuración Ackerman. La configuración Ackerman es especialmente útil en entornos donde se requiere un radio de giro reducido y una buena capacidad de maniobra, como en vehículos de conducción asistida, vehículos de tracción en las cuatro ruedas y algunos tipos de vehículos de carga. Esta configuración permite que el robot siga una trayectoria suave al girar, evitando que las ruedas se arrastren o se deslicen, lo que proporciona una mayor estabilidad y precisión en los movimientos. 2.2.2 Triciclo clásico En esta configuración, el robot tiene una sola rueda motriz en el eje delantero y dos ruedas adicionales en el eje trasero, formando un triángulo. La rueda delantera, que es la rueda motriz, está conectada a un mecanismo de dirección que permite controlar la dirección del robot. Las dos ruedas traseras, llamadas ruedas de apoyo, no están conectadas a ningún mecanismo de dirección y suelen ser ruedas libres que giran libremente. Figura 2.4 Configuración Triciclo. 14 Capítulo 3. Hardware Figura 3.3 Motor con encoder de cuadratura instalado. Figura 3.4 Esquema de pulsos de un encoder de cuadratura. Tabla 3.4 Características encoder cuadratura lynxmotion. Parámetro Valor Ciclos por revolución 100 Cuentas de cuadratura por revolución 400 Frecuencia 30kHz Recogeremos las señales producidas por los encoder y las procesaremos con el arduino para poder determinar el sentido y velocidad de cada uno de los motores. Profundizaremos mas en este aspecto dentro del apartado de software. Los encoder utilizados pertenecen al mismo fabricante, lynxmotion, ya que están diseñados específicamente para ir acoplados a sus motores. La característica mas importante necesaria para cálculos posteriores es los ciclos por revolución. La pagina de venta nos indica que se dan 100 ciclos por revolución. 3.3 Arduino DUE 15 3.3 Arduino DUE El microcontrolador elegido para operar los elementos mecánicos es el arduino DUE. El Arduino Due principalmente se caracteriza por ser la primera placa de desarrollo arduino basada en un microcontrolador ARM de 32 bits. Se destaca por ser una de las placas más potentes de la familia Arduino[1]. Figura 3.5 Arduino DUE. Tabla 3.5 Características Arduino DUE. Parámetro Valor Microcontrolador AT91SAM3X8E Voltaje de operación 3.3v Voltaje recomendado de entrada [7v, 12v] Pines de entrada y salida digitales 54 Pines de entrada analógicos 12 Pines de salida analógicos 2 Corriente de salida total en los pines I/O 130mA Corriente DC máxima en el pin de 3.3V 800mA Corriente DC máxima en el pin de 5V 800mA Memoria Flash 512KB SRAM 96KB Velocidad de reloj 84MHz El microcontrolador que posee, el SAM3X8E tiene una velocidad de reloj de 84 MHz y cuenta con 54 pines digitales de entrada/salida, de los cuales 12 pueden utilizarse como salidas PWM. Además, tiene 12 entradas analógicas, dos buses I2C, cuatro UART, dos puertos SPI y un puerto de comunicación USB. Una de las características más notables del Arduino Due es su capacidad de trabajar a niveles de tensión de 3.3V, a diferencia de otras placas Arduino que operan a 5V. Esto significa que se deben tener en cuenta las diferencias de voltaje al utilizar sensores y periféricos que funcionan con 5V, ya que podrían no ser compatibles directamente con el Arduino Due. El Arduino Due es compatible con el entorno de desarrollo de Arduino, por lo que se puede programar utilizando el lenguaje de programación Arduino y la IDE de Arduino. En nuestro caso, no usaremos la IDE de Arduino en favor de usar Visual Studio Code. Con un par de plugins, podemos 16 Capítulo 3. Hardware Figura 3.6 Esquema de pines y conexiones de Arduino DUE. gozar de un entorno cómodo de desarrollo y todas las ventajas que ofrece Visual Studio, pero se profundizará mas en este aspecto en siguientes secciones. Debido a su mayor potencia de procesamiento y memoria en comparación con otros modelos de Arduino, el DUE es especialmente adecuado para aplicaciones que requieren cálculos más intensivos, como el procesamiento de señales de audio, control de motores, comunicación avanzada, entre otros. Es este uno de los factores principales por el cual se eligió este modelo de arduino. Buscamos una fluidez en los cálculos, control y transmisión de información no obtenibles con un modelo mas limitado. Esto nos permite crear mas cómodamente un prototipo funcional sin tener que centrarnos mucho inicialmente en la optimización. En términos de memoria, el Arduino DUE cuenta con 512 KB de memoria flash para almacenar el programa, 96 KB de SRAM para variables y datos en tiempo de ejecución, y 2.5 KB de memoria EEPROM para almacenamiento no volátil. Además de sus características técnicas, el Arduino Due tiene un factor de forma similar a otras placas Arduino, lo que facilita su integración en proyectos y su compatibilidad con shields diseñados para otros modelos de Arduino. Para nuestra aplicación no harán falta ya que se conectará directamente por UART con la NVIDIA Jetson. 3.4 NVIDIA Jetson nano 17 3.4 NVIDIA Jetson nano La NVIDIA Jetson Nano[ 3 ] es una placa de desarrollo de IA compacta y potente que permite a los desarrolladores implementar aplicaciones de aprendizaje profundo y visión por computadora en dispositivos embebidos de bajo consumo energético. Con su combinación de potencia de procesamiento y capacidades de conectividad, la Jetson Nano se ha convertido en una opción popular para una amplia gama de aplicaciones de IA en el mundo de la robótica y la automatización. Figura 3.7 NVIDIA Jetson nano. Tabla 3.6 Características NVIDIA Jetson nano. Parámetro Valor CPU NVIDIA Maxwell™con 128 núcleos NVIDIA CUDA® GPU ARM®Cortex®-A57 MPCore de cuatro núcleos Memoria LPDDR4 de 4 GB y 64 bits Almacenamiento 16 GB de almacenamiento Flash eMMC 5.1 Conectividad Gigabit Ethernet Pantalla HDMI 2.0 o DP 1.2 | eDP 1.4 | DSI (1 x 2) 2 simultáneos UPHY 1 1/2/4 PCIE, 1 USB 3.0, 3 USB 2.0 E/S 1 SDIO / 2 SPI / 4 I2C / 2 I2S / GPIO Aunque para la creación de nuestro entorno de pruebas no hagamos uso de IA ni de sistemas de percepción para una navegación autónoma, será necesario para las siguientes iteraciones del proyecto si se quiere lograr el objetivo establecido inicialmente. Es por eso que integramos este sistema de forma temprana y preparamos el terreno para sus futuras funciones. La Jetson Nano cuenta con una amplia variedad de interfaces de E/S, incluyendo HDMI, USB 3.0, Ethernet, GPIO y CSI para cámaras. Esto la convierte en una plataforma versátil para la conexión y control de otros dispositivos y periféricos, teniendo asegurada una alta compatibilidad con cualquien dispositivo que queramos añadir a posteriori. En este caso inicial. La placa NVIDIA se encargara de portar el sistema operativo ROS y nos permitirá enviar comandos de forma remota para navegar con el robot. 4 Diseño y Desarrollo E n este capitulo se procederá a describir el montaje realizado y todos los procedimientos seguidos para las diferentes pruebas de funcionalidad realizadas en el proceso. 4.1 Conexionado de un motor Una vez ya conocidas todos los elementos de los que disponemos y sus respectivas especificaciones podemos comenzar con el montaje y las posteriores pruebas. El elemento mas básico necesario en este caso seria poner en funcionamiento los motores, para comprobar que todos se encuentran en un estado adecuado para continuar. Como ya comentamos anteriormente cada controlador de motor dispone de dos puentes H, por lo que disponen de varios puertos de conexionados diferenciados. Comenzamos solo con un motor para partir del caso mas básico. Se puede consultar la Figura 3.2 para mejor entendimiento de las conexiones. Empezando desde el motor, disponemos de 4 cables para conectar. Los cables de alimentación a 12v y las señales a y b (también conocidas como clk y data) del encoder. Comenzando por la alimentación, esta deberá ir conectada al controlador del motor, ya que es el que se encarga de regular la corriente y sentido de la misma que llega a este. Los cables del encoder deberán ir directamente conectados al arduino, ya que se procesaran para obtener la velocidad actual del motor. Es necesario que los puertos estén habilitados para interrupciones, ya que el encoder puede mandar una señal en cualquier momento durante la ejecución del código, y de no interrumpirla cuando se recibe la información es posible que se pierda, teniendo así unas mediciones poco fiables. Ahora deberemos conectar el controlador al arduino para poder actuar sobre el motor. Se puede observar la presencia de un pin llamado PWM y de otro llamado DIR. El pin PWM, como indica su nombre, sera donde mandaremos la señal modulada en ancho de pulso, permitiendo así regular la velocidad del motor. El pin DIR sirve para indicar la dirección de giro deseada para el motor. Es una señal binaria, con cada estado asignado a un sentido de giro. Se puede apreciar también dos pines adicionales, GND y 5v. Estos tienen relación con la función de freno de la que se hablo en la sección anterior sobre el hardware. Para que el motor gire hace falta una alimentación de 5v en ese puerto. Si quisiéramos parar el motor aunque este esté recibiendo una señal PWM deberíamos cortar la alimentación en ese puerto y el motor se pararía. Se debería conectar a un puerto del arduino si quisiéramos hacer uso de esta función del controlador, pero por el momento simplemente alimentaremos este pin y no haremos uso de esta función. Con esto ya dispondríamos de un motor controlable desde el arduino. Para hacer la prueba simplemente habría que mandar por el puerto conectado al pin PWM un señal de 0 a 255. Si nos da igual la dirección de giro no hace falta hacer nada sobre el pin DIR, ya que tomara un sentido por defecto. 19 20 Capítulo 4. Diseño y Desarrollo Figura 4.1 Conexión de prueba de un motor. 4.2 Montaje del chasis Una vez comprobado el funcionamiento de los 4 motores, teniendo todos un conexionado igual entre ellos, podemos pasar al montaje de los motores en el chasis y su posterior ensamblaje. Con el kit de chasis se tiene a disposición del usuario un manual, aunque el proceso es bastante simple y puede ser realizado sin el uso del manual. Disponemos de las piezas laterales en las cuales se han realizado unos huecos que permiten la instalación de los motores. Procedemos a instalarlos atornillándolos a la pieza metálica. Figura 4.2 Instalación de motores en chasis metálico. Una vez terminado este paso deberíamos quedarnos con las dos piezas laterales con dos motores atornillados a cada una. Ahora podemos unirlas haciendo uso de otras dos piezas adicionales de metal, dando como resultado una estructura cuadrada robusta. Por ultimo lo único que quedaría seria atornillar a esta estructura la placa de policarbonato inferior, para poder empezar a colocar el 4.3 Conexionado final de los motores 21 resto de elementos electrónicos dentro. La tapa superior por ahora la dejaremos sin atornillar para poder acceder al interior cuando deseemos. Figura 4.3 Chasis con los motores ya instalados. 4.3 Conexionado final de los motores Ahora que disponemos del chasis con los motores instalados podemos alojar toda la electrónica relacionada. El conexionado total será replicar cuatro veces el conexionado de prueba visto anteriormente. Para lograr esto se fijan dos protoboards en la tapa inferior. Figura 4.4 Instalación de los cuatro motores en el chasis. 22 Capítulo 4. Diseño y Desarrollo 4.4 Alimentación Para la alimentación de todo el sistema se hará uso de una batería de litio de 12v. de ella se dispondrá una conexión directa para alimentar los motores. Estos cables irán conectados en los controladores de los motores. Como añadido he creado e instalado una placa para conectar la batería, la cual cuenta con puertos para conexionar una fuente externa si se necesita y así no tener que usar la batería de forma obligatoria. Ademas, con esta batería se alimenta también La NVIDIA Jetson. Para esto se hará uso de un convertidor DC/DC que nos permitirá disminuir la tension dada por la batería a los 5v que necesita la Jetson. Como el arduino ira conectado por USB a la Jetson, será esta la que lo alimente. Figura 4.5 Conexión de alimentación, convertidor DC/DC. 4.5 Conexionado completo Se puede visualizar el esquemático del conexionado completo en la figura Figura 4.6. 4.5 Conexionado completo 23 1 1 2 2 3 3 4 4 D D C C B B A A Title Number RevisionSize A4 Date: 09/07/2023 Sheet of File: C:\Users\..\Sheet1.SchDoc Drawn By: Pin 2 2 Pin 3 3 Pin 4 4 Pin 5 5 Pin 6 6 Pin 7 7 Pin 8 8 Pin 9 9 Pin 26 26 Pin 27 27 Pin 28 28 Pin 29 29 Pin 30 30 Pin 31 31 Pin 32 32 Pin 33 33 VCC GND USB Due Arduino GND PWM 1 DIR 1 PWM 2 DIR 2 Enable POWER +MOTOR 2 -MOTOR 2 +MOTOR 1 -MOTOR 1 GND Puente 1 Controller GND PWM 1 DIR 1 PWM 2 DIR 2 Enable POWER +MOTOR 2 -MOTOR 2 +MOTOR 1 -MOTOR 1 GND Puente 2 Controller 12v -12v A B 4 Motor 12v -12v A B 2 Motor 12v -12v A B 3 Motor 12v -12v A B 1 Motor USB + Alimentacion - Alimentacion NVIDIA Jetson +12V -12v Alimentacion Bateria +IN -IN +OUT -OUT StepDown GND GND GND +5 +5 +5 GND GND GND 12v +5v -5v +5v -5v 12v 12v M2_a M2_b M1_a M1_b M4_a M4_b M3_a M3_b M1_a M1_b M2_a M2_b M3_a M3_b M4_a M4_b PI100IN PI10CLK0enc PI10DT0enc PI10GND CO1 PI200IN PI20CLK0enc PI20DT0enc PI20GND CO2 PI300IN PI30CLK0enc PI30DT0enc PI30GND CO3 PI400IN PI40CLK0enc PI40DT0enc PI40GND CO4 PIAlimentacion01 PIAlimentacion02 COAlimentacion PIDC0DC005v PIDC0DC0012v PIDC0DC05v PIDC0DC012v CODC0DC PIDue02 PIDue03 PIDue04 PIDue05 PIDue05V PIDue06 PIDue07 PIDue08 PIDue09 PIDue026 PIDue027 PIDue028 PIDue029 PIDue030 PIDue031 PIDue032 PIDue033 PIDue0GND PIDue0Serial0Arduino CODue PINVIDIA0a PINVIDIA0IN0Jetson PINVIDIA0Serial0Jetson CONVIDIA PIPuente 100mot1 PIPuente 100mot2 PIPuente 10012v PIPuente 105v PIPuente 1012v PIPuente 10dir01 PIPuente 10dir02 PIPuente 10GND PIPuente 10pwm01 PIPuente 10pwm02 COPuente 1 PIPuente 200mot1 PIPuente 200mot2 PIPuente 20012v PIPuente 205v PIPuente 2012v PIPuente 20dir01 PIPuente 20dir02 PIPuente 20GND PIPuente 20pwm01 PIPuente 20pwm02 COPuente 2 PIDue05V PIPuente 105v PIPuente 205v PIAlimentacion02 PIDC0DC012v PIDue0GND PIPuente 10012v PIPuente 10GND PIPuente 20012v PIPuente 20GND PI100IN PIPuente 100mot1 PI10CLK0enc PIDue026 POM10a PI10DT0enc PIDue027 POM10b PI10GND PIPuente 100mot1 PI200IN PIPuente 100mot2 PI20CLK0enc PIDue028 POM20a PI20DT0enc PIDue029 POM20b PI20GND PIPuente 100mot2 PI300IN PIPuente 200mot1 PI30CLK0enc PIDue030 POM30a PI30DT0enc PIDue031 POM30b PI30GND PIPuente 200mot1 PI400IN PIPuente 200mot2 PI40CLK0enc PIDue032 POM40a PI40DT0enc PIDue033 POM40b PI40GND PIPuente 200mot2 PIDC0DC05v PINVIDIA0a PO05v PIDC0DC005v PINVIDIA0IN0Jetson PO05v PIDue02 PIPuente 10pwm01 PIDue03 PIPuente 10dir01 PIDue04 PIPuente 10pwm02 PIDue05 PIPuente 10dir02 PIDue06 PIPuente 20pwm01 PIDue07 PIPuente 20dir01 PIDue08 PIPuente 20pwm02 PIDue09 PIPuente 20dir02 PIDue0Serial0Arduino PINVIDIA0Serial0Jetson PIAlimentacion01 PIDC0DC0012v PIPuente 1012v PIPuente 2012v PO12v PO05v PO12v POM10a POM10b POM20a POM20b POM30a POM30b POM40a POM40b Figura 4.6 Esquemático de conexión. 30 Capítulo 5. Software Código 5.4 Rutina de interrupción. void enc_isr1(){ if (digitalRead(26) != digitalRead(27)) { enc_count[0] ++; sent[0]= '0'; } else { enc_count[0] --; sent[0]= '1'; } } • Cálculo de la velocidad: Una vez obtenido el numero de pulsos podemos calcular cuanto tiempo tardo la rueda en dar una vuelta completa. Como sabemos el tamaño de la rueda podemos realizar cálculos y obtenemos la distancia recorrida por la misma: Código 5.5 Rutina de interrupción. for(int i=0; i<4; i++){ if(enc_count[i]>=200 || enc_count[i]<=-200){ enc_count[i]=0; enc_end[i]=millis(); enc_time[i]=enc_end[i]-enc_start[i]; y[i]=(12*M_PI*1000/(30*enc_time[i])); //cm/s enc_start[i]=enc_end[i]; } } Pero solo con esto no tenemos suficiente información disponible para un control correcto de los motores. Si se diese el caso de mandarle un valor de consigna de 0 al control y este detuviese los motores, el valor de la velocidad seria el ultimo registrado, ya que la función que calcula la velocidad solo es valida en el caso de que estén activos y no funciona si no están en movimiento, aumentando el valor del termino integral infinitamente hasta que se reanudase el movimiento. Para evitar esto, desarrollamos una función que se encargue de registrar los dos últimos valores registrados de velocidad. Si pasado un tiempo determinado el valor actual y los anteriores son iguales eso quiere decir que el motor esta parado y se establecerá la velocidad como cero. Con esto tenemos una medición correcta de la velocidad. • Comunicación serie: Para que la información transmitida por la NVIDIA Jetson sea procesada correctamente por el Arduino crearemos una trama de datos. La delimitamos por unos caracteres especiales, $ y ;, y dentro mandaremos los valores de velocidad angular y lineal, además de un byte de CRC para comprobar una transmisión de datos sin perdidas. El CRC funciona generando un valor numérico único por medio de operaciones polinomiales, dando un resultado conocido como "checksum" o "suma de comprobación", a partir de los datos que se desean verificar. Este valor se agrega a los datos antes de ser transmitidos o almacenados. Luego, al recibir los datos, se realiza nuevamente el cálculo del CRC y se compara con el valor original. Si los valores coinciden, se asume que los datos no han sufrido ninguna alteración durante la transmisión o almacenamiento. Si los valores difieren, se infiere 5.3 Programación en Arduino 31 que ha habido un error y se puede solicitar la retransmisión de los datos o tomar otras medidas correctivas. Para nuestra aplicación usamos el cálculo de CRC estándar con polinomial 0x07 y aceleramos el computo con una lookup table, la cual contiene los 255 resultados posibles de nuestro checksum. Código 5.6 Cálculo de CRC8. uint8_t calculateCRC8(const uint8_t *data, size_t length) {uint8_t crc = 0; for (size_t i = 0; i < length; ++i) {crc = crc8_table[crc ^ data[i]]; } return crc; } El cálculo correcto de nuestro checksum depende del tratamiento previo de la trama recibida, ya que debemos extraer de forma independiente todos los valores recibidos y actuar sobre ellos. Para esta tarea se hace uso de la funcion strtok(), la cual separa una cadena de caracteres dada en tokens según el carácter separador que le hayamos dado. en este caso, el " ". Así podemos guardar en variables todos los valores recibidos y hacer uso de ellos. Código 5.7 Uso de tokens para la trama. char* token = strtok(buffer, " "); int valueIndex = 0; while (token != NULL) { if (valueIndex == 0) { // Cuando valueIndex sea 0, estamos en el valor de velocidad lineal vel = atoi(token); // Lo convertimos en entero } if (valueIndex == 1) { // Velocidad angular ang = atoi(token); } if (valueIndex == 2) { ... } token = strtok(NULL, " "); // Obtener el siguiente token valueIndex++; } 32 Capítulo 5. Software El uso de tokens para el procesado de la trama recibida nos permite una fácil modificación del código en el caso de que queramos cambiar el numero de parámetros a recibir. En este caso inicial esperamos 4 valores, la velocidad lineal, la angular, un byte libre de ejemplo y el checksum. Pero para añadir mas valores lo único que tendríamos que hacer es crear un caso nuevo para un token extra. • Cálculo de las referencias: Nuestro vehículo funciona en una configuración diferencial. En esta configuración, el robot tiene dos ruedas o actuadores independientes ubicados en lados opuestos del cuerpo del robot. Cada rueda puede controlarse de forma independiente para lograr movimientos y giros en diferentes direcciones. como contamos con cuatro ruedas, actuaremos por igual en las ruedas de cada lado, obteniendo así un modelo similar a como si solo tuviéramos dos ruedas. Una característica importante de los robots con configuración diferencial es su capacidad para girar en el lugar sin necesidad de un mecanismo adicional, como una rueda de dirección. Esto se logra al hacer que las ruedas giren en direcciones opuestas a la misma velocidad, lo que provoca un giro del robot en torno a un punto central. Esto simplifica nuestro trabajo, eliminando la necesidad de añadir mecanismos de control de angulo de las ruedas. Desde la Jetson mandamos valores de velocidad lineal y angular del robot, pero para controlar las ruedas necesitamos valores de velocidad para cada par de ruedas. La relación entre las velocidades de las ruedas y la velocidad y dirección del robot se puede describir mediante las siguientes ecuaciones[6] V=Vr+Vl 2(5.2) Ω=Vr−Vl L(5.3) Siendo L la distancia entre el par de ruedas izquierdo y derecho. Ahora si despejamos de (5.2) y (5.3) podemos obtener las velocidades en cada una de las ruedas: Vl=V−(L∗Ω) 2(5.4) Vr=V+ (L∗Ω) 2(5.5) 5.4 ROS 5.4.1 Introducción a ROS ROS (Robot Operating System)[ 2 ] es un marco de trabajo de código abierto diseñado para facilitar el desarrollo de software para robots. Aunque se llama "sistema operativo", ROS no es un sistema operativo en el sentido tradicional, sino más bien un conjunto de bibliotecas, herramientas y convenciones que ayudan a los desarrolladores a crear software robusto y escalable para robots. ROS permite desarrollar sistemas robóticos distribuidos, donde diferentes componentes (denominados "nodos") pueden comunicarse y colaborar entre sí a través de mensajes y servicios. Esto facilita la modularidad y la reutilización de código, ya que los nodos pueden ser desarrollados de manera independiente y conectados según sea necesario, obteniendo un alto nivel de abstracción. Para que los nodos puedan comunicarse entre ellos, ROS proporciona un sistema de mensajería que permite la comunicación asíncrona entre estos. Los nodos pueden enviar y recibir mensajes a través de tópicos (topics), que son canales de comunicación unidireccionales, o mediante servicios, que son llamadas de procedimiento remoto (RPC) para comunicación bidireccional. 5.4 ROS 33 Además, cuenta con una amplia gama de bibliotecas y herramientas que facilitan el desarrollo de software robótico. Estas incluyen bibliotecas para la manipulación de datos, el control de hardware, la visualización en 3D, la planificación de movimientos, entre otras. También proporciona herramientas de línea de comandos y GUI para depurar, visualizar y supervisar los sistemas en tiempo real. Cabe añadir que es capaz de trabajar con distintos lenguajes de programación, adaptándose a las necesidades del desarrollador. Es una elección clara para usar en nuestro proyecto. ROS es un framework flexible y muy potente, perfecto para la programación de robots en diversos entornos y aplicaciones. En nuestro caso es vital para la creación de nuestra plataforma de pruebas, ya que es donde se integraran todos los algoritmos de control, sistemas de percepción y localización, permitiendo luego una rápida transferencia de los diferentes nodos usados al sistema marino real. 5.4.2 Inicializacion del workspace Un workspace se refiere a un directorio en el que se organizan y compilan los paquetes de ROS. Es una estructura de directorios que nos permite gestionar los proyectos de manera eficiente. Dentro de un workspace de ROS típico podemos encontrar los dos subdirectorios principales: • src (source): En este directorio, se almacenan los paquetes de ROS que componen el proyecto. Los paquetes son unidades de organización que contienen código fuente, archivos de configuración y recursos necesarios para el funcionamiento de un paquete. • devel (development): Este directorio se utiliza para compilar y construir los paquetes del proyecto. Cuando compilamos los paquetes en el directorio src, los archivos binarios y bibliotecas generados se almacenan en el directorio devel. Esto asegura que los paquetes compilados estén disponibles para su ejecución sin afectar la instalación global de ROS en el sistema. Un workspace de ROS nos proporciona un entorno aislado para el desarrollo de proyectos, lo que facilita la gestión de dependencias y evita conflictos entre diferentes versiones de paquetes. Además, permite la personalización de entornos de compilación y facilita la integración de paquetes de terceros en nuestro proyecto. Para trabajar en el workspace, utilizamos herramientas como catkin, que es un sistema de compilación específico de ROS. Para la inicialización de nuestro workspace comenzamos con la creación de las carpetas anteriormente mencionadas dentro de un directorio madre a nuestra elección. Primeramente necesitamos crear de forma manual la carpeta src. Luego de esto, mediante el uso del comando catkin_init_- workspace dentro de nuestro directorio src inicializamos el espacio de trabajo. Ahora podemos compilar mediante el uso de catkin_make y se nos crearan el resto de carpetas necesarias en el espacio de trabajo, devel y build, carpeta que servirá para almacenar archivos temporales durante las compilaciones. Con esto ya tenemos listo el espacio de trabajo y podemos empezar a crear nuestros propios paquetes de ROS. Figura 5.4 Directorio de trabajo. 34 Capítulo 5. Software 5.4.3 Creación de un paquete de ROS Antes de empezar a escribir nuestro nodo necesitamos crear un paquete que lo contenga. Para ello, hacemos uso de catkin_create_pkg serial roscpp, con el cual se crean las carpetas y dependencias necesarias. Nuestro paquete se llama serial y por el momento solo necesita de las dependencias roscpp, que es un paquete que contiene una biblioteca básica de C++ para ROS. Con esto ya tenemos lo necesario para comenzar a crear nuestro nodo. Dentro de la carpeta src del paquete (Importante no confundir con la carpeta src del workspace) creamos el fichero que contendrá el código de nuestro nodo, lo llamaremos serial_node.cpp, y su contenido será el que se ejecutará al iniciar el paquete. Veamos como funciona. Figura 5.5 Directorio del nodo. La única funcion que necesitamos que realice el nodo es el envío de una trama mediante el puerto serie después de haberle calculado su checksum. El mensaje lo recibirá mediante el sistema de suscripción a topic, característica principal que nos ofrece ROS y que permitirá en futuras aplicaciones de este proyecto procesos mucho mas avanzados. Creamos el topic "msg_serial" y nos suscribimos a él. ROS nos permite la ejecución de una función al recibir un mensaje de un topic al que esta suscrito. De forma estándar a esta funcion se le llama callback, y es donde realizaremos el grueso de operaciones necesarias. Con esto nos queda una funcion main() muy reducida. Código 5.8 Función principal del nodo. int main(int argc, char** argv) {ros::init(argc, argv, "serial_node"); ros::NodeHandle nh; ros::Subscriber sub = nh.subscribe("msg_serial", 10, callback); ros::spin(); return 0; } En la función callback() comenzamos con la conversión del mensaje recibido en un arreglo de bytes, para el cálculo posterior del checksum. El cálculo de CRC8 implementado deberá ser el mismo que el incluido en el arduino, ya que ambos valores deberán coincidir. Una vez calculado este checksum se añade al final del mensaje junto con el terminador ; y se envía por puerto serie al arduino haciendo uso de las funciones incluidas en la librería de C++ "asio". Además enviaremos a la consola el valor del mensaje recibido y el enviado para la comprobación del funcionamiento. De la forma que esta creado el nodo los parámetros de configuración del puerto serie son introducidos en el código y si se quieren realizar cambios hay que modificar el nodo y volver a 5.4 ROS 35 compilar, esto puede llegar a ser un inconveniente si se quieren hacer varios cambios, pero en un principio no se debería necesitar realizar estos cambios al puerto serie. Con esto ya tenemos un paquete con un nodo funcional en ROS. Preparamos un fichero para la ejecución de ROS junto con el nodo. Para ello, creamos una carpeta nueva "launch" dentro del paquete que contendrá el fichero. En el indicamos que queremos que se ejecute ROS junto con el nodo y que todo el output suministrado por el nodo sea mostrado en pantalla mediante la consola. Ejecutamos con la funcion roslaunch serial serial_node.launch y una vez cargado podemos empezar a publicar mensajes en el tópico con el siguiente formato: rostopic pub -1 /msg_serial std_msgs/String "data: $XX XX X". El -1 indica que solo queremos que se publique una vez, ya que si no lo incluimos el mensaje es ta publicandonse constantemente. Para acceder a la telemetria enviada por el arduino deberemos hacer uso del comando cat -v < /dev/ttyACM0, eñ cual nos muestra el trafico del puerto seleccionado. La funcion se puede encontrar en el Anexo B Sección B.1 El nodo esta construido de una manera que no importa realmente el numero de parámetros enviados, es decir, que podríamos añadir más bytes en la trama y no habría que modificar el nodo para adaptarlo al nuevo numero de datos. 6 Cambio de morfología - Omnidireccional H asta ahora se ha usado la configuración diferencial para el desarrollo de las diferentes partes del proyecto debido a su mayor simplicidad. Una vez visto que todos los sistemas se han integrado correctamente, se procede a cambiar el modo de funcionamiento a uno omnidireccional. 6.1 Cambios en el hardware Como ya vimos en el apartado dedicado a las diferentes morfologias disponibles para un robot móvil de suelo(Capítulo 2), una parte importante de la configuración omnidireccinal es el uso de unas ruedas especializadas. En nuestro caso se han utilizado ruedas mecanum, también disponibles en la Figura 2.2. Figura 6.1 Ruedas omnidireccionales instaladas en el robot. 37 38 Capítulo 6. Cambio de morfología - Omnidireccional Como se puede observar en el tipo de ruedas, cabe la posibilidad de colocarlas de diferentes formas según la orientación de los rodillos internos. En nuestro caso, se han colocado de la siguiente forma: Donde se puede ver que los rodillos forman una equis entre ellos si se uniesen por una Figura 6.2 Posición elegida para nuestras ruedas. linea imaginaria. Esta es la mejor forma de colocar este tipo de ruedas ya que de otra manera se introducirían rotaciones no deseadas. Con esto tenemos todos los cambios necesarios a nivel de hardware, pasemos ahora a ver los cambios necesarios a nivel de software para integrar este modo de funcionamiento. 6.2 Cambios en el software Para que nuestra nueva configuración funcione deberemos hacer cambios a nivel de código para poder hacer uso de las nuevas ruedas. Uno de los principales cambios será el ajuste de la recepción de datos por puerto serie para poder recoger las nuevas variables necesarias, además de incluir una selección de modo de funcionamiento para poder cambiar entre modo diferencial y omnidireccional. Primeramente veamos el fundamento teórico del modo omnidireccional y sus ecuaciones. 6.2.1 Fundamentos del modo omnidireccional Basándonos en la Figura 2.8 vista anteriormente , se definen las velocidades de cada rueda con las siguientes ecuaciones [4]: 6.2 Cambios en el software 39 ω1=1 R · Vx−Vy− Lx+Ly 2!·ω!(6.1) ω2=1 R · Vx+Vy+ Lx+Ly 2!·ω!(6.2) ω3=1 R · Vx+Vy− Lx+Ly 2!·ω!(6.3) ω4=1 R · Vx−Vy+ Lx+Ly 2!·ω!(6.4) Donde: •R es el radio de las ruedas •Vx es la velocidad en el eje X del robot •Vy es la velocidad en el eje Y del robot •Lx es la distancia en el eje X entre las ruedas de ese eje •Ly es la distancia en el eje Y entre las ruedas de ese eje 6.2.2 Cambios en el código Debido a que no queremos sustituir el modo diferencial previamente desarrollado, lo que haremos será incluir la opción de elegir entre los dos modos. Un byte dentro del mensaje que enviamos desde la Jetson será el que dictaminará que ecuaciones se aplican, permitiéndonos así una mayor flexibilidad. Ademas, debido a que las variables necesarias cambian de un modelo a otro, deberemos ajustar el código encargado de procesar los datos que llegan desde el puerto serie para incluir las nuevas variables. El mensaje enviado desde la Jetson tendrá el siguiente formato, $Vx Vy Ang Modo CRC;. Si se quiere usar el modo diferencial, se introducirá un cero en las variables no usadas, en este caso Vy. No hace falta realizar cambios en el nodo de ROS ya que acepta cualquier mensaje de entrada. 46 Capítulo 7. Pruebas de funcionamiento •Variamos el parámetro D, encargado de prevenir la sobreoscilacion y suavizar la respuesta: –P: 0.1 –I: 0.01 –D: 50 Figura 7.9 Telemetría de los motores con el control 5. Figura 7.10 Señal de los actuadores para el control 5. Un valor más alto de D proporciona una respuesta más rápida a cambios en el error pero puede causar oscilaciones si se configura demasiado alto. Aun así, se puede observar un comportamiento aceptable, excepto por pequeños errores producidos durante la subida del escalón. 7.2 Funcionamiento del nodo 47 Un comportamiento singular observado durante las pruebas es la activación momentánea de los motores al iniciar la conexión por puerto serie. Esto hace que se registren velocidades al inicio mientras no se les esta dando una señal de control, provocando un aumento del termino integral del control y una primera respuesta ante una señal de control muy lenta, ya que el control tiene que remontar el termino integral acumulado mientras está parado. Es por eso que se decidió empezar a controlar y registrar las velocidades una vez se hayan recibido unas referencias validas a través del puerto serie, solucionando el problema. 7.2 Funcionamiento del nodo En esta prueba se ha accedido a la Jetson nano mediante el uso del protocolo de red SSH, conectándonos y permitiéndonos usar el terminal de forma remota. Con esto se han abierto 3 terminales para controlar y visualizar diferentes aspectos del funcionamiento del nodo, como se puede ver en la Figura 7.11 Figura 7.11 Conexión mediante SSH. A continuación publicamos un mensaje en el topic y vemos la respuesta del nodo. Como se ha explicado anteriormente en Subsección 5.4.3, publicamos una vez el mensaje deseado con los valores de velocidades y el nodo a continuación calculará el checksum y lo añadirá al final del mensaje. El nodo nos irá devolviendo por consola los diferentes pasos que ha seguido el nodo, ya sea el mensaje inicial recibido, el checksum calculado, y el mensaje final enviado. 48 Capítulo 7. Pruebas de funcionamiento Figura 7.12 Publicación de mensaje en tópico. Figura 7.13 Respuesta del nodo. 7.3 Prueba experimental completa Como prueba final, se procederá a darle instrucciones al robot para que realice una forma cuadrada en el suelo, terminando con un movimiento en diagonal. Se hará uso de la configuración omnidireccional. Se preparan unas referencias en el suelo que marcarán las esquinas de un cuadrado de un metro cuadrado. El robot empezará en una esquina y irá recorriendo el contorno hasta volver al principio, donde realizará un desplazamiento diagonal hasta la esquina numero 3. Con el dato de ser un cuadrado de un metro de lado, sabemos que en aproximadamente 5 segundos el robot llegara a la referencia si se le indica una velocidad de 20 cm/s. 7.3 Prueba experimental completa 49 Figura 7.14 Prueba experimental - movimiento deseado. Figura 7.15 Prueba experimental - robot en posición inicial. 50 Capítulo 7. Pruebas de funcionamiento Figura 7.16 Prueba experimental - ultimo movimiento diagonal. Figura 7.17 Prueba experimental - llegada al punto final. 7.3 Prueba experimental completa 51 Figura 7.18 Prueba experimental - datos de telemetría(Velocidades). Figura 7.19 Prueba experimental - datos de telemetría(Actuadores). 8 Conclusiones E l desarrollo de una plataforma de pruebas para un proyecto busca la creación de unas bases solidas sobre las que poder expandirse. A lo largo de los capítulos anteriores se han enseñado y explicado todos los procesos seguidos en la creación de este TFG, buscando la mayor claridad posible para que los futuros desarrolladores que trabajen en el proyecto. Es por eso que todo el código desarrollado ha sido pensado para poder ampliarse con nuevas funciones de manera sencilla, como el sistema de envío y recepción de tramas. También en la parte practica de construcción he intentado crear una plataforma fiable, organizando los cables, soldando y fijando todos los elementos sensibles buscando la mayor fiabilidad. 8.1 Resultados obtenidos En este trabajo, se ha logrado con éxito el diseño, montaje y puesta en funcionamiento de una plataforma de pruebas, cumpliendo con todos los objetivos planteados inicialmente. Se inició con el montaje de un motor y la implementación de un sistema de control PID monitorizado mediante Arduino, asegurando el correcto estado y funcionamiento de los motores. Esta fase inicial fue crucial para sentar las bases de un sistema robusto y confiable. Posteriormente, se ensambló el chasis y se instalaron los motores, prestando especial atención al conexionado necesario para su óptimo funcionamiento. Esta etapa fue fundamental para garantizar la integridad estructural del robot y la correcta transmisión de fuerza a las ruedas. La ampliación del código de Arduino y el procesamiento de mensajes serie permitieron una gestión eficiente de todos los motores, así como una comunicación efectiva con otros componentes del sistema. La adaptación del código para manejar múltiples motores simultáneamente y la gestión de la información entrante fueron pasos esenciales para lograr una operatividad completa del robot. La inicialización de la Jetson Nano y la creación del nodo de comunicación representaron un avance significativo en el desarrollo del software y en la capacidad de comunicación del sistema. Esto permitió una integración efectiva entre el hardware y el software, esencial para el funcionamiento autónomo del robot. En la fase final, se completó el montaje del robot, ubicando todos los elementos necesarios y realizando el soldaje de cables. La colocación estratégica de la Jetson junto al Arduino y la batería en sus ubicaciones finales fue crucial para el equilibrio y la distribución del peso del robot. Las pruebas de funcionamiento finales fueron fundamentales para verificar la operatividad del robot y realizar ajustes necesarios. La transición a ruedas omnidireccionales y la adición de un cambio de modo de funcionamiento ampliaron significativamente las capacidades de movimiento del robot, demostrando su versatilidad y eficiencia. Este proyecto ha logrado con éxito la creación de la plataforma de pruebas completamente funcional. 53 54 Capítulo 8. Conclusiones 8.2 Lineas futuras de trabajo El primer paso de este proyecto se ha encargado de desarrollar la base y todos los sistemas de bajo nivel. Un siguiente paso sería el de integrar los sistemas de alto nivel necesarios para una operación autónoma del sistema. Comunicaciones a larga distancia, elementos como sensores que den visión e información del entorno permitirían que el robot se aproxime al sistema real. Una vez hecho esto, ya se podría trabajar sobre la plataforma de pruebas antes de implementar los elementos desarrollados a la embarcación real. Apéndice A Códigos de Arduino Aqui se incluyen los códigos utilizados en el arduino A.1 Código principal de arduino - main.cpp #include <Arduino.h> #include "utils/PID.h" static const uint8_t crc_table[] = { 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15, 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d, 0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65, 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d, 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2, 0xf5, 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd, 0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85, 0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd, 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2, 0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea, 0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2, 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a, 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32, 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a, 0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42, 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a, 0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c, 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4, 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec, 0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4, 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c, 0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44, 0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c, 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34, 0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b, 0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63, 0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b, 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13, 0xae, 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb, 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83, 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb, 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3 }; // Objetivo y la velocidad actual/pasada float y_ref[4] = {0,0,0,0}; float y[4] = {0,0,0,0}; 55 62 Capítulo A. Códigos de Arduino while (dataLength--) {byte extract = *data++; for (byte tempI = 8; tempI; tempI--) {byte sum = (crc ^ extract) & 0x01; crc >>= 1; if (sum) {crc ^= 0x8C; } extract >>= 1; } } return crc; } void speed_data(){ Serial.print(time_change); Serial.print(" "); Serial.print(y_ref[0]); Serial.print(" "); Serial.print(y_ref[1]); Serial.print(" "); Serial.print(y_ref[2]); Serial.print(" "); Serial.print(y_ref[3]); Serial.print(" "); Serial.print(y[0]); Serial.print(" "); Serial.print(y[1]); Serial.print(" "); Serial.print(y[2]); Serial.print(" "); Serial.print(y[3]); Serial.print(" "); Serial.print(sent[0]); Serial.print(" "); Serial.print(sent[1]); Serial.print(" "); Serial.print(sent[2]); Serial.print(" "); Serial.print(sent[3]); Serial.print(" "); Serial.print(u[0]); Serial.print(" "); Serial.print(u[1]); Serial.print(" "); Serial.print(u[2]); Serial.print(" "); A.2 Clase PID 63 Serial.print(u[3]); Serial.println(" "); } A.2 Clase PID // PID.h #ifndef PID_h #define PID_h #include <Arduino.h> class PID { private: float y; // this is the input of the PID controller float y_prev; // this is the input of the PID controller in t-1 float u; // this is the output of the PID controller float e; // this is the error of the PID controller float prev_e; // this is the error of the PID controller in t-1 float prev_integral; // this is the integral of the error of the PID controller float Kp; // this is the proportional gain of the PID controller float Ki; // this is the integral gain of the PID controller float Kd; // this is the derivative gain of the PID controller float Ts; // this is the sample time int PWM_pin; // this is the pin where the PWM signal is sent to the motor int PWM_direction_pin; // this is the pin where the direction of the motor is set float PWM_max; // this is the maximum value of the PWM signal float PWM_min; // this is the minimum value of the PWM signal public: // Here we define the methods of the class PID // This is the constructor of the class PID PID(float Kp, float Ki, float Kd, int PWM_pin, int PWM_direction_pin, float PWM_max, float PWM_min, float Ts); // Constructor of the class PID // This is the method that updates the PID controller void update(float y, float y_ref, char sent); //Output getter 64 Capítulo A. Códigos de Arduino float getU(); }; #endif A.3 Objeto PID #include "PID.h" #include <Arduino.h> PID::PID(float Kp, float Ki, float Kd, int PWM_pin, int PWM_direction_pin, float PWM_max, float PWM_min, float Ts) {/* Here we initialize the variables of the class PID-Controller */ this->Kp = Kp; this->Ki = Ki; this->Kd = Kd; this->PWM_pin = PWM_pin; this->PWM_direction_pin = PWM_direction_pin; this->PWM_max = PWM_max; this->PWM_min = PWM_min; this->Ts = Ts; this->y = 0; this->y_prev = 0; this->e = 0; this->prev_e = 0; this->prev_integral = 0; this->u = 0; // Set the pinout pinMode(PWM_pin, OUTPUT); pinMode(PWM_direction_pin, OUTPUT); // Set the initial direction of the motor digitalWrite(PWM_direction_pin, HIGH); //mot 2,3,4 adelante, 1 hacia atras } void PID::update(float y, float y_ref, char sent) { A.3 Objeto PID 65 /* Here starts the Update of the PID */ //first check in which direction the motor is spinning, compared to y_ref if(y_ref < 0){ if(sent == '1'){ digitalWrite(PWM_direction_pin, HIGH); } y_ref = abs(y_ref); }else{ if(sent == '0'){ digitalWrite(PWM_direction_pin, LOW); } } // error this->e = y_ref - y; //derivative float derivative = (e - prev_e) / Ts; //integral float integral = prev_integral + e * Ts; // PID: this->u = Kp * e + Ki * integral + Kd * derivative; // Saturation if (u > PWM_max) this->u = PWM_max; else if (u < PWM_min) this->u = PWM_min; else // Anti-windup this->prev_integral = integral; // store the state for the next iteration this->prev_e = e; this->prev_integral = integral; // Send the PWM signal to the motor controller analogWrite(PWM_pin,u); } float PID::getU(){ return this->u; } Apéndice B Códigos de ROS B.1 Código del nodo #include <ros/ros.h> #include <std_msgs/String.h> #include <serial/trama.h> #include <boost/asio.hpp> #include <cstdint> #include <cstddef> // Tabla de CRC8 (polinomio x^8 + x^2 + x^1 + x^0, 0x07) precalculada static const uint8_t crc8_table[256] = { 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0 x31, 0x24, 0x23, 0x2A, 0x2D, 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0 x41, 0x54, 0x53, 0x5A, 0x5D, 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0 xD1, 0xC4, 0xC3, 0xCA, 0xCD, 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0 xA1, 0xB4, 0xB3, 0xBA, 0xBD, 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0 xF6, 0xE3, 0xE4, 0xED, 0xEA, 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0 x86, 0x93, 0x94, 0x9D, 0x9A, 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0 x16, 0x03, 0x04, 0x0D, 0x0A, 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0 x66, 0x73, 0x74, 0x7D, 0x7A, 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0 xB8, 0xAD, 0xAA, 0xA3, 0xA4, 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0 xC8, 0xDD, 0xDA, 0xD3, 0xD4, 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0 x58, 0x4D, 0x4A, 0x43, 0x44, 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0 x28, 0x3D, 0x3A, 0x33, 0x34, 67 68 Capítulo B. Códigos de ROS 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0 x7F, 0x6A, 0x6D, 0x64, 0x63, 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0 x0F, 0x1A, 0x1D, 0x14, 0x13, 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0 x9F, 0x8A, 0x8D, 0x84, 0x83, 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0 xEF, 0xFA, 0xFD, 0xF4, 0xF3 }; uint8_t calculateCRC8(const uint8_t *data, size_t length) {uint8_t crc = 0; for (size_t i = 0; i < length; ++i) {crc = crc8_table[crc ^ data[i]]; } return crc; } void callback(const std_msgs::String::ConstPtr& msg) {ROS_INFO("Mensaje recibido: %s", msg->data.c_str()); // Se convierte el mensaje a un arreglo de bytes const uint8_t* message_data = reinterpret_cast<const uint8_t*>(msg-> data.c_str()); size_t message_length = msg->data.length(); // Se calcula el CRC8 del mensaje uint8_t crc_result = calculateCRC8(message_data, message_length); ROS_INFO("CRC8 calculado: 0x%02X", crc_result); // Abre una conexión serie boost::asio::io_service io; boost::asio::serial_port serial(io, "/dev/ttyACM0"); // Reemplaza "/ dev/ttyUSB0" con tu puerto serie serial.set_option(boost::asio::serial_port_base::baud_rate(115200)); // Se agrega el checksum std::string new_message = msg->data + " " + std::to_string( crc_result)+ ";"; ROS_INFO("Mensaje a enviar: %s", new_message.c_str()); // Se envía el mensaje a través del puerto serie boost::asio::write(serial, boost::asio::buffer(new_message)); B.2 Código del archivo launch 69 serial.close(); } int main(int argc, char** argv) {ros::init(argc, argv, "serial_node"); ros::NodeHandle nh; ros::Subscriber sub = nh.subscribe("msg_serial", 10, callback); ros::spin(); return 0; } B.2 Código del archivo launch <launch> <node pkg="serial" type="serial_node" name="serial_node" output="screen"> </node> </launch> Índice de Figuras 2.1 Morfologias de robots de superficie 6 2.2 Distintos tipos de ruedas disponibles 6 2.3 Configuración Ackerman 7 2.4 Configuración Triciclo 7 2.5 Configuración diferencial 8 2.6 Configuración skid-steer 9 2.7 Configuración sincrona 9 2.8 Configuración Omnidireccional 10 3.1 Motor de 12VDC, 200 rpm 12 3.2 Controlador puente H dual basado en MOSFER IRF3205 13 3.3 Motor con encoder de cuadratura instalado 14 3.4 Esquema de pulsos de un encoder de cuadratura 14 3.5 Arduino DUE 15 3.6 Esquema de pines y conexiones de Arduino DUE 16 3.7 NVIDIA Jetson nano 17 4.1 Conexión de prueba de un motor 20 4.2 Instalación de motores en chasis metálico 20 4.3 Chasis con los motores ya instalados 21 4.4 Instalación de los cuatro motores en el chasis 21 4.5 Conexión de alimentación, convertidor DC/DC 22 4.6 Esquemático de conexión 23 5.1 Diagrama de bloques de funcionamiento 25 5.2 Esquema de un sistema de lazo cerrado 26 5.3 Diagrama de bloques para un PID 27 5.4 Directorio de trabajo 33 5.5 Directorio del nodo 34 6.1 Ruedas omnidireccionales instaladas en el robot 37 6.2 Posición elegida para nuestras ruedas 38 7.1 Telemetría de los motores con el control 1 42 7.2 Señal de los actuadores para el control 1 42 7.3 Telemetría de los motores con el control 2 43 7.4 Señal de los actuadores para el control 2 43 71