Fabricando un coche de competición

El curso pasado puse un marcha un proyecto para alumnos de 1º Bachillerato denominado 20% Project. En este proyecto los alumnos disponen del 20% del tiempo anual de la asignatura para realizar un proyecto que esté relacionado con algo que les apasione. Tiene que tener una base tecnológica, donde la única condición previa es que creen algún prototipo de algo con tecnologías de las cuales no tengan ningún conocimiento previo.

De esta forma instamos a los alumnos a que sean ellos los que decidan qué quieren hacer, qué necesitan aprender, planificar y analizar la evolución de forma mensual escribiendo un post en el blog del proyecto respondiendo a 3 preguntas: ¿qué dificultades he tenido?¿cómo las he superado?¿cuales son mis siguientes pasos?.

El primero de los proyectos que este año os quiero compartir es el de Héctor Lizar. Su pasión era construir un coche siguelíneas que pudiera participar en competiciones. Por lo tanto, su primera labor de investigación fue conocer el reglamento de la Liga Nacional de Robots, que nos iba a condicionar en los componentes a utilizar y en el diseño del coche.

En otro proyecto ya os enseñamos a cómo montar un siguelineas en un coche, pero en este caso, la dificultad aumenta porque es necesario introducir fórmulas matemáticas para conseguir la mejor combinación de velocidad, distribución de cargas y agarre en curvas. En el código fuente publicado en nuestra web Héctor os contará lo necesario para que el rendimiento sea el óptimo.

Por otra parte está el diseño del coche y los componentes a introducir: un Arduino Nano, barra de sensores, los motores, un driver de motores para poder gestionar bien su potencia, la placa PCB, un led y un zumbador. Además, hay que perforar el chasis del robot con los orificios necesarios para ensamblarlos junto con el interruptor de encendido.

Finalmente, antes de comenzar a programar y hacer los test de calibración necesarios, se probaron varias alternativas con diagramas de la distribución y conexiones entre ellos para su posterior trabajo de soldado.

Os mostramos el proceso de montaje paso a paso:

Os mostramos también en imagen ampliada quien es quien en el coche de competición:

 

Obviamente, la parte casi más importante de todo el proyecto es el algoritmo que va a controlar el coche para que circule de forma autónoma siguiendo las líneas. Pero lo que será más importante es la función que va a controlar el freno en las curvas de forma inteligente y posterior aceleración. Veamos a continuación el código:

 

/* MACE
* System v0.2
* Héctor Lizar
* 1 BACH - Colegio Juan de Lanuza
* May 2018
*
* Sensor Infrarrojos: Pololu QT8RC Digital
* Driver L9110
Motor 1
+ D10 -> B-IA -> PWM
+ D11 -> B-IB -> Direction

Motor 2
+ D6 -> A-IA -> PWM
+ D9 -> A-IB -> Direction
*/

//------ Sensor QT8RC Digital
#include <QTRSensors.h>
#define NUM_SENSORS 6 // number of sensors used
#define TIMEOUT 2000 // waits for 2500 microseconds for sensor outputs to go low
#define EMITTER_PIN A4 // emitter is controlled by digital pin 2

// sensors 0 through 7 are connected to digital pins 3 through 10, respectively
QTRSensorsRC qtrrc((unsigned char[]) {12, 2, 4, 5, 7, 8}, NUM_SENSORS, TIMEOUT, EMITTER_PIN);
unsigned int sensorValues[NUM_SENSORS]; //Almacena los valores del sensor
unsigned int position=0; //Almacena la posición

// ------- Driver Motores L9110
// wired connections
#define HG7881_B_IA 10 // D10 --> Motor B Input A --> MOTOR B +
#define HG7881_B_IB 11 // D11 --> Motor B Input B --> MOTOR B -
#define HG7881_A_IA 6 // D6 --> Motor A Input A --> MOTOR A +
#define HG7881_A_IB 9 // D9 --> Motor A Input B --> MOTOR A -

// functional connections
#define MOTOR_B_PWM HG7881_B_IA // Motor B PWM Speed
#define MOTOR_B_DIR HG7881_B_IB // Motor B Direction
#define MOTOR_A_PWM HG7881_A_IA // Motor A PWM Speed
#define MOTOR_A_DIR HG7881_A_IB // Motor A Direction

// the actual values for "fast" and "slow" depend on the motor
#define PWM_SLOW 50 // arbitrary slow speed PWM duty cycle
#define PWM_FAST 200 // arbitrary fast speed PWM duty cycle
#define DIR_DELAY 1000 // brief delay for abrupt motor changes

// ----- Variables para el PID
int derivativo=0, proporcional=0, integral=0; //parametros
int salida_pwm=0, proporcional_pasado=0;

//*****************************************************************************************************
//********************* Parámetros a calibrar para el PID (En función de las características del robot
//*****************************************************************************************************
int velocidad=120; //variable para la velocidad, el maximo es 255 (pruebas a 120)
float Kp=0.5, Kd=4, Ki=0.001; //constantes (Kd=4 pruebas)
int linea=0; // 0 para lineas negra con fondo blanco, 1 para lineas blancas con fondo negro

//------ Otros
const int pinLED=13;
const int pinPulsador=A1;
const int pinBuzzer=3;

void esperaPulsador() // espera la pulsación del pulsador
{
while(true)
{
digitalWrite(pinLED,HIGH); //Apaga el LED
int x=digitalRead(pinPulsador); //leemos y guardamos el valor
delay(100);
if(x==HIGH) //si se presiona boton
{
tone(pinBuzzer, 440,200); // pitido corto
digitalWrite(pinLED,LOW); //Apaga el LED
delay(200);
break; //saltamos hacia el bucle principal
}
}
}

void setup()
{
//------- Otros
pinMode(pinLED, OUTPUT);
pinMode(pinPulsador, INPUT);

//------- Motores
pinMode( MOTOR_B_DIR, OUTPUT );
pinMode( MOTOR_B_PWM, OUTPUT );
digitalWrite( MOTOR_B_DIR, LOW ); //Para el motor B
digitalWrite( MOTOR_B_PWM, LOW );

pinMode( MOTOR_A_DIR, OUTPUT );
pinMode( MOTOR_A_PWM, OUTPUT );
digitalWrite( MOTOR_A_DIR, LOW ); //Para el motor A
digitalWrite( MOTOR_A_PWM, LOW );

//------- Sensor QT8RC
// Calibración: Cuando se enciende el LED verde y suena el pitido -> mover el sensor por encima de la línea
// Durante 10 seg
delay(500);
digitalWrite(pinLED, HIGH); // turn on Arduino's LED to indicate we are in calibration mode
tone(pinBuzzer, 440,200); // pitido corto
delay(200);
esperaPulsador();

for (int i = 0; i < 300; i++) // make the calibration take about 10 seconds (Original=400)

{
qtrrc.calibrate(); // reads all sensors 10 times at 2500 us per read (i.e. ~25 ms per call)
}
digitalWrite(pinLED, LOW); // turn off Arduino's LED to indicate we are through with calibration
tone(pinBuzzer, 494,500); // pitido largo
delay(500);

Serial.begin(9600);

// Muestra los valores de la calibración
// print the calibration minimum values measured when emitters were on

for (int i = 0; i < NUM_SENSORS; i++)
{
Serial.print(qtrrc.calibratedMinimumOn[i]);
Serial.print(' ');
}
Serial.println();

// print the calibration maximum values measured when emitters were on
for (int i = 0; i < NUM_SENSORS; i++)
{
Serial.print(qtrrc.calibratedMaximumOn[i]);
Serial.print(' ');
}
Serial.println();
Serial.println();
delay(1000);

// Esperar a que se pulse al botón
digitalWrite(pinLED,HIGH); //encendiendo led 1
tone(pinBuzzer, 440,200); // pitido corto
delay(200);

esperaPulsador();

}

void loop()
{
//--------- Sensor QT8RC
// read calibrated sensor values and obtain a measure of the line position from 0 to 5000
// To get raw sensor values, call:
// qtrrc.read(sensorValues); instead of unsigned int position = qtrrc.readLine(sensorValues);
//unsigned int position = qtrrc.readLine(sensorValues);

// print the sensor values as numbers from 0 to 1000, where 0 means maximum reflectance and
// 1000 means minimum reflectance, followed by the line position
/* for (unsigned char i = 0; i < NUM_SENSORS; i++)
{
Serial.print(sensorValues[i]);
Serial.print('\t');
}
//Serial.println(); // uncomment this line if you are using raw values
Serial.println(position); // comment this line out if you are using raw values

delay(250);
*/

//--- Loop del ejemplo

pid(linea,velocidad,Kp,Ki,Kd); //funcion para algoritmo pid

//*************************************
//*************************************
//frenos_contorno(0,700);
frenos_contorno(linea,700); //funcion para frenado en curvas tipo ************ Prueba quitando está linea

}

////////funciones para el control del robot////
void pid(int linea, int velocidad, float Kp, float Ki, float Kd)
{
position = qtrrc.readLine(sensorValues, QTR_EMITTERS_ON, linea); //0 para linea negra, 1 para linea blanca
proporcional = (position) - 2700; // set point es 3500, asi obtenemos el error
Serial.print("position:"); Serial.println(position);

integral=integral + proporcional_pasado; //obteniendo integral
derivativo = (proporcional - proporcional_pasado); //obteniedo el derivativo
if (integral>1000) integral=1000; //limitamos la integral para no causar problemas
if (integral<-1000) integral=-1000;
salida_pwm =( proporcional * Kp ) + ( derivativo * Kd )+(integral*Ki);

if ( salida_pwm > velocidad ) salida_pwm = velocidad; //limitamos la salida de pwm
if ( salida_pwm < -velocidad ) salida_pwm = -velocidad;

if (salida_pwm < 0)
{
motores(velocidad+salida_pwm, velocidad);
}
if (salida_pwm >0)
{
motores(velocidad, velocidad-salida_pwm);
}

proporcional_pasado = proporcional;
}

//----- Control de MOTORES
void motores(int motor_izq, int motor_der)
{
if ( motor_izq >= 0 ) //motor izquierdo
{
digitalWrite(MOTOR_B_DIR,HIGH); // con high avanza
analogWrite(MOTOR_B_PWM,255-motor_izq); //se controla de manera
//inversa para mayor control
}
else
{
digitalWrite(MOTOR_B_DIR,LOW); //con low retrocede
motor_izq = motor_izq*(-1); //cambio de signo
analogWrite(MOTOR_B_PWM,motor_izq);
}

if ( motor_der >= 0 ) //motor derecho
{
digitalWrite(MOTOR_A_DIR,HIGH);
analogWrite(MOTOR_A_PWM,255-motor_der);
}
else
{
digitalWrite(MOTOR_A_DIR,LOW);
motor_der= motor_der*(-1);
analogWrite(MOTOR_A_PWM,motor_der);
}
}

void frenos_contorno(int tipo,int flanco_comparacion)
{

if(tipo==0)
{
if(position<=50) //si se salio por la parte derecha de la linea
{
motores(-80,90); //debido a la inercia, el motor
//tendera a seguri girando
//por eso le damos para atras , para que frene
// lo mas rapido posible
while(true)
{
qtrrc.read(sensorValues); //lectura en bruto de sensor
if( sensorValues[0]>flanco_comparacion || sensorValues[1]>flanco_comparacion )
//asegurar que esta en linea
{
break;
}
}
}

if (position>=5000) //si se salio por la parte izquierda de la linea
{
motores(90,-80);
while(true)
{
qtrrc.read(sensorValues);
if(sensorValues[5]>flanco_comparacion || sensorValues[4]>flanco_comparacion )
{
break;
}
}
}
}

if(tipo==1) //para linea blanca con fondo negro
{
if(position<=50) //si se salio por la parte derecha de la linea
{
motores(-80,90); //debido a la inercia el motor
//tendera a seguri girando
//por eso le damos para atras
//para que frene lo mas rapido posible
while(true)
{
qtrrc.read(sensorValues); //lectura en bruto de sensor
if(sensorValues[0]<flanco_comparacion || sensorValues[1]<flanco_comparacion ) //asegurar que esta en linea
{
break;
}
}

if(position>=5000) //si se salio por la parte izquierda de la linea
{
motores(90,-80);
while(true)
{
qtrrc.read(sensorValues);
if(sensorValues[5]<flanco_comparacion || sensorValues[4]<flanco_comparacion)
{
break;
}
}
}
}
}
}

En el mes de Mayo Héctor finalizó su proyecto realizando una charla a sus compañeros donde contó por qué le apasionaba hacer este proyecto, qué dificultades había tenido, las tecnologías utilizadas y realizó una demostración del coche en un circuito de carreras simulado. Podéis ver el video a continuación:

 

Como profesor de estos alumnos, fue increíble lo vivido durante el año. Pasamos de un mar de dudas al comienzo del curso a un ambiente de trabajo fluido donde ellos fueron casi casi autónomos y yo sólo tuve que acompañarlos y guiarlos en el proceso.

Finalmente os dejo con Héctor que os va a contar mejor que nadie cómo fue su 20% Time Project:

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *