Creando un carro de conducción autónoma
Este es un proyecto muy especial dentro de mi portafolio. Lo considero como un hito importante, porque el proceso detrás del prototipo lleva todo un análisis lógico y matemático.
El proyecto en sí, fue desarrollado desde 0, tanto en el armado del hardware (cuyo modelo ya existe en el mercado), como con el desarrollo del software. Todo el sistema fue codificado desde 0, para hacer más eficiente el código que se encuentra en línea en muchos sitios de internet. También vale la pena mencionar que el prototipo pasó por varios procesos de adaptación, para garantizar el correcto funcionamiento.
Hablemos sobre el código
Es importante mencionar que el funcionamiento de esta creación, fue desarrollada mediante una placa Arduino Uno, y un Shell Controlador de motores, por lo que utiliza algunas de las librerías para garantizar el funcionamiento de todo nuestro sistema.
#include <Servo.h> // Servomotor #include <AFMotor.h> // Shell controlador de motores
Se asignan todos los pines que se requieren para el correcto funcionamiento de nuestro vehículo autónomo.
#define SERVO 10 #define Echo 9 #define Trigger 2 #define LedDerechaRojo A0 #define LedDerechaVerde A1 #define LedIzquierdaRojo A2 #define LedIzquierdaVerde A3 #define LedFrenteRojo A4 #define LedFrenteVerde A5
Y, por supuesto, se declaran y asignan todos y cada uno de los motores del carro.
Servo servo; AF_DCMotor motorIzqFren(1); AF_DCMotor motorDerFren(2); AF_DCMotor motorDerAtr(3); AF_DCMotor motorIzqAtr(4);
Hasta este punto, todo lo que hemos hecho, ha sido definir las variables globales y llamar a las liberías que pretendemos utilizar. Ahora, empezamos a generar el bloque de setup.
Esta función se ejecuta una sola vez al iniciar el programa y no vuelve a ejecutarse. La función define los parámetros por defecto de nuestro servomotor, de los motores de corriente directa, asegura la configuración de los pines del sensor ultrasónico y de los seis leds conectados al arduino. Igualmente, asigna una velocidad de transmisión al serial del arduino UNO al que se encuentra conectado el dispositivo.
void setup() { servo.attach(SERVO); // Se asigna el pin asignado al servomotor servo.write(90); // Posición inicial del servo motorIzqFren.setSpeed(200); // Se asigna velocidad al motorIzqFren motorDerFren.setSpeed(200); // Se asigna velocidad al motorDerFren motorDerAtr.setSpeed(200); // Se asigna velocidad al motorDerAtr motorIzqAtr.setSpeed(200); // Se asigna velocidad al motorIzqAtr pinMode(Trigger, OUTPUT); // Se asigna el pin del Trigger pinMode(Echo, INPUT); // Se asigna el pin del Echo Serial.begin(9600); // Se inicializa la comunicación serial // Se asignan los pines a los leds pinMode(LedDerechaRojo, OUTPUT); pinMode(LedDerechaVerde, OUTPUT); pinMode(LedIzquierdaRojo, OUTPUT); pinMode(LedIzquierdaVerde,OUTPUT); pinMode(LedFrenteRojo, OUTPUT); pinMode(LedFrenteVerde, OUTPUT); }
Ahora, creamos una función que nos permita obtener la distancia con respecto a los objetos más cercanos. Para hacerlo, necesitamos hacer uso del sensor ultrasónico del arduino, mediante este pequeño bloque de código:
int getDistancia(){ digitalWrite(Trigger, HIGH); // Envía un pulso ultrasónico delayMicroseconds(10); // Espera 10 us digitalWrite(Trigger, LOW); // Apaga el pulso ultrasónico return pulseIn(Echo, HIGH) / 59; // Convierte el resultado en cm }
Adicionalmente, es necesario crear un método que haga que el carro avance. En otras palabras, lo que el método debe hacer es asignar la dirección que debe tomar cada uno de los motores y asignar el voltaje que se debe enviar a cada uno de los leds. Las posiciones de BACKWARD y FORWARD pueden cambiar dependiendo de la forma en que conectemos los motores.
void runCar() { motorIzqFren.run(BACKWARD); motorDerFren.run(FORWARD); motorDerAtr.run(FORWARD); motorIzqAtr.run(BACKWARD); analogWrite(LedFrenteVerde, 128); analogWrite(LedFrenteRojo, 0); analogWrite(LedDerechaVerde, 0); analogWrite(LedDerechaRojo, 128); analogWrite(LedIzquierdaVerde, 0); analogWrite(LedIzquierdaRojo, 128); }
Igualmente, necesitamos un método que funcione de la forma opuesta, es decir, que nos permita retroceder en lugar de avanzar. Para ello, utilizamos el siguiente método:
void backCar(){ motorIzqFren.run(FORWARD); motorDerFren.run(BACKWARD); motorDerAtr.run(BACKWARD); motorIzqAtr.run(FORWARD); analogWrite(LedFrenteVerde, 0); analogWrite(LedFrenteRojo, 128); analogWrite(LedDerechaVerde, 0); analogWrite(LedDerechaRojo, 128); analogWrite(LedIzquierdaVerde, 0); analogWrite(LedIzquierdaRojo, 128); }
Agregamos dos métodos, uno para girar a la izquierda, y otro para girar a la derecha.
void turnRightCar() { motorIzqFren.run(BACKWARD); motorDerFren.run(BACKWARD); motorDerAtr.run(BACKWARD); motorIzqAtr.run(BACKWARD); analogWrite(LedFrenteVerde, 0); analogWrite(LedFrenteRojo, 128); analogWrite(LedDerechaVerde, 128); analogWrite(LedDerechaRojo, 0); analogWrite(LedIzquierdaVerde, 0); analogWrite(LedIzquierdaRojo, 128); } void turnLeftCar() { motorIzqFren.run(FORWARD); motorDerFren.run(FORWARD); motorDerAtr.run(FORWARD); motorIzqAtr.run(FORWARD); analogWrite(LedFrenteVerde, 0); analogWrite(LedFrenteRojo, 128); analogWrite(LedDerechaVerde, 0); analogWrite(LedDerechaRojo, 128); analogWrite(LedIzquierdaVerde, 128); analogWrite(LedIzquierdaRojo, 0); }
Finalmente, debemos crear una función que nos permita detenernos, para poder con ella, tomar mediciones de las distancias, y evitar chocar contra obstáculos.
void stopCar() { motorIzqFren.run(RELEASE); motorDerFren.run(RELEASE); motorDerAtr.run(RELEASE); motorIzqAtr.run(RELEASE); analogWrite(LedFrenteVerde, 0); analogWrite(LedFrenteRojo, 128); analogWrite(LedDerechaVerde, 0); analogWrite(LedDerechaRojo, 128); analogWrite(LedIzquierdaVerde, 0); analogWrite(LedIzquierdaRojo, 128); }
Ahora, vamos a trabajar exclusivamente sobre la función main, y el vehículo deberá siempre de iniciar midiendo la distancia
Serial.println(getDistancia()); // Obtiene la distancia if(getDistancia()<15){ // Las siguientes lineas de código sirven como preámbulo stopCar(); // Detiene el carro delay(100); // Espera 100 ms. backCar(); // Retrocede el carro delay(300); // Permanece en el estado de retroceso por 300 ms. stopCar(); // Detiene el carro }
Seguimos operando dentro de la instrucción if, para obtener la información de todo aquello que se encuentre a la izquierda.
servo.write(180); // El servomotor se posiciona en 180° (A la izquierda) delay(1000); // Espera 1s int left = getDistancia(); // Obtiene la distancia que se encuentra a la izquierda Serial.print(left); // Envia el valor de la distancia por comunicación serial Serial.print(" | "); // Envía un símbolo que servirá de división
Hacemos el mismo proceso para el lado derecho:
servo.write(0); // El servomotor se posiciona en 0° (A la derecha) delay(1000); // Espera 1s int right = getDistancia(); // Obtiene la distancia que se encuentra a la derecha Serial.println(right); // Envia el valor de la distancia por comunicación serial servo.write(90); // El servomotor se posiciona en 90° (Al centro)
Y finalmente, tomamos la decisión sobre la dirección que debemos usar:
if(left>right){ turnLeftCar(); // Gira a la izquierda Serial.println("Left"); // Envía decisión por comunicación serial } else{ turnRightCar(); // Gira a la derecha Serial.println("Right");// Envía decisión por comunicación serial }
Y definimos un delay antes de continuar.
delay(800); // Espera 800ms para que el carro gire stopCar(); // Detiene el carro
Y, evidentemente, tomamos la decisión para un else, que nos permita avanzar sin ningún problema.
else{ runCar(); // Avanza de manera por defecto }
Todo el código, así como el diagrama de construcción, se encuentran liberados, y con el código sobrecomentado, en su repositorio de Github, bajo licencia MIT para software libre.