domingo, 18 de mayo de 2014

Aprovechando la primavera

Llega la primavera, mi epoca preferida a nivel de fotografia, los campos verdes , los cielos azules, una buena temperatura y muchas dias con una buena visibilidad, ya no existe la niebla del invierno ni esa "calicha" que se ocasiona en verano. Aparte es una epoca en que de vez en cuando cae un chaparrón y sirve para limpiarlo todo, inclusive el ambiente. 

En fín que ha sido una temporada para realizar Gigapanes o Gigafotos, en este post os intentaré hacer un resumen de mis experiencias:

Empiezo por una Gigafoto realizada en mi pueblo natal, Súria , en la cual se puede ver el rio Cardener. Este dia fué un poco desastre con el tema baterias, el problema empezó cuando un domingo por la mañana y viendo la climatologia exterior uno decide ir a hacer fotos...

Lo preparo todo: Nikon d5200, Nikkor 18-200, robot , miro las baterias y en principio tienen carga, lo cojo todo y me desplazo hasta el lugar elejido.

Hace un poco de viento, alineo el conjunto y empiezan las tomas...tengo el tiempo justo debido a otros compromisos, todo parece que va bién hasta que se acaba la bateria de la cámara cuando tenia realizada algo más de la mitad de las tomas. Tengo dos opciones, cancelar o probar de cambiar la bateria. Decido esta segunda opción pero para ello debo aflojar completamente la camara y tengo un riesgo excesivo de que luego no se solapen las imagenes. Y asi lo hice: cambio de bateria y reanudamos el ciclo desde el principio de la linea dónde estaba. Es muy importante antes de empezar con las tomas apuntarse el numero de filas, columnas, diafragma y otros valores empleados, asi si hay que seguir el ciclo después de un problema evitaremos tener otro... El problema que tuve luego es que me equivoque e introduje una columna menos en la segunda parte del proyecto.


Ese dia también se me acabó la bateria de la camara deportiva que me acompaña mientras realizaba un time-lapse del making off. Y para rematar la bateria del telefono movil también se agotó!

En fín que fué un poco un desastre. Una vez en casa logré cuadrar las capturas de la última columna, eliminando las ultimas tomas de cada fila previas, y de este modo el software de pegado AutoPano Giga no tuviera problemas para procesar las 555 capturas que ofrecen una resolución de 9.5 GPx.

Y el resultado aqui esta:  http://gigapan.com/gigapans/151988



Dias mas tarde , decidí realizar otra gigafoto pero con el equipo grande, es decir usando el Sigma Bigmos 150-500 + el teleconversor 1.4x. Aqui ya utilizo el otro robot, más grande y robusto y optimizado para este objetivo. En esta ocasión el sitio a fotografiar fué la Vall de Lord, cerca del pueblo de Sant Llorenç de Morunys (Port del Compte - Lérida).

Dado a que con este objetivo acabo generando una imagen con muchas fotos, me cercioné de tener esta vez todas las baterias bien cargadas antes de salir de casa.

Salí de casa y me desplazé hasta el aparcamiento más cercano, a unos 700 metros del mirador desde realizaría las capturas. Ahora tocaba pasear todo el material de un viaje, es un proceso muy agotador que todo el conjunto pesa... (robot + soporte + objetivo + 2 baterias 12v/7Ah , cuatro herramientas de  supervivéncia, etc... unos 20 kilos quizás...). Una vez allí empecé a montarlo todo. Era un dia soleado con algunas nuves, y no tenia nadie "molestando". Es normal cuando me encuentro a alguién que te ve con el robot que empiece a hacer preguntas del tipo "lo vas a poner en youtube" (si es una foto , no un video!), luego los que preguntan en que canal de la TV va  a salir...nada un chou !



La cosa fué bién, no hubo ningun percance a destacar y aqui teneis el resultado de 2925 capturas y una resolución de 49 GPx.

Os dejo el video del making off...


Y una pequeña captura de pantalla de la foto resultante. Si quereis verla en grande entrad al siguiente link : http://gigapan.com/gigapans/152891



Dias mas tarde, ya en semana santa y con dias libres tenia previsto fotografiar Barcelona, el sitio ya elejido previamente, sus accesos, todo claroy todo a punto en casa. Cojo el coche y empiezo a desplazarme, pero pocos minutos después de salir de casa ya empiezan mis dudas...Barcelona tiene otros handycaps: la nieblilla generada por la brisa del mar y su contaminación que reduce la visión y como que parte de la grácia de las fotografias de detalles es ver detalles lo más nítido posible acabé cancelando el proyecto justo llegando a la capital. Estuve esperando a ver la ciudad para confirmar lo que me esperaba, por lo que media vuelta y de retorno a Manresa. Llegé a mi ciudad al mediodia, momento en que la ciudad esta tranquila de personas ya que el sector comercial permanece cerrado a diferencia de las grandes ciudades , y me planté el la Plaça Sant Domenec.

Monté el robot pequeño en el tripode Manfrotto y empecé con las capturas con la Nikon d5200 + Nikkor 18-200 @200mm. Una detrás de otra y de nuevo otra vez con las visitas pertinentes preguntando. Como que habian personas en movimiento de vez en cuando debia pausar para evitar cortes de cabezas inesperados.

En este caso fueron 850 tomas y algo cerca de 13.7 GPx de resolución final.

Podeis verla en grande en el siguiente link : http://gigapan.com/gigapans/153338


Esta dió guerra a la hora de procesarla debido a que corte un coche, difuminé caras de los niños y reparé pequeñas imperfecciones con el Photoshop. La acción de reparar el coche cortado fué clonando la cartelera publicitaria que hay en la izquierda de la pantalla , "montando" otra unos metros más hacia arriba en sentido de un coche gris. Fué "fácil" el clonarla, el problema es que con una fotografia de este tamaño se acaban generando archivos de unos 100 Gb de peso que cuestan de trabajar, aunque hace pocos dias que una persona me dijo... tampoco es tanto! ignorantes...

Tres dias más tarde, decidí subir al Collbaix, una montañita con vistas a gran parte de la comarca del Bages, aqui también me acompañaria mi robot, el trípode y la cámara deportiva AEE SD19, ya que se trataba de un proyecto atrasado por culpa de la dificultad de subir el material a esta montaña, algo mas de 30 minutos cuesta arriba con un ambiente soleado, y una vez allí unas vistas dignas de quedarse un rato a relajarse o simplemente a descansar. Es una montaña con asiduidad de visitas por lo que decidí subir al mediodia para poder montar los artilugios y evitar encontrar muchas personas en el sitio dónde debería montar el robot.


No hubo ningún percance ni en las tomas ni en el procesamiento de las 608 imagenes que darian una resolución de 11 GPx. Esta fotografia la dediqué a toda la gente que le gustaria subir para ver las vistas que por los motivos que sean no puedan hacerlo y les pique la curiosidad de que es lo que se observa desde allí arriba, para vosotros va!

Os dejo el link de la imagen en grande: http://gigapan.com/gigapans/153421


Y un pequeño video time-lapse con el making off que también deseo que os guste:


Y para terminar el post , otra gigafoto apurando los dias de semana "manta" , esta vez desde Cardona con su castillo medieval en primer plano. Tuve que colocar el trípode como pude en un punto de la montaña en que no me molestara ningún arbol.


 Fueron 468 imagenes y unos 8.5 GPx de resolución. Esta vez también usé los mismos equipos y fué acabar las últimas fotos y oscurecerse el cielo, pero aun llegé al coche sin mojarme después de los 20 minutos de paseada. Si la quereis ver en grande podeis verla en http://gigapan.com/gigapans/153499 


Y este es el primer "paquete" de fotografias que realicé durante primavera y semana Santa, en estos momentos tengo otras por procesar en espera de un nuevo iMac más potente que deseo que fluya aun más rápido que el anterior.




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!

domingo, 11 de mayo de 2014

Lo que dura dura (Batería)

Utilizo una Nikon d5200 para realizar mis inventos, es mi cámara de batalla con la que realizo las Gigafotografías panorámicas. Esta cámara va equipada con una bateria EN-EL14 que subministra unos 7.4 voltios y un puñadito de miliAmperios suficientes para realizar unas teóricas 600 capturasas, pero todo depende de si utilizamos mucho la pantalla, el estabilizador, el auto-focus, el flash... por lo que esas 600 teóricas tomas son muy relativas.

Yo he logrado realizar algo más de 800 capturas trabajando con enfoque manual , y eliminando por completo todo el tema pre-visualizaciones y evitando al máximo esos consumos citados.

A veces uno sale de casa con las baterías que están en principio a tope pero por lo que sea no lo están, o bién simplemente realizas unas tomas previas, previsualizas , juegas con los menús y luego a fotografiar en serio, resumen que ya no están al 100%.

En los dos últimos meses me he encontrado dos veces a 3/4 partes de una Gigapanorámica que me quedo sin batería y en mi último robot para cambiar la misma debo desmontar la cámara. Si debo aflojar el tornillo que fija la cámara hay un riesgo muuuuuy grande de que pierda un poco la posición, lo suficiente para que luego el acoplamiento no sea correcto a la hora de procesarlas. También puede pasar que con esos minutos "perdidos" se muevan las nuves o algun elemento móvil. Tenia la opción de hacer un agujero en la base dónde se aploma la cámara para poder cambiar la batería en cualquier momento, pero ... ¿no será más práctico olvidarse de la batería?

Es por ello que empecé a investigar por el Google ya que sabia que estas cámaras son un poco pejigeras con las baterías y si no le pones una "codificada" se bloquea la cámara.


Nota> Si utilizais un grip para alimentar la cámara mientras una de las dos baterias sean "codificadas" no hay problemas de funcionamiento. El hecho de "codificar" basicamente consiste en la existencia de una pequeña comunicación bateria<=>cámara tal y como explican en el siguiente post de NikonHacker.

Tras ver esta información decidí jugarmela, entendiendo que aparte de la electrónica hay una batería pura y dura y esta es la parte de que yo quería prescindir por lo que cojí una batería clónica (codificada) y haciendo un poco de palanca con un cuchillo de cocina puntiagudo fuí abriendola poco a poco hasta encontrarme con las tripas como se dice bulgarmente.


Tal y como vemos se trata de dos baterias de 3.7V seriadas lo que dan un voltage de 7.4v. Lo curioso es que sale un conductor en el punto de union de ambas por lo que a nuestra placa electrónica también tendrá una señal de 3.7v que deduzco que será usada para hacer trabajar la lógica de la comunicación a 3.3v . 


Mediante el uso del voltimetro confirmo los voltajes y polaridad aunque en la propia placa viene marcado como +B y -B . Una vez todo claro y con el uso de unas alicates de corte rompo los tres conductores para liberar la batería de la electrónica.

Ahora es cuando cojo un step-down de los que hay que comprar en eBay  pues en las tiendas de electrónica de este pais no saben ni lo que es! Se trata de un regulador de voltaje altamente eficiente pues no pierde energía como los 78xx ya que no disipa apenas calor.  Lo conecto a una fuente de alimentación externa para regular el voltaje de sálida al mismo valor que me dá cuando conecto el voltímetro a los bornes positivo y negativo de la batería original (en mi caso unos 7.8v marca el display, pero vosotros haced las pruebas correspondientes, yo me la juego con mis equipos, y aquí cada uno que sea consciente de lo que hace).

Como que en el sistema original habia una toma de tensión que tenia la mitad del voltaje, decido aplicar la ley de ohm y hacer un divisor de tensión, para ello usaremos dos resistencias, las dos que encontré primero fueron de 10 k y lo conectaremos tal y como está el siguiente esquema:


Soldamos los cables a la sálida de nuestro step down al igual que las dos resistencias unidas en su extremo entre si. En este punto es dónde obtendremos la mitad del voltaje.



Una vez comprobado que los voltajes sean apropiados sólo queda soldar estos tres cables a la placa electrónica de nuestra batería.


Ahora haremos un pequeño agujero a la carcasa de nuestra antigua batería para poder sacar el cable. Yo he utilizado un cable de portero automático de 3 x 0.5mm


Ya sólo falta ensamblar y probar. Tal y como veis en la siguiente foto aparecen unos terminales en la bateria marcados como T y D que son los encargados de la comunicación.


Finalmente os dejo un pequeño video demostrativo, grabado como he podido pues me faltaban manos jiji! . Yo aun no he tenido la posibilidad de hacer una prueba de campo con centenares de fotografías pero estoy convencido que funcionará.


Como anécdota deciros que a mi me falló la primera vez y no entendía el porqué, lo más preocupante pasaba por pensar si habría frito la cámara pero tras colocar una batería buena observé que funcionaba bién. Luego con paciencia y con la ayuda del téster fuí analizando hasta encontrar que me fallaba la pista del negativo por lo que acabé realizando un puente (cablecito amarillo) casi directo tal y como veréis en la siguiente foto.


Deseo que os guste y os sirva de algo. 
Saludos y DIY !







viernes, 2 de mayo de 2014

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

Otra vez de nuevo por aqui...pasando el rato...

Hoy voy a explicar un poco a grandes pasos el programa de Arduino que gestiona el robot panoramico XS.
Como que algunos estais provando de construiros uno, mostraré la versión que funciona a traves de la LCD shield de 2x16 caracteres, de esta manera ya tendreis pulsadores y pantalla integrados.

Antes de nada, prepararos con el tostón jiji, son poco menos de 1700 lineas a comentar. Obviamente quién lo quiera me lo puede pedir, o podeis ir haciendo copiar+pegar...

Este programa está basado en mi primer robot panorámico al cual se le han ido haciendo mejoras, pero una de las caracteristicas de este programa es que tiene la virtud de servir para dos aplicaciones : Hacer fotografias de alta resolución y gestionar una slide dolly. Al fín y al cabo el hardware es el mismo y el control va hacia dos motores en ambos casos (si en la slide dolly sólo quereis usar uno no pasa nada, pero deberiais conectarlo en el eje X). En mi caso mediante unos conectores DB-9 conecto los motores que van a la dolly o hacia el robot.

Si no recuerdo mal, en el post en que explicaba el programa de mi robot original está mejor comentado el tema de variables de la función gigapan.

Pero...vamos al grano! Una justa explicación del programa de Arduino.

Empezamos con una pequeña descripción del programa:


/*
 GigaRobot_XS based on GigapanRobot 1.8beta2

 Photo Robot 2x L298N + Nema 17
 language: Wiring/Arduino

 This program drives two bipolar stepper motor.
 Photo-shots signals it's actuated by relay --
 Movement manual motor by array switchs of keypad

 Created 28 Dec. 2012
 Last updated 22 Feb. 2014

 by XavierGP

 */


Incluimos las tres librerias que nos haran falta, a destacar la <Stepper2.h> que es una modificación casera para trabajar con medios pasos. Si alguién la necesita que la pida! Si en cambio quereis usar la libreria <Stepper.h> que viene instalada por defecto en vuestro software de arduino también debereis modificar las variables motorStepsX y motorStepsY que debereis poner el numero de pasos del motor. En mi caso he puesto 400 en vez de los 200 típicos de un motor NEMA-17.


#include <Stepper2.h>        // incluimos la Stepper library
#include <LiquidCrystal.h>  // incluimos la LiquidCrystal LCD library
#include <Wire.h>  // Comes with Arduino IDE

Continuamos definiendo las variables, algunas de ellas están comentadas entre //

#define _360div2xPI     57.29577951
#define motorStepsX 400     // Pasos motor X 360º/1.8º   com que treballo en llibreria de 1/2 pas multiplico per 2
#define motorStepsY 400     // Pasos motor Y 360º/1.8º                      
#define pulsosvueltaejex 2075            // 200impulsos x reduccion mecanica   200*5.1*2
#define pulsosvueltaejey 2075            // 200impulsos x reduccion mecanica
float impX=pulsosvueltaejex/360;      // número de impulsos que corresponden a 1º del eje X    
float impY=pulsosvueltaejey/360;      // número de impulsos que corresponden a 1º del eje Y   
int pasoX;                            // pasos motor entre capturas eje X 
int pasoY;                            // pasos motor entre capturas eje Y  
float xact;               // º actual eje X
float yact;               // º actual eje Y
float xmax;               // º posición máxima eje X
float xmin;               // º posición mínima eje X
float ymax;               // º posición máxima eje Y
float ymin;               // º posición mínima eje Y
int xtemp;
int ytemp;
int nshot;
int columnasX;                 // número de filas eje X >> (xmax-xmin)/pasoX
int columnasXX;
int filasY;              // número de columnas eje Y    >> (ymax-ymin)/pasoY
int filasYY;
int previsionShot;          // prevision número shots   columnasX * filasY
int segundos ;
int minutos ;
int horas ;
int pause;

int pdisplay;
int pdisplay0;

float temperaturaX;  // variable to store the value of radiator
float temperaturaY;
float voltage;      // variable to store the voltage battery supply
float valorTempX;
float valorTempY;
int progreso;
float solape=0.20;
float retardo;
int origen;
int tempj;
int tempi;
int a;
int sentidoX;
int sentidoY;
int shot=25;
float pasoXX;              // valor temporal dolly eje X
float pasoYY;              // valor temporal dolly eje Y
int pasX;
int pasY;
long int xy;
long int resto;
float segundos_secuencia=3;
float posicion_actual_X;
float posicion_actual_Y;
int velocidadX;
int velocidadY;
float tiempo_reloj;
int espai;


Ahora definimos los pins: En nuestro caso el PinFan corresponde a la salida dónde deberiamos conectar un relé que accionaria el ventilador en caso de exceso de temperatura en los drivers (es una cosa opcional pero recomendable). El PinShot es la salida a la cual conectariamos un relé, optoacoplador o transistor para accionar el comandamiento remoto de nuestra cámara.

#define PinFan 24
#define PinShot 25


Acto seguido es cuando introducimos los valores obtenidos al pulsar las teclas de la shield LCD (ver más info en el post anterior dónde se explica cómo obtener dichos valores). Como "extra" hay la variable margen que es una tolerancia que se le aplica al valor para que no sea tan crítico. Cuanto más grande más tolerancia pero luego puede existir "solapes" entre valores de teclas y que no nos responda correctamente al pulsar los pulsadores.

#define abajo 306      //definimos valores teclas 255
#define arriba 131     //99      // 132
#define left 480       // 407   
#define right 0
#define select 721     // 636
#define margen 50    //abans era 30

Ahora unas cuantas variables que serviran para optimizar los movimientos de nuestro robot teniendo pequeños detalles fotograficos.

float d1;         // distancia 1 sensor CMOS (ancho)
float d2;         // distancia 2 sensor CMOS (alto) 
float diagonal_sensor;
float grados_diagonal;
float grados_horizontales;
float grados_verticales;
float factor=1.5;     // en su defecto : FF = 1, Nikon DX=1.5 , Canon DX=1.6 , Olympus= 2 
float focal = 200;  
int paso;
int menu=2;
int menu0;
int modenight=2;


Aunque no lo tengo montado, el programa está preparado para montar dos sensores de origen en nuestra dolly , para que cuando arranque antes de nada vaya a un punto de inicio limite y podamos estar seguros de que no se nos va a perder el carro...

#define limitX 39             // pin entrada sensor origen X             
#define limitY 49             // pin entrada sensor origen Y                           


Finalmente todas las variables con el nombre logoxx son la definicion de caracteres especiales encargados de dar la forma de camara fotográfica haciendo fotos cuando estamos en modo time-lapse.

byte logo1a[8] = {B01111, B00110, B01111, B11111, B11111, B11111, B11110, B11110};
byte logo1d[8] = {B00000, B00000, B11110, B11111, B11111, B11111, B11111, B11111};
byte logo1e[8] = {B11110, B11110, B11110, B11111, B11111, B11111, B11111, B01111};
byte logo1h[8] = {B01111, B01111, B01111, B11111, B11111, B11111, B11111, B11110};


Aqui definimeros las funciones correspondientes a los motores.En este caso el motorX lo estamos definiendo en que utilizaremos las salidas 36, 38, 40 y 42 de nuestro arduino, mientras que en el caso del motorY seran las 46, 48, 50 y 52
.
Stepper2 myStepperX(motorStepsX, 36, 38, 40, 42); // initialize the library motor X
Stepper2 myStepperY(motorStepsY, 46, 48, 50, 52); // initialize the library motor Y


También definimos los pins que corresponden a las salidas que van al LCD. Si quisieramos utilizar un LCD via i2C la cosa cambia y habria que redefinir unas algunas cosas que no voy a explicar ahora para no hacer un tostón de post.

LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // definimos los pins utilizados para la comunicacion con la pantalla LCD

Luego es cuando establecemos los pins como entradas o salidas

void setup() {
Serial.begin(9600);
lcd.begin(16, 2);

pinMode(PinShot, OUTPUT);      // Output digital shot
pinMode(PinFan, OUTPUT);          // Output digital fan
pinMode(PinSelect, INPUT);
digitalWrite(PinShot, LOW);
digitalWrite(PinFan,LOW);
pinMode(limitX, INPUT);
pinMode(limitY, INPUT);

Generamos los caracteres que nos compondrá la imagen de camara fotografica y salimos del void setup , esta función sólo será ejecutada una vez en todo el programa, a diferencia del void loop que se repite infinitamente.

lcd.createChar(1, logo1a);
lcd.createChar(4, logo1d);
lcd.createChar(5, logo1e);
lcd.createChar(8, logo1h);
}

Empezamos definiendo la velocidad de los motores X e Y. Y acto seguido llamamos a la subrutina presentacioninicio() que es la encargada de darnos la bienvenida.


void loop(){
myStepperX.setSpeed(5);  // set the motor X speed 
myStepperY.setSpeed(8);  // set the motor Y speed 
presentacioninicio();
progreso=0;


A continuación elejimos nuestro modo de trabajo , ya si queremos realizar una gigafoto o bién un time-lapse , mejor dicho motion-lapse pues nuestra dolly se va a mover! Dependiendo de nuestra elección se ejecutará la subrutina gigapan() o slidedolly() y una vez terminen su ciclo se realizará un borrado de todas las variables mediante la subrutina reset() . Estas subrutinas ya las explicaré cuando llegen. Llegado a este punto os habreis dado cuenta que no explico línea a línea sinó esto daria para escribir una novela...

lcd.clear();
lcd.setCursor(0,0);
lcd.print("[UP]=Slide Dolly");
lcd.setCursor(0,1);
lcd.print("[DOWN] = Gigapan");
while(progreso!=1){ //enter
delay(150);
if((analogRead(0)/margen)==(arriba/margen)){  //arriba
slide_dolly();
reset();
}
if((analogRead(0)/margen)==(abajo/margen)){   //abajo
gigapan();
reset();
}
}
}





Ahora toca comentar la subrutina presentacioninicio() : Tras mostrarse un "Hola XavierGP!" ,despues de un borrado de pantalla y nos muestra el nombre del proyecto mientras aparece una camara fotografica haciendo dos fotos. La subrutina dónde se almacenan / generan los caracteres especiales del "gif" se llaman logo2() y logo3()


void presentacioninicio(){
lcd.clear();
lcd.setCursor(1,0);
lcd.print("Hola XavierGP!");
delay(3000);
lcd.clear();
lcd.setCursor(1,0);
lcd.print("Panorama");
lcd.setCursor(1,1);
lcd.print("Robot");
logo3();
delay(800);
logo2();
delay(400);
logo3();
lcd.setCursor(7,1);
lcd.print("XS");
delay(800);
logo2();
delay(400);
logo3();
delay(3000);  // fin GIF
}





En la subrutina logo2() y logo3() generamos los caracteres, de la siguiente manera: cada caracter sabemos que está compuesto por 8 lineas de 5 pixeles cada una. Cuando el valor es 0 el pixel está apagado mientras que cuando tiene valor 1 está encendido.

void logo2(){
byte logo1b[8] = {B00000, B00111, B11100, B10001, B00111, B01111, B01111, B11111};
byte logo1c[8] = {B00000, B11100, B00111, B10001, B11100, B11110, B11110, B11111};
byte logo1f[8] = {B11110, B11111, B01111, B01111, B00111, B10001, B11100, B11111};
byte logo1g[8] = {B01111, B11111, B11111, B11110, B11100, B10001, B00111, B11111};
byte logo1d[8] = {B00000, B00000, B11110, B11111, B11111, B11111, B11111, B11111};
lcd.createChar(2, logo1b);
lcd.createChar(3, logo1c);
lcd.createChar(6, logo1f);
lcd.createChar(7, logo1g);
lcd.createChar(4, logo1d);
lcd.setCursor(12,0);
lcd.write(1);
lcd.setCursor(13,0);
lcd.write(2);
lcd.setCursor(14,0);
lcd.write(3);
lcd.setCursor(15,0);
lcd.write(4);
lcd.setCursor(12,1);
lcd.write(5);
lcd.setCursor(13,1);
lcd.write(6);
lcd.setCursor(14,1);
lcd.write(7);
lcd.setCursor(15,1);
lcd.write(8);
}

void logo3(){
byte logo1b[8] = {B00000, B00111, B11100, B10001, B00110, B01100, B01000, B10000};
byte logo1c[8] = {B00000, B11100, B00111, B10001, B01100, B00110, B00010, B00001};
byte logo1f[8] = {B10000, B10000, B01000, B01100, B00110, B10001, B11100, B11111};
byte logo1g[8] = {B00001, B00001, B00011, B00110, B01100, B10001, B00111, B11111};
byte logo1d[8] = {B00000, B00000, B11110, B11111, B11111, B11111, B11111, B11111};
lcd.createChar(2, logo1b);
lcd.createChar(3, logo1c);
lcd.createChar(6, logo1f);
lcd.createChar(7, logo1g);
lcd.createChar(4, logo1d);
lcd.setCursor(12,0);
lcd.write(1);
lcd.setCursor(13,0);
lcd.write(2);
lcd.setCursor(14,0);
lcd.write(3);
lcd.setCursor(15,0);
lcd.write(4);
lcd.setCursor(12,1);
lcd.write(5);
lcd.setCursor(13,1);
lcd.write(6);
lcd.setCursor(14,1);
lcd.write(7);
lcd.setCursor(15,1);
lcd.write(8);
}

Ahora en la subrutina gigapan() es dónde en realidad empieza lo divertido!


Primero de todo llamamos a la subrutina defineparametro() que explicaré más adelante (se definen los parametros fotograficos tales como tipo de camara, zoom a utilizar y solape entre capturas).

Acto seguido nos pide que busquemos el punto de inicio, llamando a la subrutina buscaorigen() , este punto siempre deve ser la esuina inferior izquierda de nuestra foto panorámica. Seguidamente nos pedirá que busquemos el punto final (esquina superior derecha).

Una vez tenemos confirmados estos puntos nos servirá para calcular tanto el numero de filas y columnas , y multiplicando entre ellas obtenemos la previsión de capturas


void gigapan(){
defineparametro();
lcd.clear();
lcd.setCursor(1,0);
lcd.print("Define  origen");
lcd.setCursor(0,1);
lcd.print("mediante flechas");
delay(2500);
lcd.setCursor(0,1);
lcd.print("[SELECT]  valida");
delay (3000);
lcd.clear();
buscaorigen();
lcd.clear();
lcd.setCursor(1,0);
lcd.print("Define  final");
lcd.setCursor(0,1);
lcd.print("mediante flechas");
delay(2500);
lcd.setCursor(0,1);
lcd.print("[SELECT]  valida");
delay (3000);
lcd.clear();
buscafinal();
xact=-xmin;
yact=-ymin;
columnasX=((xmax-xmin)/pasoX)+1;
columnasXX=columnasX;
filasY=((ymax-ymin)/pasoY)+1;
filasYY=filasY;
previsionShot=columnasX*filasY;




Es hora de calcular el tiempo necesario en segundos para realizar el proyecto,  ello dependerá de si hemos elejido la opción de retardo o no.


if (retardo>1000){
segundos=(previsionShot*((retardo+500)/1000))+((filasY-1)*pasoY*1.3)+((columnasX-1)*pasoX*1.3)+(previsionShot*0.6); 
}else{
segundos=(previsionShot*retardo/1000)+((filasY-1)*pasoY*1.2)+((columnasX-1)*pasoX*1.2)+(previsionShot*0.6);   //   (previsionShot*retardo/1000)+(0.5*(filasY-1))+(0.7*(filasY*pasoY-1))+(0.7*(columnasX*pasoX-1))  
}

Luego el robot se irá a la posición inicio del eje X. Si hemos elejido modo noche el robot no descenderá en su eje Y ya que en vez de empezar las capturas de abajo a arriba lo hará luego de arriba a abajo. Esta función está pensada para aprovechar la "hora azul" que no dura una hora y somos nosotros los que decidimos cuando empezamos a fotografiar el cielo.



lcd.clear();
lcd.setCursor(0,0);
lcd.print("Moviendo  hacia");
lcd.setCursor(0,1);
lcd.print("posicion  inicio");

myStepperX.step(-(columnasX-1)*pasoX); 

if(modenight==0){                          // bajamos el carro si trabajamos en modo clasico
myStepperY.step(-(filasY-1)*pasoY);    
}

Es hora de mostrar los datos previstos de nuestro proyecto, es decir, las filas, columnas, numero de fotos, grados en vertical y horizontal , la duración prevista en tiempo y la dimensión de nuestra fotografia resultante en megapixeles.




lcd.clear();
lcd.setCursor(0,1);
lcd.print("Home position !");
delay(2000);
lcd.clear();
lcd.print("Datos proyecto:");
lcd.setCursor(0,1); 
lcd.print(previsionShot);
lcd.print(" capturas:"); 
delay(2500);
lcd.setCursor(0,0);  
lcd.print((-xmin+xmax)/impX/1.17,1);
lcd.print(char(223));
lcd.print("H // ");
lcd.print((-ymin+ymax)/impY/1.17,1);
lcd.print(char(223));
lcd.print("V");
lcd.setCursor(0,1);
lcd.print(columnasX);
lcd.print("filas / ");
lcd.print(filasY);
lcd.print("columnas");
delay (4500);
  

Para convertir los segundos a horas y minutos utilizaremos la funcion conversiotiempo() y a continuación está todo a punto para empezar, sólo falta pulsar [SELECT]
  

conversiontiempo();

lcd.clear();
lcd.setCursor(0,0);
lcd.print("[SELECT] para");
lcd.setCursor(0,1);
lcd.print("iniciar y pausar");

delay(2000);
lcd.clear();
lcd.print("[SELECT] = start");
pause=1;

while(pause!=0){
if((analogRead(0)/margen)==(select/margen)){ //enter)
pause=0;
}
}
xmax=xmax-xmin;
ymax=ymax-ymin;
xmin=0;
ymin=0;
xact=0;
yact=0;

En el caso que trabajemos en modo noche , invertimos el sentido de giro del motor del eje Y, para ello invertimos el signo del  valor de PasoY (pasos a efectuar por el motor del eje Y entre filas). Acto seguido empieza la rutina:
* Mostrar pantalla (pantallaGigapan())
* Petición de pausa siempre y cuando no sea la primera toma (como que para arrancar la secuencia es el mismo pulsador que para pausar se generaba la pausa automáticamente al iniciar el ciclo)
* Esperamos un segundo
* Chequeamos temperatura en los drivers (si es necesario arrancará o se parará el ventilador)
* Analizamos si el retardo es superior a 1" por lo que si es asi le aplicaremos un retardo "extra" de medio segundo antes de disparar la foto
* Volveremos a pedir petición de pausa (este paso se repetirá continuadamente, es la única manera de poder pausar en cualquier momento)
* Realizamos una captura
* Actualizamos datos en la pantalla
* Esperamos medio segundo aparte del retardo seleccionado
* Desplazamos el motor horizontal X hasta la siguiente captura, hasta llegar al limite de capturas horizontales , momento en que retrocederá el robot hasta el punto inicial del eje X mientras nos muestra en pantalla la temperatura y voltaje. Una vez llegada a la posición inicial será momento de mover el motor del eje Y hasta la siguiente fila, y luego volvemos a iniciar todo el ciclo hasta llegar a la última posición en que nos mostrará el mensaje de "Panorámica completa!".


if (modenight==1) {      // si trabajamos en modo night invertimos el sentido de paso del eje Y
pasoY=pasoY*(-1);
}
lcd.clear();
pantallaGigapan();
for(int i=0; i<filasY; i++){      // empieza secuencia panoramica         
if (nshot!=0){
  peticiopausa();
}
delay(1000);
for(int j=0; j<(columnasX-1); j++){   //columnasX
peticiopausa();
analisisTemperatura();
tempi=i+1;
tempj=j+1;
if(retardo>1000){    //per compensar modo night
delay(500);
}
peticiopausa();
digitalWrite(PinShot, HIGH);
delay(500);                       
nshot=nshot++;
pantallaGigapan();
digitalWrite(PinShot, LOW);
peticiopausa();   
delay(500+retardo); 
if(j!=(columnasX+0)){   
myStepperX.step(pasoX);
xact=xact+(pasoX);
tempj=j+2;
pantallaGigapan();
tempj=j+1;

delay(500);                                                            
}

nshot=nshot++;
digitalWrite(PinShot, HIGH);
tempj=tempj+1;
pantallaGigapan();
delay(500);                         
digitalWrite(PinShot, LOW);
delay(500+retardo);

pantallatemperatura();
       
myStepperX.step((-(columnasXX)+1)*pasoX);   
xact=0;
if(i!=filasY-1){myStepperY.step(pasoY);}                                 
yact=yact+(pasoY);
delay(1000);
lcd.clear();
}
lcd.clear();
lcd.setCursor(1,0);
lcd.print("Panoramica");
lcd.setCursor(6,1);
lcd.print("completa !");
if(modenight==0){
myStepperY.step((-filasYY+1)*pasoY);
}

delay(5000);
}


Y una vez llegemos al final de las capturas es cuando aparecerá el mensaje de Panorámica completa!



En la siguiente subrutina sirve para definir los parametros de la cámara y el solape entre imagenes. No todas las camaras tienen el mismo tamaño del sensor, y ese tamaño nos influye en el factor recorte resspecto el formato de 35mm hoy en dia llamado con el nombre de Full Frame. En principio podemos determinar que tipo de camara reflex vamos a usar (está pensado para las marcas más populares, pero es muy fácil incluir otras).




void defineparametro(){ // rutina definimos tipo de camara y focal
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Formato d camara");
lcd.setCursor(2, 1);
lcd.print("[UP] & [DOWN]");
delay(2500);
lcd.setCursor(0,1);
lcd.print("[SELECT]  valida");
delay(2500);
while((analogRead(0)/margen)!=(select/margen)){ //enter
delay(300);
if((analogRead(0)/margen)==(abajo/margen)){  //arriba
menu=menu+1;
if(menu>=4){menu=4;}
}
if((analogRead(0)/margen)==(arriba/margen)){   //abajo
menu=menu-1;
if(menu<=1){menu=1;}
}
if(menu==1){ // FF
lcd.setCursor(0,1);
lcd.print("Full Frame 35mm.");
d1=35.8;         //canon 5d
d2=23.9;         //canon 5d
factor=1;
diagonal_sensor=43.0447441623248;
}
if(menu==2){ // Nikon DX
lcd.setCursor(0,1);
lcd.print(" Nikon DX  1.5x  ");
factor=1.5;
d1=23.5;         // distancia 1 sensor CMOS (ancho)   Valor de la Nikon d5200 !
d2=15.6;         // distancia 2 sensor CMOS (alto)    Valor de la Nikon d5200 !
diagonal_sensor=28.2899275361390772;
}
if(menu==3){ // Canon DX
lcd.setCursor(0,1);
lcd.print(" Canon DX  1.6x  ");
factor=1.6;
d1=22.2;         // eos1000d
d2=14.8;         // eos1000d
diagonal_sensor=26.68107943843352;
}
if(menu==4){ // Olympus 4/3
lcd.setCursor(0,1);
lcd.print(" Olympus 4/3 2x ");
factor=2;
d1=17.3;         // e-520
d2=13.0;         // e-520
diagonal_sensor=21.6400092421422;
}
}

Debido a que tenia problemas con las fórmulas y me hacia cosas raras, decidí implementar directamente la medida del sensor en diagonal en vez de hacer la hipotenusa de los dos lados, de esa manera ahorraba algunas lineas pero los problemas aparecidos no compensaban...

Acto seguido definimos la focal que vamos a usar en la cámara, para ello nos fijaremos o bién en los datos exif o en el zoom , pero nunca deberemos aplicar el factor recorte ocasionado por el tamaño del sensor ya que el propio Arduino ya se encargará de aplicarlo a la hora de mostrarnos la focal equivalente. Dependiendo de nuestra focal a la hora de propgramar veremos que los pasos empiezan de milimetro en milimetro y a medida que vamos subiendo de zoom también augmenta este escalonado. Es poco útil subir con pasos pequeños cuando las focales ya pasan de los 100 mm. !

Obviamente estos valores introducidos son cruciales para optimizar las tomas.



lcd.clear();     // rutina definimos focal
lcd.setCursor(0,0);
lcd.print("Longitud  focal:");
lcd.setCursor(1, 1);
lcd.print("[UP] & [DOWN]");
delay(2500);
lcd.setCursor(0,1);
lcd.print("[SELECT]  valida");
delay(2500);
lcd.setCursor(0,1);
lcd.print("                ");
while((analogRead(0)/margen)!=(select/margen)){ //enter
if (focal<100){
paso=1;
delay(75);
}
if (focal>=100){
paso=25;
delay(120);
}
if (focal>399){
paso=50;
delay(150);
}
if((analogRead(0)/margen)==(arriba/margen)){  //arriba
focal=focal+(paso);
}
if((analogRead(0)/margen)==(abajo/margen)){   //abajo
if(focal>1){focal=focal-(paso);}
}
lcd.setCursor(5,1);
if (focal<100){
lcd.print(" ");
}
if (focal<10){
lcd.print(" ");
}  
lcd.print(focal,0);
lcd.setCursor(9,1);
lcd.print("mm.");
}

Acto seguido es hora de definir el sopale, yo siempre suelo usar un 20% aunque desde foros y expertos dicen que ideal un 25-33%. Os puedo asegurar que nunca he tenido problemas, pero todo depende de la mecánica de vuestro robot... Por ejemplo cuando disparamos a 200mm equivale a unos 8º de visión, y nuestro solape andará un poco por encima del 1.5º, si tenemos un juego superior a ese 1.5º el resultado  puede ser un autentico desastre. Mediante software se han marcado unos limites entre 1-50% aunque creo que debería de "caparlo" mas que luego cuando la gente pone valores anormales y no sale la panorámica. 

Obviamente si vamos cortos de memoria o de bateria siempre seria mejor reducir el solape, pero cada uno mismo con su mecanismo!


lcd.clear();     // rutina definimos solape
lcd.setCursor(0,0);
lcd.print(" Define solape: ");
lcd.setCursor(1, 1);
lcd.print("[UP] & [DOWN]");
delay(2500);
lcd.setCursor(0,1);
lcd.print("[SELECT]  valida");
delay(2500);
lcd.setCursor(0,1);
lcd.print("                ");
while((analogRead(0)/margen)!=(select/margen)){ //enter
delay(150);
if((analogRead(0)/margen)==(arriba/margen)){  //arriba
if(solape<=0.49){solape=solape+0.01;}
}
if((analogRead(0)/margen)==(abajo/margen)){   //abajo
if(solape>=0.1){solape=solape-0.01;}
}
lcd.setCursor(5, 1);
lcd.print(solape*100,0);
lcd.print(" % ");
}


Acto seguido nos pide si queremos hacer una foto clasica o bién modo noche, en realidad la diferencia radica en que en el modo clasico empieza desde abajo y va subiendo filas mientras que en el modo noche empieza desde arriba y va bajando , ideal para tomas en hora azul explicada anteriormente.


lcd.clear();
lcd.setCursor(1,0);
lcd.print("Modo secuencia");
delay(2500);
lcd.clear();
lcd.setCursor(1,0);
lcd.print("[UP] = Clasico");
lcd.setCursor(1,1);
lcd.print("[DOWN] = Noche");
while(modenight==2){ //enter
delay(150);
if((analogRead(0)/margen)==(arriba/margen)){  //arriba
modenight=0;
}
if((analogRead(0)/margen)==(abajo/margen)){   //abajo
modenight=1;
}
}

Finalmente nos deja elejir un retardo, este sirve para generar una pausa después de dar señal de foto, ideal para fotos de larga exposición , de esta manera evitaremos que salga movida. En teoria debería ir bién pero siendo sincero, aun no he hecho ninguna nocturna...de cara al verano con los mosquitos...ya falta menos !

lcd.clear();     // rutina definimos retardo (modo noche)
lcd.setCursor(0,0);
lcd.print("Define  retardo ");
lcd.setCursor(0,1);
lcd.print("larga exposicion");
delay(2500);
lcd.setCursor(0,0);
lcd.print(" [UP] & [DOWN] ");
lcd.setCursor(0,1);
lcd.print("[SELECT]  valida");
delay(2500);
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Define  retardo:");
while((analogRead(0)/margen)!=(select/margen)){ //enter
delay(100);
if((analogRead(0)/margen)==(arriba/margen)){  //arriba
if(retardo<=9800){retardo=retardo+100;}
}
if((analogRead(0)/margen)==(abajo/margen)){   //abajo
if(retardo>=100){retardo=retardo-100;}
}
lcd.setCursor(1, 1);
lcd.print("    ");
lcd.print(retardo/1000,1);
lcd.print(" seg.   ");

}

Aqui es cuando se calculan los pasos que deberan dar nuestros motores en función de los datos que le hemos definido y a continuación nos indicará nuestra focal equivalente que no deja de ser el zoom por el factor recorte del objetivo.



// diagonal_sensor=sqrt((d1*d1)+(d2*d2));

focal=focal*factor;
grados_diagonal=2* (atan(diagonal_sensor/(2*focal))*_360div2xPI);  // 2*focal*factor...
grados_horizontales=factor*(grados_diagonal*d1/diagonal_sensor)*(1-solape);  //originalment 2* (atan(diagonal_sensor/(2*focal*factor))*_360div2xPI);
grados_verticales =factor*(grados_diagonal*d2/diagonal_sensor)*(1-solape);        //originalment grados_horizontales * d2 / d1

pasoX=(pulsosvueltaejex*(grados_horizontales/360));
pasoY=(pulsosvueltaejey*(grados_verticales/360));

lcd.clear();
lcd.setCursor(0,0);
lcd.print ("Focal equivalente");
lcd.setCursor(4, 1);
lcd.print(focal,0);
lcd.print(" mm.");
delay(2000);
}

Acto seguido toca explicar la subrutina de busqueda de origen que es muy simple. Esta llama a la subrutina movimiento que es la encargada de habilitar el movimiento mediante los pulsadores UP/DWN/LFT/RGT y una vez pulsamos sobre SELECT valida los datos.

La busqueda de posición final es igual que la de origen, lo único que se memorizan las posiciones actuales en memorias diferentes y una vez generada el resto es cuando en realidad se determina los grados que van a acaparar nuestra fotografia resultante.


void buscaorigen(){
while((analogRead(0)/margen)!=(select/margen)){ //enter
movimiento();
}
xact=0; 
yact=0;   
xtemp=0;
ytemp=0;  
xmin=xact;   
ymin=yact;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Posicion  inicio");
lcd.setCursor(1, 1);
lcd.print("- CONFIRMADA -");
origen=1;
delay(3500);
}

void buscafinal(){
while((analogRead(0)/margen)!=(select/margen)){ //enter
movimiento();
}
xmax=xact;
ymax=yact;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Posicion  final");
lcd.setCursor(1, 1);
lcd.print("- CONFIRMADA - ");
delay(3500);
}
  

Vamos a por la subrutina movimiento(), antes de nada ejecuta la subrutina de analisisTemperatura() para comprobar que no exista mucha temperatura en los drivers de potencia, y acto seguido mediante los pulsadores nos deja hacer movimientos memorizandolos en las variables xact e yact (de actual). Luego más tarde actualiza los datos en la pantalla.

El programa es una simplificación del que yo uso con el LCD de 4 x 20 caracteres, en cambio con este LCD estamos más limitados. En mi caso si determina que estamos haciendo movimientos referidos al punto final también nos mostrará el número previsto de tomas a realizar, eso es útil para nuestra información, yo por ejemplo sé que en la Nikon D5200 puedo hacer poco mas de 800 fotos con una bateria cuando trabaja con el robot panorámico (estabilizador y lcd apagada! ). También el programa nos fuerza a que la posición final siempre sea en sentido más hacia la derecha y mas hacia arriba. Lo siento pero el software de edición de Gigapan's que uso (AutoPanoGiga) sólo contempla 4 modos de reconocimiento y y creo que siempre son de izquierda a derecha...

void movimiento(){
analisisTemperatura();

if((analogRead(A0)/margen)==(right/margen)) {xact=xact+(pasoX);}    //  derecha +X       
if((analogRead(A0)/margen)==(arriba/margen)){yact=yact+(pasoY);}    //   arriba +Y

if((analogRead(A0)/margen)==(left/margen))  {       //  izquierda -X
if (origen==1 && xact>=pasoX){
xact=xact-(pasoX);
}
if (origen==0){
xact=xact-(pasoX);

}     

if((analogRead(A0)/margen)==(abajo/margen)) {    //  abajo -Y 
if (origen==1 && yact>=pasoY){
yact=yact-(pasoY);
}
if (origen==0){
yact=yact-(pasoY);

}     
  


lcd.setCursor(0,0);
lcd.print("Pos X:    Pos Y:");
lcd.setCursor(0, 1);
lcd.print(xact/impX/1.17,1);   
lcd.print(char(223));
lcd.print(" ");
lcd.setCursor(10, 1);
lcd.print(yact/impY/1.17,1);   
lcd.print(char(223));
lcd.print(" ");

/*lcd.setCursor(4,2);
lcd.print((xact/pasoX)+origen,0);
lcd.print(" ");
lcd.setCursor(14,2);
lcd.print((yact/pasoY)+origen,0);
lcd.print(" ");
if (origen!=0){
lcd.setCursor(0,3);
lcd.print("Numero de fotos: ");
lcd.print(((xact/pasoX)+1)*((yact/pasoY)+1),0);
lcd.print(" ");
}
*/

Acto seguido los motores se mueven si han determinado alguna variación en los valores actuales respecto los temporales (antigua lectura)

myStepperX.step(xact-xtemp);
xtemp=xact;
myStepperY.step(yact-ytemp);
ytemp=yact;
delay(20);
}

La siguiente subrutina es la que nos servirá para convertir los segundos a horas, minutos y segundos de una manera un poco "bruta" pero efectiva y acto seguido nos informará de lo mismo.

void conversiontiempo(){  // convertimos tiempo necesario en realizar proyecto 
while(segundos>=3600){   
horas = horas++;
segundos = segundos - 3600;
}
while(segundos>=60){
minutos = minutos++;
segundos = segundos - 60;
}
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Tamano previsto");
lcd.setCursor(0,1);

Previamente nos dirá el tamaño previsto, yo lo tengo configurado para mi Nikon d5200, habria que modificar el 24.2 de la siguiente linea por los megapixeles de vuestra cámara.



lcd.print((((xmax/pasoX)+1)*((ymax/pasoY)+1)*24.2*(1-solape)));

Serial.println(((xmax/pasoX)+1)*((ymax/pasoY)+1));

lcd.print("Mb.");
delay(3500);

Y acto seguido la duración:

lcd.setCursor(0,0);
lcd.print("Duracion prevista");
lcd.setCursor(0,1);
lcd.print(horas);
lcd.print("hr ");
if (minutos<10){
lcd.print("0");
}
lcd.print(minutos);
lcd.print("min ");
if (segundos<10){
lcd.print("0");
}
lcd.print(segundos);
lcd.print("seg");
delay(5000);
}

Venga, que ya falta menos para llegar al fín de la primera parte del tostón... Ahora viene la subrutina pantallaGigapan() que es dónde se ejecutan las instrucciones para mostrar en el LCD la posición actual.



void pantallaGigapan(){
// lcd.clear();
progreso=((nshot*100)/(previsionShot));
lcd.setCursor(0,0);
lcd.print(progreso);
lcd.print("%");
lcd.setCursor(6,0);
lcd.print("X:    Y:");
lcd.setCursor(0,1);
lcd.print(nshot);
lcd.print("s");
lcd.setCursor(6,1);
lcd.print(tempj);
lcd.print("  ");
lcd.setCursor(12,1);
lcd.print(tempi);
lcd.print("  ");
/*
lcd.setCursor(7,3);
lcd.print(xact/5.85,1);   // possar /5
lcd.print(char(223));
lcd.print("  ");
lcd.setCursor(13,3);
if(modenight==0){
lcd.print(yact/5.85,1);     // possar /5
}
else{
lcd.print(-yact/5.85,1);     // possar /5
}

lcd.print(char(223));
lcd.print("  ");
*/
}

Y ahora la visualización de la pantalla de temperatura que se ejecuta cuando retrocede el robot al final de cada fila.
No hay mucho que explicar pues son simples de programar, pero si quieres que quede bonito hay que acabar modificando la posición de los caracteres seguro con la pega que hay que probar y probar. No le hagais mucho caso a los valores mostrados en la siguiente foto , estaba todas estas entradas desconectadas. Si vais a usar la LCD shield tocará modificar el pineado de el voltimetro y del sensor de temperatura, o bién hacer alguna ñapa en la shield conectado como se pueda...



void pantallatemperatura(){
analisisTemperatura();
lcd.clear();
lcd.print("Voltage: ");
voltage=analogRead(A5)*1.212;
lcd.print(voltage/100);   
lcd.print(" v.");
lcd.setCursor(0,1);
lcd.print("Temp.:  ");
lcd.print(valorTempX,1);
lcd.print(char(223));
lcd.print("C");
}

La subrutina peticiopausa() se caracteriza por pedir 10 veces seguidas si está el pulsador SELECT activado, ¿por qué diez veces? Pues porque con una simple NUNCA se enteraba y pasaba de largo. Si detecta que está pulsado se activa una Pausa y hasta que no pulsemos la tecla DOWN no continuará. Acto seguido llama a la subrutina de la pantallaGigapan para actualizar y seguir con el ciclo.


void peticiopausa(){
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();
}
}
}
}
pantallaGigapan();
}

Finalmente la subrutina de analisis de temperatura está pensada para usar dos sensores de temperatura aunque en la pantalla lcd sólo os marcará el del driver X . En este caso si detecta que que la temperatura excede en cualquiera de ellos de 33ºC arranca el ventilador dando señal al PinFan y este sólo se parará cuando la temperatura en ambos drivers sea inferior a 29ºC.


void analisisTemperatura(){
temperaturaX=analogRead(A6);         // comprobamos temperaturaX en disipador LM35DT
temperaturaY=analogRead(A7);         // comprobamos temperaturaY en disipador LM35DT
valorTempX = ((5.0 * temperaturaX * 100 )/1024.0);
valorTempY = ((5.0 * temperaturaY * 100 )/1024.0);
if(valorTempX<29  && valorTempY<29){digitalWrite(PinFan,LOW);}              // deshabilita tensión en ventilador - energy saving - si temperatura < 29ºC
if(valorTempX>33 || valorTempY>33){digitalWrite(PinFan, HIGH);}            // habilita tensión en ventilador si temperatura > 33ºC
}

void slide_dolly(){


Y por hoy esto es todo! Aqui es dónde empieza las funciones de la slide dolly.
Os dejo un video para que veais el comportamiendo de los menus. Las imégenes también tienen un efecto barril provocadas por la camara deportiva AEE SD19 , pero más vale esto que nada no?


Saludos!