domingo, 18 de mayo de 2014

Robot panorámico XS (El programa de Arduino 2 de 2)

Ahora toca explicar la segunda parte del programa del robot panorámico. Ahora es el momento del programa para la slide dolly.

Empoezamos borrando la pantalla y en principio ejecutamos la calibración inicial de ejes. Esta subrutina esta comentada para que no se active ya que para que funcione correctamente hay que montar unos sensores extra para marcar los límites.


Acto seguido llamamos a la subrutina define_modo() ,  programa_limite_recorrido() y la subrutina define_velocidad() que cuando llege el momento ya las explicaré con mas detalle, pero que en resumidas cuentas la primera sirve para elejir si queremos hacer time-lapse, video o un modo joystick. En la segunda subrutina es la encargada de programar los limites mientras que en la tercera definimos la velocidad a la que vamos a mover los motores. También definimos la variable pp con el valor de pdisplay (modo de funcionamiento) . Mágicamente el valor de pdisplay se cambiava sólo y el hecho de definir una variable nueva en este punto sirve para memorizar el valor correctamente.


void slide_dolly(){

lcd.clear();
// calibracion_inicial_ejes();     *********BORRAR
define_modo();
int pp=pdisplay;
programa_limite_recorrido();
define_velocidad();

Acto seguido comprobamos si hemos elejido el modo de time-lapse, si es que si, ejecutaremos la subrutina define_fotos() , define_tiempo(); y acto seguido una vez tengamos ejecutadas estas subrutinas que son las encargadas de definir el número de fotos y el espacio de tiempo entre tomas. A continuación dividimos los pasos totales que hemos definido previamente por el numero de fotos a realizar menos una. Igualmente haremos lo mismo para el segundo eje.

if (pp==0){                            // if modo !=timelapse no demani ni define_fotos ni define_tiempo
define_fotos();

define_tiempo();
pasoX=pasoX/(shot-1);
pasoY=pasoY/(shot-1);
}

Borramos de nuevo la pantalla y nos preparamos para empezar mostrandonos en la pantalla el mensaje de "[ENTER] = Start", acto seguido redondeamos los pasos que se van a mover los diferentes ejes , para ello sumamos +0.4 antes de redondear estos valores, esto lo hago porque se tratan de valores enteros y si tiene que dar 33.7 el arduino entenderia que son 33 pasos enteros , aunque se aproxima más a 34, es por ello que le sumo 0.4 y con eso se convierten en 33.7+0.4=34.1 que una vez redondeado que en 34. En cambio si el valor fuera de 33.4 al sumarle 0.4 quedaria en 33.8 que al ser redondeado por arduino serian 33 igualmente.

Acto seguido provocamos una pausa poniendo la variable a=0 y hasta que no pulsemos select, este valor no cambiará a 1 y estará continuamente en el bucle esperando.


lcd.clear();
lcd.setCursor(1,0);
lcd.print("[ENTER] = Start");
lcd.setCursor(0,1);
for (int k=0 ; k<16; k++){
lcd.print(".");
delay(100);
}
// Convertim pasos amb valor decimal a numero sencer.
/*   el paso X es perillos de convertir ja que el valor pot ser superior a 32k i una int no ho aguanta
pasoX=pasoX+0.4;
pasoXX=(int) pasoX;
pasoX=pasoYY;
*/
pasoY=pasoY+0.4;
pasoYY=(int) pasoY;
pasoY=pasoYY;
xy=pasoX/pasoY;
a=0;                 // provoquem pausa
while(a==0){
if((analogRead(0)/margen)==(select/margen)){ //enter)
a=1;
}
}

Borramos de nuevo la pantalla y depeendiendo del tipo de programa que queramos ejecutar (memorizado en la variable pp) acabaremos ejecutando las subrutinas correspondientes rutina_video_1_eje(),  rutina_video_3_ejes(), rutina_time_lapse() o bién la rutina_joystick(). Una vez ejecutada y finalizada la subrutinas , volverá al menú inicial (el que nos deja elejir el modo de Gigapan o le de Slide_dolly), a excepción de si elejimos el modo joystick que sólo podremos salir de ella forzando un reset a nuestro Arduino.

lcd.clear();
if(pp==1){
rutina_video_1_eje();
}
if(pp==3){
rutina_video_3_ejes();
}
if(pp==0){
rutina_time_lapse();
}
if(pp==4){
rutina_joystick();
}
}

La siguiente subrutina lo único que hace es mostrarnos en la pantalla un mensaje informandonos que se están buscando los limites , empezando primero por el eje X . Para ello va dando impulos en sentido negativo al motor paso a paso hasta que detecta el sensor de origen (limitX) . En el momento que detecte este sensor, el motor avanza 10 pulsos positivos con lo que en principio se espera que el sensor de limite deja de detectar. Una vez tiene el eje X en origen, realizará la misma operación pero en el eje Y siguiendo la misma lógica pero está vez se moverá el motor Y y el límite de posicionamiento vendrá dado por le sensor limitY.

void calibracion_inicial_ejes(){
lcd.setCursor(0,0);
lcd.print("Buscando limites");
lcd.setCursor(0,1);
lcd.print("Eje X");
int a=0;
while(a==0){
if(digitalRead(limitX)==HIGH){  //
a=1;
}
myStepperX.step(-1);
}
delay(300);
myStepperX.step(10);
a=0;
lcd.setCursor(0,1);
lcd.print("Eje Y");
while(a==0){
if(digitalRead(limitY)==HIGH){      //
a=1;
}
myStepperY.step(-1);
}
delay(300);
myStepperY.step(10);
a=0;
}

Ahora es el momento de definir el modo de funcionamiento de nuestra slide dolly mediante la subrutina define_modo(). Empezaremos borrando la pantalla e informando de que estamos en el modo de Slide dolly para confirmarlo. Tras unos segundos nos dejará elejir si queremos hacerla funcionar en modo automático o en modo manual (llamado modo Joystick). 
Mediante el uso de los pulsadores UP & DOWN nos desplazaremos entre estas opciones hasta que no pulsemos Select para validar.La variable pdisplay será la encargada de memorizar la opción elejida en esta parte del programa, pero luego se convierte en variable pp porque se "perdia" el valor mágicamente.


void define_modo(){
lcd.clear();
lcd.print("__Slide__dolly__");
delay(3000);
lcd.clear();
lcd.setCursor(1,0);
lcd.print(">1. Automatico");
lcd.setCursor(2,1);
lcd.print("2. Joystick");


while((analogRead(0)/margen)!=(select/margen)){  // select original era :  while(digitalRead(PinSelect)!=HIGH)
delay(100);
if((analogRead(0)/margen)==(abajo/margen)){   //abajo
pdisplay0=4;
lcd.setCursor(1,1);
lcd.print(">");
lcd.setCursor(1,0);
lcd.print(" ");
}
if((analogRead(0)/margen)==(arriba/margen)){  //arriba
pdisplay=0;
lcd.setCursor(1,1);
lcd.print(" ");
lcd.setCursor(1,0);
lcd.print(">");
}
}

Si hemos elejido el modo Joystick como que no hay nada más que programar, se ejecutará directamente su subrutina especifica, la rutina_joystick() de lo contrarrio nos dejará elejir entre realizar un time-lapse o un video, y también lo elejiremos mediante las teclas UP y DOWN , y de nuevo hasta que no pulsemos la tecla Select podremos dudar que es lo que queremos hacer. Se observa un delay(1000) justo después de aparecer el menú en la pantalla, esto lo hago para que no se validen automaticamente todos los menús por error evitando que se seleccionen las opciones sin querer por tener aun pulsados la tecla Select desde nuestra anterior elección del menú previo. 

if(pdisplay0>=4){
rutina_joystick();
}
//if (pdisplay0==0){
lcd.clear();
lcd.setCursor(1,0);
lcd.print(">1. Time-lapse");
lcd.setCursor(2,1);
lcd.print("2. Continuo");


delay(1000);
while((analogRead(0)/margen)!=(select/margen)){  // select original era :  while(digitalRead(PinSelect)!=HIGH)
if((analogRead(0)/margen)==(abajo/margen)){   //abajo
pdisplay=1;

lcd.setCursor(1,1);
lcd.print(">");
lcd.setCursor(1,0);
lcd.print(" ");
}
if((analogRead(0)/margen)==(arriba/margen)){  //arriba
pdisplay=0;

lcd.setCursor(1,1);
lcd.print(" ");
lcd.setCursor(1,0);
lcd.print(">");
}
}
Serial.println(pdisplay);

a=0;

Si hemos elejido el moto video, nos aparecerá otro menú que servirá para que elijamos si queremos realizar un video con un sólo eje (motor principal de la dolly) o bién deseamos utilizar los 2 ejes. Esta parte del programa es simplemente igual que las dos anteriores comentadas en concepto de programación. 


if(pdisplay==1){          //elejimos 1 o 2 ejes en modo video
lcd.clear();
lcd.clear();
lcd.setCursor(0,0);
lcd.print(">1 = Eje Horizon");
lcd.setCursor(1,1);
lcd.print("2 = Los 2 ejes");
delay(500);
while((analogRead(0)/margen)!=(select/margen)){  // select
delay(100);
if((analogRead(0)/margen)==(abajo/margen)){   //abajo
pdisplay=3;
lcd.setCursor(0,1);
lcd.print(">");
lcd.setCursor(0,0);
lcd.print(" ");
}
if((analogRead(0)/margen)==(arriba/margen)){  //arriba
pdisplay=1;
lcd.setCursor(0,1);
lcd.print(" ");
lcd.setCursor(0,0);
lcd.print(">");
}
}
}
}

A continuació la secuencia de programación de limites de recorrido: Empezamos con una pausa y acto seguido nos muestra en pantalla un mensaje informandonos que elijamos el punto de inicio del eje X. Acto seguido mediante las teclas LEFT y RIGHT si las pulsamos avanzaremos o retrocederemos el equivalente a 10 pasos de nuestro motor paso a paso. 
En caso de que activaramos el sensor de limite, nos aparecerá un mensaje informandonos que estamos en el límite. Podremos mover el carro hasta dónde queramos hasta que no pulsemos la tecla Select, para ello en el inicio de esta subrutina se define el valor de a=0 y para provocar el bucle lo logramos mediante la instrucción while(a==0).
Otra cosa que no he comentado, es que cuando pulsamos cualquier botón lo que hacemos es leer el valor de la entrada análogica0 y la dividimos del valor margen, este sirve para dar una tolerancia de error, cuanto más grande sea el valor margen , mayor será la tolerancia de nuestro pulsador pero no es conveniente que sea muy grande , no sea que se solapen dos valores muy parecidos y luego nuestro Arduino haga lo primero que le parezca...

void programa_limite_recorrido(){
//a=0;
delay(1000);
lcd.clear();
lcd.setCursor(1,0);
lcd.print("Elije punto de");
lcd.setCursor(0,1);
lcd.print("inicio del eje X");          //buscar punto INICIO de la dolly eje X


delay(200);
while(a==0){
if((analogRead(0)/margen)==(select/margen)){ //enter)
a=1;
}
delay(15);
if((analogRead(0)/margen)==(right/margen)) {   //  derecha +X
myStepperX.step(10);  
}
if((analogRead(0)/margen)==(left/margen)) {   //  izquierda -X
for (int i = 0; i<10; i++){
if(digitalRead(limitX)==HIGH){
lcd.setCursor(0,1);
lcd.print("ATENCION LIMITE!");
}else{
myStepperX.step(-1);
}
}
}
}

Una vez hallado el punto de inicio del eje X haremos lo mismo pero para el punto final. La lógica usada será la misma que la utilizada para buscar el punto de inicio pero con una pequeña diferencia: ahora memorizaremos en lal variable pasoX  los pasos que realice el motor cuando nosotros lo estamos posicionando. Este valor nos lo irá mostrando continuadamente en la pantalla.


a=0;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Busca punt final");
lcd.setCursor(3,1);
lcd.print("del eje X:");          //buscar el punto FINAL de la dolly eje X


while(a==0){
if(((analogRead(0)/margen)==(select/margen))&(pasoX!=0)){        
a=1;
}
if((analogRead(0)/margen)==(right/margen)) {   //  derecha +X
if (pasoX<20000){
pasoX=pasoX+10;
myStepperX.step(10);
lcd.setCursor(3,1);
lcd.print("   ");
lcd.setCursor(6,1);
lcd.print(pasoX);
lcd.print("     ");
}
}
if((analogRead(0)/margen)==(left/margen)) {   //  izquierda -X
if(digitalRead(limitX)==HIGH){
lcd.setCursor(0,1);
lcd.print("ATENCION LIMITE!");
}else{
pasoX=pasoX-10;
myStepperX.step(-10);
}
lcd.setCursor(3,1);
lcd.print("   ");
lcd.setCursor(6,1);
lcd.print(pasoX);
lcd.print("     ");
}
}

A continuación realizaremos lo mismo pero para el eje Y. Empezaremos buscando el punto inicial en pasos de 50 pulsos, este valor lo podeis modificar a vuestro gusto. Y una vez obtenido este, buscaremos elpunto final del eje Y memorizando la posición  mediante la variable PasoY.


if (pdisplay==1){
a=1;
}else{
a=0;
delay(1000);
lcd.clear();
lcd.setCursor(1,0);
lcd.print("Elije punto de");
lcd.setCursor(0,1);
lcd.print("inicio del eje Y");          //buscar punto INICIO de la dolly eje Y
delay(200);
}
while(a==0){
if((analogRead(0)/margen)==(select/margen)){ //enter)
a=1;
}
delay(15);
if((analogRead(0)/margen)==(right/margen)) {   //  derecha +Y
myStepperY.step(5);
}
if((analogRead(0)/margen)==(left/margen)) {   //  izquierda -Y
myStepperY.step(-5);
}
}
if (pdisplay==1){
a=1;
}else{
a=0;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Busca punt final");
lcd.setCursor(3,1);
lcd.print("del eje Y:");          //buscar el punto FINAL de la dolly eje Y


delay(500);
}
while(a==0){

Originalmente con la siguiente instrucción forzaba a que el punto final fuera diferente del inicial, simplemente porque cuando luego habia que buscar la proporción de pasosX respecto Y, si este último valor era 0 la división era infinito provocando un error con repercusiones desconocidas. Si quereis modificar esta linea solo teneis que incluir el &(pasoY!=0) y organizar corchetes.

if(((analogRead(0)/margen)==(select/margen))){   //original &(pasoY!=0)

a=1;
}
delay(15);
if((analogRead(0)/margen)==(right/margen)) {   //  derecha +X
if (pasoY<3200){
pasoY=pasoY+5;
myStepperY.step(5);
lcd.setCursor(3,1);
lcd.print("   ");
lcd.setCursor(6,1);
lcd.print(pasoY);
lcd.print("     ");
}
}
if((analogRead(0)/margen)==(left/margen)) {   //  izquierda -X
pasoY=pasoY-5;
myStepperY.step(-5);
lcd.setCursor(3,1);
lcd.print("   ");
lcd.setCursor(6,1);
lcd.print(pasoY);
lcd.print("     ");
}
}

A continuación se colocan los motores en su posición de inicio , es tan fácil como forzar el movimiento de cada uno de los dos motores para que actuen los pasos programados anteriormente pero en sentido inverso. Primero empezamos con el motor X y luego con el Y.

// desplazamos hasta el origen eje X
lcd.clear();
lcd.setCursor(1,0);
lcd.print("Mueve a origen");
lcd.setCursor(5,1);
lcd.print("Eje X");


myStepperX.step(-pasoX);

Una manera muy tonta de tener claro el sentido de trabajo de cada motor es cuestionandonos si el valor de pasos es positivos o negativos. Ahora cuando aun no se ha movido ningun motor es fácil, el problema vendrá luego mas tarde, una vez hechas las proporciones de pasoX/pasoY si ambas son negativas se convierte el resultado positivo todo y aqui es cuando hay que tener una memoria para que en todo momento quede claro el sentido si no queremos enviar la dolly a casa del vecino.

if (pasoX>0){
sentidoX=0;
}else{
sentidoX=1;
}

lcd.setCursor(5,1);
lcd.print("Eje Y");
myStepperY.step(-pasoY);
if (pasoY>0){
sentidoY=0;
}else{
sentidoY=1;
}
}

En la siguiente subrutina define_fotos() tal y como dice su nombre sirve para que programemos el número de fotos a realizar, en principio ya viene pre-definida como 25 fotos, pero si vais a hacer un time-lapse de varios segundos necesitareis unas cuantas mas. Tened en cuenta que son necesarias 25 imagenes por segundo como mínimo para que nuestro video de time-lapse tenga un refresco de imagen aceptable . En principio está capado a una capacidad de 250 capturas pero si buscais en las siguientes lineas, vereis que es muy fácil modificarlo/eliminar esta opción.

Utilizo la misma lógica de siempre: se declara la variable a=0 y luego hay un while que viene a decir "mientras a sea 0 ves haciendo el bucle" . Para salir del bucle hay que pulsar Select y automaticamente cambiará el valor de a=1 y acto seguido saldremos del bucle.

Dentro del bucle si pulsamos UP incrementamos una foto y si pulsamos DOWN decrementamos una foto.

También hay unas lineas dónde miramos el valor de fotos (variable shot) que si es superior a un valor quita o pone espacios en blanco en nuestro lcd antes de mostrar el valor, esto lo hago para que quede todo mas pulido, esta es una de las partes más pesadas, hacerlo que quede bonito en nuestra pantalla dentro de las limitadas posibilidades que nos ofrece.


void define_fotos(){
a=0;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Numero de fotos");
lcd.setCursor(4,1);
lcd.print(shot,1);
lcd.setCursor(6,1);
lcd.print(" fotos");
delay(200);
while(a==0){
if((analogRead(0)/margen)==(select/margen)){ //enter)
a=1;
}
delay(150);
if((analogRead(A0)/margen)==(arriba/margen)) {   //  arriba
if (shot<250){
shot=shot+1;
lcd.setCursor(3,1);
if (shot<100){
lcd.print(" ");
}
if (shot<10){
lcd.print(" ");
}
lcd.print(shot);
lcd.setCursor(6,1);
lcd.print(" fotos");
}
}
if((analogRead(A0)/margen)==(abajo/margen)) {   //  abajo
if (shot>1){
shot=shot-1;
lcd.setCursor(3,1);
if (shot<100){
lcd.print(" ");
}
if (shot<10){
lcd.print(" ");
}
lcd.print(shot);
lcd.setCursor(6,1);
lcd.print(" fotos");
}
}
}
}

En la siguiente subrutina define_tiempo() sirve para determinar el tiempo entre fotos de nuestro time-lapse, sigue la misma lógica de la subrutina de define_fotos, pero esta vez la variable a memorizar es segundos_secuencia.


void define_tiempo(){
a=0;
lcd.clear();
lcd.setCursor(1,0);
lcd.print("Define  tiempo");
lcd.setCursor(2,1);
lcd.print("entre  fotos");

delay(1000);
while(a==0){
if((analogRead(0)/margen)==(select/margen)){ //enter)      
a=1;
}
delay(150);
if((analogRead(0)/margen)==(arriba/margen)){  //arriba
if (segundos_secuencia<250){
segundos_secuencia=segundos_secuencia+1;
lcd.setCursor(0,1);
lcd.print("    ");
if(segundos_secuencia<100){
lcd.print(" ");
}
if(segundos_secuencia<10){
lcd.print(" ");
}
lcd.print(segundos_secuencia);
lcd.setCursor(7,1);
lcd.print(" seg.  ");
}
}
if((analogRead(0)/margen)==(abajo/margen)){   //abajo
if (segundos_secuencia>1){
segundos_secuencia=segundos_secuencia-1;
lcd.setCursor(0,1);
lcd.print("    ");
if(segundos_secuencia<100){
lcd.print(" ");
}
if(segundos_secuencia<10){
lcd.print(" ");
}
lcd.print(segundos_secuencia);
lcd.setCursor(7,1);
lcd.print(" seg.  ");
}
}
}
}

En la siguiente subrutina sirve para definir la velocidad de los ejes. Es una función que he probado poco, lo siento, no tengo tanto tiempo como me gustaria! Basicamente sigue la misma lógica que las subrutinas anteriores en cuanto a control de pulsadores y la variable a tratar es velocidadX, mediante el programa múltiplicamos este valor por 2.5, de esta manera si queremos llegar al 100% de velocidad , el valor real será 40.

void define_velocidad(){
a=0;
velocidadX=10;
lcd.clear();
lcd.setCursor(1,0);
lcd.print("Velocidad Ejes");
lcd.setCursor(5,1);
lcd.print(velocidadX*2.5);
lcd.print(" %  ");
delay(500);
while(a==0){
if((analogRead(0)/margen)==(select/margen)){ //enter)
a=1;
}
delay(150);
if((analogRead(0)/margen)==(arriba/margen)){  //arriba
if (velocidadX<40){
velocidadX=velocidadX+1;
}
lcd.setCursor(5,1);
lcd.print(velocidadX*2.5);
lcd.print(" %  ");
}  
if((analogRead(0)/margen)==(abajo/margen)){   //abajo
if(velocidadX>1){
velocidadX=velocidadX-1;
}
lcd.setCursor(5,1);
lcd.print(velocidadX*2.5);
lcd.print(" %  ");
}
}


Una vez confirmada nuestra selección igualamos el valor en las velocidades de ambos ejes y la aplicamos mediante las funciones myStepper?.setSpeed(velocidad)

velocidadY=velocidadX;
myStepperX.setSpeed(velocidadX);      // definimos velocidad impulsos motor
myStepperY.setSpeed(velocidadY);      // definimos velocidad impulsos motor

}

A por la rutina de video de 1 eje ! Empezamos haciendo una limpieza de pantalla y nos aparecerá en pantalla un mensaje informandonos que vamos a grabar, y un segundo y medio después se activará la señal de remoto de nuestra cámara. Acto seguido en la pantalla nos mostrará el % de velocidad a que se va a realizar . Si durante la grabación pulsamos UP o DOWN modificaremos esta velocidad.

void rutina_video_1_eje(){        //rutina video
lcd.clear();
lcd.setCursor(0,0);
lcd.print("A T E N C I O N");
lcd.setCursor(0,1);
lcd.print("Grabando video");
delay(1500);
digitalWrite(PinShot, HIGH);
delay(1000);
if(pasoX<0){
pasX=-pasoX;
}else{
pasX=pasoX;
}

En vez de ejecutar con una sola instrucción el movimiento realizando todos los pasos de golpe, realizamos el movimiento paso a paso y verificamos continuamente que no excedamos de la posición de los micros de seguridad. El programa esta preparado, pero no lo tengo implementado y lógicamente no está probado!

for (int i = 0; i<pasX; i++) {
if(digitalRead(limitX)==HIGH){
lcd.clear();
lcd.setCursor(0,1);
lcd.print("ATENCION LIMITE");
delay(10000);
i=pasX;
break;
}else{

Aqui es dónde podemos modificar el valor de velocidad. El hecho de hacer mover el motor paso a paso también sirve para que podamos realizar pequeñas actuaciones como esta , si definieramos todo el movimiento de golpe no podriamos cambiar la velocidad a media trayectoria. Comentar que si entre las instrucciones de movimiento de un paso de motor hasta que se repite si hay muchas instrucciones que aunque sean "tontas" nos frena el programa y podemos provocar que vaya a golpes. Sobretodo instrucciones de visualización en nuestra LCD nos puede provocar este problema. El pobre Arduino le falta velocidad de procesamiento y de tratamiento de datos.

if((analogRead(0)/margen)==(arriba/margen)){  //arriba
if (velocidadX>2 && velocidadX<40){
velocidadX=velocidadX+1;

myStepperX.setSpeed(velocidadX);      // definimos velocidad impulsos motor
}
}
if((analogRead(0)/margen)==(abajo/margen)){   //abajo
if (velocidadX>2 && velocidadX<40){
velocidadX=velocidadX-1;

myStepperX.setSpeed(velocidadX);      // definimos velocidad impulsos motor
}
}
if (sentidoX==1){
myStepperX.step(-1);
}else{
myStepperX.step(1);
}
}
}
delay(200);
digitalWrite(PinShot, LOW);

Una vez recorrido el espacio, en nuestra LCD nos confirmará el fín de la grabación y el carro retornará a la posición de inicio.

lcd.clear();
lcd.setCursor(0,0);
lcd.print("Fin del video");
delay(2000);
lcd.setCursor(0,1);
lcd.print("Retorno a inicio");
myStepperX.setSpeed(10);      // definimos velocidad impulsos motor
myStepperX.step(-pasoX);

lcd.clear();
lcd.setCursor(5,2);
lcd.print("F I N  !");
delay(5000);
}

Vamos a por el video de 2 ejes, este todo y ser casi igual al de 1 eje da mucha guerra... Arduino SÓLO puede ejecutar una acción a la vez y por lo tanto mientras mueve un motor el otro está quieto, es otra de las limitaciones que tiene y que ya he repetido varias veces. Otra cosa es que lo intentemos engañar con pequeños movimientos entre ambos motores y de sensación de que todo va sincronizado...pero ja! Es más , nosotros estamos utilizando motores paso a paso y confiamos en que no se pierda ningún paso por el camino, lo suyo seria tener un encoder o aunque fuera muy guarro un potenciometro variable para tener medio controlada la posición de nuestro invento...

Previamente a este punto hay una línea dónde dice algo asi: xy=pasoX/pasoY que sirve para buscar una proporción de los pasos que va a dar el eje X respecto el Y. Como que el resultado de xy es un valor entero quiere decir que si multiplico xy*pasoY es posible que no de pasoX, y esto es lo que hacemos en la formula dónde definimos el resto. Este valor serán los pasos "perdidos" del eje X.

Para que me entendais mejor, imaginamos que pasoX=1000, pasoY= 220, cuando hacemos xy=pasoX/pasoY , sería 1000/220 = 4.54, como que xy está definido como una int , es un número entero y va a memorizar con valor 4.

Entonces se moveria 4 pasos durante 220 veces el eje X y a cada paso 4 pasos de X se moveria uno de Y, resultado final = nos faltarian 1000-880 pasos = 120 pasos perdidos.

¿ Como lo arreglo? calculo el resto = 120 pasos y lo divido de 2. 

Arrancamos el eje X durante la mitad de pasos del resto (60 pasos) y acto seguido hago la secuendica de 220 veces 4 pasos de X por 1 paso de Y (total 880 pasos de X y 220 de Y). Llegados a este punto llevamos 940 pasos de X y 220 de Y . Ya sólo falta mover el eje X durante la mitad del resto (60 pasos)  para completar todo el recorrido de X y de Y. Si estos "escalones" no os gustan siempre podeis eliminarlos o editar el video...

¿Vaya toston no? Pues es lo que hay! Si a alguién se le ocurre una manera mejor que me la explique!

void rutina_video_3_ejes(){        //rutina video 2 ejes

resto=pasoX-(xy*pasoY);

lcd.setCursor(0,0);
lcd.print("A T E N C I O N");
lcd.setCursor(0,1);
lcd.print("Grabando video");
delay(1500);
digitalWrite(PinShot, HIGH);
delay(1000);


myStepperX.step(resto/2);

if(pasoX<0){
pasX=-pasoX;
}else{
pasX=pasoX;
}
if(pasoY<0){
pasY=-pasoY;
}else{
pasY=pasoY;
}
if(xy<0){       //linies noves
  xy=xy*(-1);
}
// *********************************
   
for (int m = 0; m<pasY; m++) {
for (long int n = 0; n<(xy); n++) {
if(digitalRead(limitX)==HIGH){
lcd.clear();
lcd.setCursor(0,1);
lcd.print("ATENCION LIMITE!");
delay(10000);
n=pasoX;
break;
}else{
if((analogRead(0)/margen)==(arriba/margen)){  //arriba
if (velocidadX>2 && velocidadX<40){
velocidadX=velocidadX-1;
myStepperX.setSpeed(velocidadX);      // definimos velocidad impulsos motor
myStepperY.setSpeed(velocidadX);      // definimos velocidad impulsos motor
}
}
if((analogRead(0)/margen)==(abajo/margen)){   //abajo
if (velocidadX>2 && velocidadX<40){
velocidadX=velocidadX+1;
myStepperX.setSpeed(velocidadX);      // definimos velocidad impulsos motor
myStepperY.setSpeed(velocidadX);      // definimos velocidad impulsos motor
}
}
if (sentidoX==1){
myStepperX.step(-1);
}else{
myStepperX.step(1);
}
}
}
if (sentidoY==1){
myStepperY.step(-1);
}else{
myStepperY.step(1);
}
}

// ***********************************************

myStepperX.step(resto/2);

delay(200);
digitalWrite(PinShot, LOW);
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Fin del video");
delay(2000);

Luego después de que se nos muestre el mensaje de "Fín de video" volvemos a inicio y acabamos con nuestra rutina. Lo siento no tengo fotos, mirad el video...




lcd.setCursor(0,1);
lcd.print("Retorno a inicio");

myStepperX.step(-pasoX);

myStepperY.step(-pasoY);

lcd.clear();
lcd.setCursor(5,1);
lcd.print("F I N  !");
delay(10000);
}

Ahora es la hora de la rutina time-lapse, xD ! creo que nunca acabaré este post! Es muy parecido en cuanto a funcionamiento al video de 2 ejes pero no se ha implementado la funcion resto ya que es "tonteria" que se mueva un espacio y luego se descuadre . Dónde hay más trabajo es en la visualización de datos en nuestra LCD.

Empezamos creando una rutina que se repetirá tantas veces como numero de fotos a realizar. Actualizamos valores en pantalla y realizamos una foto mientras el gif cierra el diafragma. 


void rutina_time_lapse(){       //rutina time-lapse
for(int i = 1; i< shot; i++){  
//logo0();
logo2();
lcd.setCursor(0,0);
lcd.print("s:");
lcd.print(i);
lcd.setCursor(7,1);
lcd.print(i*100/shot);
lcd.print("%");
lcd.setCursor(1,1);
lcd.print("F O T O  ! ");
digitalWrite(PinShot, HIGH);
logo2();
delay(300);
digitalWrite(PinShot, LOW);
delay(300);

logo3();
lcd.setCursor(1,1);
lcd.print("M O V E  ! ");
if(pasoX<0){
  pasX=-pasoX;
}else{
  pasX=pasoX;
}
if(pasoY<0){
  pasY=-pasoY;
}else{
  pasY=pasoY;
}

Acto seguido y tras un pequeño retardo de 300 mS se mueve la dolly paso a paso en el eje X verificando que no exista contacto con el sensor de limite del ejeX, luego moverá el ejeY.

for (int i = 0; i<pasX; i++) {
if(digitalRead(limitX)==HIGH){
lcd.setCursor(0,1);
lcd.clear();
lcd.print("ATENCION LIMITE!");
delay(10000);
i=pasX;
break;
}else{
if (sentidoX==1){
myStepperX.step(-1);
}else{
  myStepperX.step(1);
}
}
}
for (int i = 0; i<pasY; i++) {
if (sentidoY==1){
myStepperY.step(-1);
}else{
  myStepperY.step(1);
}
}
lcd.setCursor(0,1);
lcd.print("           ");
lcd.setCursor(7,1);
lcd.print(i*100/shot);
lcd.print("%");

posicion_actual_X=(posicion_actual_X+pasoX);
posicion_actual_Y=(posicion_actual_Y+pasoY);

Mientras está efectuando el retardo entre fotos podemos incrementar este retardo mediante las teclas UP y DOWN, si pulsais UP al incrementar el valor os va a parecer que no haceis nada, pero a cada decima de segundo le estais incrementando una más , y os dareis cuenta en la siguiente "pausa". Si por el contrario pulsais DOWN decrementais este valor

Si por el contrario pulsais SELECT provocaremos una pausa.

for (int j=0; j<(segundos_secuencia*10); j++){  
if((analogRead(0)/margen)==(arriba/margen)){  //arriba
segundos_secuencia=segundos_secuencia+0.1;
}
if((analogRead(0)/margen)==(abajo/margen)){   //abajo
segundos_secuencia=segundos_secuencia-0.1;
}
delay (100);
lcd.setCursor(0,1);
tiempo_reloj=j*0.1;
if ((segundos_secuencia-tiempo_reloj)<10){
lcd.print(" ");
}
lcd.print(segundos_secuencia-tiempo_reloj);
lcd.setCursor(4,1);
lcd.print(char(34));
lcd.print(" ");
lcd.setCursor(6,0);
lcd.print("X:");
lcd.print(posicion_actual_X,0);
tempi=i;
peticiopausadolly();
/*
lcd.setCursor(6,2);
lcd.print("Y:");
lcd.print(posicion_actual_Y,0);
*/
}
}

Una vez realizadas todos los movimientos nos faltará la última foto para completar, y a continuación ya nos aparecerá el mensaje de MotionLapse completo! Momento en que la dolly retrocederá a su punto de origen.

logo2();
lcd.setCursor(0,1);
lcd.print("     ");
digitalWrite(PinShot, HIGH);
logo3();
delay(500);
digitalWrite(PinShot, LOW);
delay(100);
lcd.clear();
logo2();
lcd.setCursor(0,0);
lcd.print("MotionLapse");
lcd.setCursor(0,1);
lcd.print(" completo!");

myStepperX.step(-posicion_actual_X);
myStepperY.step(-posicion_actual_Y);
delay(5000);
}

La subrutina peticiopausadolly() preguntará diez veces si está el pulsador de SELECT apretado, si sólo pusieramos una, se lo pasaria por el forro.. aqui si que corre el Arduino! Si detecta que esta pulsado entraremos en modo pausa y no saldremos hasta que pulsemos la tecla DOWN, momento en el cual actualizaremos datos de la pantalla y continuaremos (saldremos de la subrutina)

void peticiopausadolly(){
for (int k=0; k<10; k++){ //prova pausa
if((analogRead(0)/margen)==(select/margen)){     // tecla SELECT si apretem provoquem una pausa fins que no apretem ENTER
lcd.clear();
lcd.setCursor(2,0);
lcd.print("P a u s a  !  ");
lcd.setCursor(0,1);
lcd.print("[DOWN] = reStart");
delay(1000);  //abans no hi era i em rearrancaba
pause=2;
while(pause>1){
if((analogRead(0)/margen)==(abajo/margen)){  //abajo
pause=0;
lcd.clear();
logo2();
lcd.setCursor(0,0);
lcd.print("s:");
lcd.print(tempi);
lcd.setCursor(7,1);
lcd.print(tempi*100/shot);
lcd.print("%");
}
}
}
}
}

La subrutina joystick nos servirá para mover manualmente nuestra dolly/robot mediante los pulsadores UP, DOWN, LEFT y RIGHT , y realizar capturas cada vez que pulsemos el botón SELECT.
Para salir de esta subrutina hay que forzar un reset a nuestro Arduino.
Creo que si habeis entendido el resto de programa, aqui ya no hace falta ninguna explicación mas, simplemente es mas de lo mismo.


void rutina_joystick(){
a=0;
while(a==0){
myStepperX.setSpeed(3);      // definimos velocidad impulsos motor
myStepperY.setSpeed(3);      // definimos velocidad impulsos motor
lcd.clear();
lcd.setCursor(1,0);
lcd.print("MODO  JOYSTICK");
delay(500);

while(pdisplay!=99){
if((analogRead(0)/margen)==(right/margen)){   //derecha +X
myStepperX.step(5);
pasoX=pasoX+1;
}
if((analogRead(0)/margen)==(left/margen)){   //izquierda -X
myStepperX.step(-5);
pasoX=pasoX-1;
}

if((analogRead(0)/margen)==(arriba/margen)){   //arriba +Y
myStepperY.step(5);
pasoY=pasoY+1;
}

if((analogRead(0)/margen)==(abajo/margen)){   //abajo -Y
myStepperY.step(-5);
pasoY=pasoY-1;
}

if((analogRead(0)/margen)==(select/margen)){ //enter)
digitalWrite(PinShot, HIGH);
lcd.setCursor(0,1);
lcd.print("   F O T O  !");


delay(500);
digitalWrite(PinShot, LOW);
lcd.setCursor(0,1);
lcd.print("             ");
delay(500);
}
lcd.setCursor(1,1);
lcd.print("X: ");
lcd.print(pasoX);
lcd.print("  ");
lcd.setCursor(9,1);
lcd.print("Y: ");
lcd.print(pasoY);
lcd.print("  ");
}
}
}

Finalmente implementé la subrutina reset para poner todos los registros de valores a 0 para que una vez realizadas cualquier secuencia y fueramos a hacer otra no aparecieran movimientos ilógicos. Simplemente hay una re-definición de los valores de todas las variables.

void reset(){
progreso=0;
pasoX=0;
pasoY=0;
xact=0;               // º actual eje X
yact=0;               // º actual eje Y
xmax=0;               // º posición máxima eje X
xmin=0;               // º posición mínima eje X
ymax=0;               // º posición máxima eje Y
ymin=0;               // º posición mínima eje Y
xtemp=0;
ytemp=0;
nshot=0;
columnasX=0;                 // número de filas eje X >> (xmax-xmin)/pasoX
columnasXX=0;
filasY=0;              // número de columnas eje Y    >> (ymax-ymin)/pasoY
filasYY=0;
previsionShot=0;          // prevision número shots   columnasX * filasY
segundos=0 ;
minutos=0 ;
horas=0 ;
pause=0;
pdisplay=0;
pdisplay0=0;
valorTempX=0;
valorTempY=0;
progreso=1;
solape=0.20;
retardo=0;
origen=0;
tempj=0;
tempi=0;
a=0;
sentidoX=0;
sentidoY=0;
shot=25;
pasoXX=0;              // valor temporal dolly eje X
pasoYY=0;              // valor temporal dolly eje Y
pasX=0;
pasY=0;
xy=0;
resto=0;
segundos_secuencia=3;
posicion_actual_X=0;
posicion_actual_Y=0;
velocidadX=0;
velocidadY=0;
tiempo_reloj=0;
espai=0;
paso=0;
menu=2;
menu0=0;
modenight=2;
factor=1.5;     // en su defecto : FF = 1, Nikon DX=1.5 , Canon DX=1.6 , Olympus= 2
focal = 200;

}

Y se acabó por hoy ! Reconozco que hay varios puntos a mejorar, se aceptan ayudas jiji! Y no sé cuantos post tengo pendientes de publicar...pero es que me gusta compartirlos pero faltan horas!

Si quereis todo el programa completo, no dudeis en escribirme.

Saludos!

No hay comentarios:

Publicar un comentario