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.