Sigue Líneas en LeJOS

Medidas sensor luminosidadEn este artículo voy a explicar el uso básico del sensor de luz y a realizar un par de programas de prueba: el sigue líneas básico y el sigue líneas proporcional. Ambos programas ya han sido explicados en NXT-G, por lo que podréis compararlos y ver que lleva menos trabajo hacer el sigue líneas en LeJOS (sobretodo el sigue líneas proporcional).

Uso básico del sensor de luz:

El sensor de luz tiene una clase en LeJOS llamadas LightSensor. Esta clase dispone de varios métodos para calibrar este sensor así como para establecer sus valores normalizados. En cualquier caso el sensor de luz es de uso sencillo y con los siguientes métodos bastará para nuestro propósito:

int readValue(): Lee el valor de luz y lo devuelve en un entero. Su valor es parecido al devuelto en NXT-G.

int readNormalizedValue(): Lee un valor normalizado, que es más preciso y de tres cifras, y lo devuelve en un entero.

Al igual que los demás sensores, el sensor de luz se debe definir con su método constructor, indicando a que puerto va a estar conectado. Se definiría de la siguiente manera:

LightSensor nombre = new LightSensor(SensorPort.S1);

Una vez definido ya estará listo para su uso.

Sigue líneas básico en zigzag:

El sigue líneas es un problema que consiste en que el robot camine por una línea sin salirse. Esto se puede resolver mediante el uso de diferentes sensores, y de multitud de formas dentro de cada sensor. Una de las formas básicas de solucionarlo es que el robot se dirija hacia el borde de la linea negra esté en lo negro o en lo blanco. Como resultado parecerá que va haciendo un pequeño zigzag, ya que nunca será capaz de ir exactamente por el borde.
SigueLíneas

La primera parte del programa será, como en todos los sigue líneas, el calibrado. Este calibrado sirve para que el robot distinga entre que es negro, que es blanco, y que sería el borde de la línea. Lo que haremos en este caso para calcular ese borde de la linea será: med = (max + min)/2. Este punto medio seria el sitio ideal por donde debería ir el robot. Para calibrarlo dejaríamos al robot en el centro de la línea e iniciaríamos el programa; el programa tomaría el valor del negro, luego giraría hasta el blanco y tomaría su valor, calcularía el valor medio, volvería a girar a la posición inicial (o a una que calculéis más cercana al borde de la línea), y finalmente empezaría con la parte de sigue líneas. Dicho todo esto la parte de calibrado quedaría de la siguiente manera:

min = sluz.readNormalizedValue();
LCD.drawString("Valor minimo:", 2, 3);
LCD.drawInt(min, 4, 6, 5);
Thread.sleep(2000);

Motor.A.rotate(-90,true);
Motor.B.rotate(90);
max = sluz.readNormalizedValue();
LCD.clear();
LCD.drawString(“Valor maximo:”, 2, 3);
LCD.drawInt(max, 4, 6, 5);
Thread.sleep(2000);

Motor.A.rotate(45,true);
Motor.B.rotate(-45);
med = (max + min)/ 2;
LCD.clear();
LCD.drawString(“Valor medio:”, 2, 3);
LCD.drawInt(med, 4, 6, 5);
Thread.sleep(2000);

Una vez calibrado sabemos cual es el valor medio hacia el que tiene que moverse el robot, por tanto lo ponemos en marcha y vamos leyendo los valores del sensor de luz, haciendo que si el valor es superior al valor medio paremos el motor derecho (en este caso el Motor.B) y aceleremos con el izquierdo (Motor.A en este caso), lo cual nos hará rotar hacia la izquierda, donde estará la línea. Si en caso contrario estamos muy dentro de la línea y leemos un valor menor al valor medio con el sensor de luz, hay que hacer la operación inversa (parar el motor izquierdo, y mover el motor derecho). Todo esto metido en un bucle infinito hace que el robot se mueva por el borde de la línea. La parte de código de sigue líneas sería la siguiente:

Motor.A.setSpeed(300);
Motor.B.setSpeed(300);
while (true) {
if (sluz.readNormalizedValue()>med){
Motor.A.forward();
Motor.B.stop();
}
else{
Motor.B.forward();
Motor.A.stop();
}
if (Button.ESCAPE.isPressed()){
break;
}
}

El programa completo sería (calibrado más sigue líneas):

import lejos.nxt.Button;
import lejos.nxt.LCD;
import lejos.nxt.LightSensor;
import lejos.nxt.Motor;
import lejos.nxt.SensorPort;
public class SigueLineasZ {

/**
* @param args
*/
public static void main(String[] args) throws Exception
{
LightSensor sluz = new LightSensor(SensorPort.S1);
int max, min, med;

min = sluz.readNormalizedValue();
LCD.drawString(“Valor minimo:”, 2, 3);
LCD.drawInt(min, 4, 6, 5);
Thread.sleep(2000);

Motor.A.rotate(-90,true);
Motor.B.rotate(90);
max = sluz.readNormalizedValue();
LCD.clear();
LCD.drawString(“Valor maximo:”, 2, 3);
LCD.drawInt(max, 4, 6, 5);
Thread.sleep(2000);

Motor.A.rotate(45,true);
Motor.B.rotate(-45);
med = (max + min)/ 2;
LCD.clear();
LCD.drawString(“Valor medio:”, 2, 3);
LCD.drawInt(med, 4, 6, 5);
Thread.sleep(2000);

Motor.A.setSpeed(300);
Motor.B.setSpeed(300);
LCD.clear();
LCD.drawString(“Pulse ESCAPE”, 2, 3);
LCD.drawString(“para parar”, 3, 5);
while (true) {
if (sluz.readNormalizedValue()>med){
Motor.A.forward();
Motor.B.stop();
}
else{
Motor.B.forward();
Motor.A.stop();
}
if (Button.ESCAPE.isPressed()){
break;
}
}
// TODO Auto-generated method stub

}

}

Y aquí os pongo un video demostrativo de como funcionaría un NXT con este programa:


Espero que os haya gustado. Ahora vamos a probar algo más complejo.

Sigue líneas proporcional:

Una manera más avanzada de resolver el problema del sigue líneas es mediante el método proporcional. En este método lo que haremos es aplicarle a los motores una potencia proporcional al error que tengamos (diferencia entre el valor medio hallado en la calibración y el valor actual del sensor de luz). De esta manera nos quedará un movimiento mucho más suave y preciso. Por supuesto la parte complicada es calcular la relación entre el error y la potencia que tenemos que dar a cada motor.

La parte del calibrado es la misma que la del sigue líneas anterior, por lo que no es necesario repetirla. La parte de sigue líneas será de la forma siguiente: una vez calibrado, el robot se pone en marcha; dentro de un búcle se calcula el error (valor medio – lectura actual) y se guarda en una variable; se modifica ese error para que pueda ser coherente a la hora de restárselo y sumarselo a la velocidad de los motores*, y por último se resta ese valor a la velocidad de un motor (en este caso el izquierdo (Motor.A)) y se le suma al otro (en este caso el derecho (Motor.B)). Finalmente se repite este bucle infinitas veces.

*Por ejemplo supongamos que estamos completamente en lo negro y el error es de 6, no tiene sentido que si el motor B va a 300 grados por segundo le restemos solo 6, porque no va a modificar casi su trayectoria. Sin embargo si multiplicamos 6 por 30 tendremos 180, que ya si es una reducción bastante notable de velocidad en ese motor. Por esto hay que tener en cuenta de que magnitud van a ser las unidades de este error. En este caso las lecturas de readNormalizedValue() son de entre 580 lo más blanco, y unos 250 lo más negro, por lo que la media serán de aproximadamente 400. Los errores serán como mucho del orden de 200 (Ej.: 400 – 580 = -180), por lo que podemos restarlos directamente a las velocidades de los motores ya que usaremos entre 300 y 400 grados por segundo de velocidad (Ej.: 300 – (-180) = 480, este motor acelerará claramente para tratar de corregir el error y situarse en el borde de la línea, mientras que el otro motor frenará: 300 + (-180) = 120).

El programa completo con la parte de calibrado incluida quedaría de la siguiente manera:

import lejos.nxt.Button;
import lejos.nxt.LCD;
import lejos.nxt.LightSensor;
import lejos.nxt.Motor;
import lejos.nxt.SensorPort;
public class SigueLineasP {

/**
* @param args
*/
public static void main(String[] args) throws Exception
{
LightSensor sluz = new LightSensor(SensorPort.S1);
int error, max, min, med;

min = sluz.readNormalizedValue();
LCD.drawString(“Valor minimo:”, 2, 3);
LCD.drawInt(min, 4, 6, 5);
Thread.sleep(2000);

Motor.A.rotate(-90,true);
Motor.B.rotate(90);
max = sluz.readNormalizedValue();
LCD.clear();
LCD.drawString(“Valor maximo:”, 2, 3);
LCD.drawInt(max, 4, 6, 5);
Thread.sleep(2000);

Motor.A.rotate(45,true);
Motor.B.rotate(-45);
med = (max + min)/ 2;
LCD.clear();
LCD.drawString(“Valor medio:”, 2, 3);
LCD.drawInt(med, 4, 6, 5);
Thread.sleep(2000);

Motor.A.forward();
Motor.B.forward();
LCD.clear();
LCD.drawString(“Pulse ESCAPE”, 2, 3);
LCD.drawString(“para parar”, 3, 5);
while (true) {
error = med – sluz.readNormalizedValue();
Motor.A.setSpeed(300 – error);
Motor.B.setSpeed(300 + error);
if (Button.ESCAPE.isPressed()){
break;
}
}
// TODO Auto-generated method stub
}

}

Y aquí tenéis un vídeo demostración de este programa en ejecución:


Finalmente deciros que si tenéis alguna duda o pregunta podéis escribirla en el foro.

Comments are closed.