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!

domingo, 20 de abril de 2014

Robot panorámico XS (la electrónica)

Por si alguién quiere montarse uno, os dejo un poco de información referente al conexionado de los diferentes elementos. En alguna otra ocasión ya sugerí que es recomendable tener ciertos conocimientos básicos de electrónica y programación del Arduino, más que nada que en google se puede encontrar información pero cuando surgen problemas especificos es cuando uno tiene que auto-espavilarse como pueda. Yo siempre he intentado ayudar, pero no dispongo de todo el tiempo del mundo  para hacerlo.

En mi caso utilizo una pantalla LCD de 4 lineas x 20 caracteres y un joystick sacado de un nunchuck de la wii , como que estos dos componentes son un poco usuales , os voy a describir a continuación como hacer funcionar el robot con una shield de pantalla LCD de 2x16 caracteres con pulsadores como la de la siguinete captura:

Usando una shield como esta reduciremos cableado y posibles errores de conexionado. Como pega, no es lo mismo tener una pantalla de 32 (2x16) caracteres que una de 80 (4x20) y eso se ve reflejado a la hora de darle un aspecto más bonito a la aplicación o un muestreo extra de información que siempre es mejor.

Y obviamente si vamos a utilizar esta pantalla habrá que usar un programa en concreto, mientras que en la de 4x20 caracteres hay que "reformarlo" en gran parte, debido entre otras cosas que se comunica sólo mediante 4 cables mediante el bus I2C (positivo, negativo, SDA y SCL) y que al no tener pulsadores incorporados hay que cablearlo aparte definiendo nuevas entradas.


Una de las ventajas del LCD shield es que incorpora de serie varios botones (UP, DOWN, LEFT, RIGHT y SELECT) , todos ellos van dirigidos hacia la entrada análogica 0 de nuestro Arduino. Por cierto alguien se preguntará porque un MEGA y no un UNO. El problema es que el UNO tiene una capacidad de memoria de 32 kB y el programa que vamos a utilizar ronda los 31 kB. Otro de los problemas es que al utilizar la shield LCD y "montarla" justo encima hay que empezar a hacer "guarrerias" para conectar los cables que van hacia los drivers de potencia etc...

A lo que iva...todo y vivir en un mundo digital lleno de ceros y unos, es muy bonito usar las entradas analógicas que nos brinda el Arduino. Mediante un voltage de referencia de +5v que podemos aplicar a dichas entradas podemos tratarlas luego internamente de varias maneras generando una entrada multiplexada y ahorrandonos entradas.

Si le aplicamos a una entrada análogica 0 v obtendremos un valor de gestión de valor 0, mientras que si aplicamos 5v en la entrada obtendremos un valor de gestión de 1023. Es decir tenemos una resolución de 1024 con el que podemos jugar...Ahora bién, y electricamente como podemos aplicar un valor variable mediante pulsadores? Muy fácil ! con el uso de resistencias tal y como se muestra en el siguiente esquema:


Estas resistencias ya están montadas en nuestra shield, por lo que no deberemos preocuparnos. Por ejemplo si pulsaramos sobre el pulsador RIGHT provovariamos que en la entrada AD0 un voltaje de 0v, mientras que si pulsaramos sobre UP deberiamos calcular el divisor de tensión generado por las resistencias R2 y R3. A efectos practicos no nos interesa en concreto el valor recibido a la entrada AD0, pues nosotros vamos a tratar con el valor de gestión que reciba nuestro Arduino. Y que valor es este? Os propongo que utiliceis el siguiente sketch para ver como se comportan los pulsadores en vuestro caso:

/*
Test LCD shield
by XavierGP
*/

#include <LiquidCrystal.h>           // incluimos la LiquidCrystal LCD library
int valor;                           // variable recibida en el keypad
LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // define los pins del LCD

void setup() { 
lcd.begin(16, 2);
lcd.print(" Hola XavierGP !");
delay(2500);
}

void loop(){
valor=analogRead(0);
if(valor<1020){
lcd.clear();
lcd.print(valor);
delay(350);       
}
}

Una vez cargado este sketch y con la shield LCD montada encima el Arduino observaremos como cambian los valores dependiendo del pulsador que estamos apretando. Estos valores nos serán necesarios a la hora de realizar ajustes en nuestro programa. No todas las shields proporcionan el mismo valor por lo que quizás sea necesario corregirlos. Os enseño unas muestras entre dos shields en principio iguales pero a la realidad los valores cambian:


Finalmente os adjunto el circuito eléctrico básico para que funcione el robot:



Empezando por la parte inferior izquierda encontramos una batería típica de 12v / 7Ah. de la cual salen dos pares de cables, unos de ellos se van directamente hacia la alimentación del Arduino (hay que comprar el jack adecuado! En el centro de este soldaremos el positivo mientras que el negativo va en el lado exterior del mismo). El propio arduino dispone de un regulador/estabilizador incorporado pero no estaria de más ponerle algun tipo de proteción intermedia...

El otro par de cables van hacia un step-down (regulador de voltage) en el que obtengo una tensión de salida constante de 5v y hasta 10Amperios de intensidad entre el cable amarillo y cualquiera de los negros. Estos 5v los utilizaremos para alimentar los motores(al menos con los motores que yo utilizo!, si montais otros motores quizas haya que cambiar el step down por un regulador de voltage) que seran gestionados por los drivers L298N. El driver se comportará como un amplificador de potencia ya que la intensidad que nos ofrece el arduino en sus salidas es muy pequeña para alimentar a los motores.

Luego tenemos dos grupos de 4 cables que van de las sálidas 36, 38, 40 y 42 , y 46, 48, 50 y 52 que van hacia las entradas de pilotaje de los drivers IN1, IN2, IN3 e IN4 . Cada grupo de 4 conductores van hacia cada uno de los dos drivers.  Es recomendable seguir bién el orden. Podeis leer posts antiguos sobre motores paso a paso para entender como funcionan estos...

A las 4 salidas del driver conectaremos nuestros motores paso a paso.

Luego nos toca conectar el relé en la sálida 25 de nuestro arduino , y el otro borne del relé a GND. En este esquema no está marcado correctamente para no liar más entre los diferentes conductores, pero debajo de los PINS 52 y 53 tenemos dos pins de GND. Y para vuestra información encima de los PINS22 y 23 tenemos dos pins que nos ofrecen 5v continuadamante.

Un detalle "tonto" pero necesario es la colocación de un diodo en paralelo a la bobina del relé para eliminar la corriente generada al desactivar el relé, protegiendonos nuestra salida del arduino.

Este relé es el encargado de activar el contacto remoto de nuestra camara.

Finalmente y como "accesorios" extras tengo montado unos sensores de temperatura tipo LM35 que a su vez gestionan el funcionamiento de otro relé que activa/desactiva el ventilador de refrigeración del sistema. En este caso la lectura la realizo en la entrada análogica A6 y la salida va al PIN 24. Sólo como curiosidad , en mi robot XS con pantalla más grande gestiono las temperaturas de los dos drivers por lo que utilizo dos LM35 , uno va a la entrada A6 y el otro a la A7, pero sigue existiendo sólo un ventilador que enfria a los dos.

Os dejo un ejemplo del conexionado del mismo, con la diferencia que aqui está conectado en el borne A5 en vez del A6. Podeis encontrar infinidad de información en Google sobre circuitos con el LM35.



Y otra "mariconada" más es un divisor de tensión que sirve para informarme del voltage de la bateria. Esta información es ofrecida cuando el robot retorna a la posición de inicio de cada linea, pero a efectos prácticos si no lo tenemos habilitado el programa en si cumple con su proposito. Esto lo gestiono a través de la entrada análogica A5, siguiendo el siguiente principio de circuito:


Finalmente os adjunto una vista de como lo tengo yo montado , pero cuidadín , repito, yo no utilizo la SHIELD LCD !


Si lo conectáis todo igual , y utilizais los mismos motores (ver post de lista de la compra de materiales) , tan sólo faltará casi cargar el programa, pero esto será para otro dia.

Saludos y DIY! 

miércoles, 19 de marzo de 2014

De pruebas con el robot panorámico XS

Los dias pasaban, y muy a mi pesar no tenia momentos para probar mi nuevo robot panorámico. Entre semana no dispongo actualmente de mucho tiempo libre por lo que siempre toca esperar al fin de semana.

Entre semana voy "corrigiendo" errores de programación y dejando que la visualización en la pantalla sea agradable.

El programa del robot tiene la misma base que en mis otros dos robots panorámicos, pero esta vez le he hecho unas cuantas mejoras:

* Cuando arranca , puedo elejir entre modo Gigapan o modo time-lapse. Aprovechando que tengo dos salidas con unos conectores tipo DB-9 puedo o bién conectar mi slide dolly (actualmente de un sólo eje) o bién dejar las conexiones para el robot panorámico, con esto gano que con un sólo control lo aprovecho para más cosas, total, la electrónica es la misma!

* Si elijo el modo Gigapan puedo elejir el tipo de formato de camara que voy a usar (Full Frame, Nikon DX, Canon DX u Olympus 4/3). Acto seguido puedo elejir el zoom al que quiero trabajar . Con estas dos variables, el Arduino calcula ya el angulo que le corresponde . Finalmente nos deja elejir el solape entre fotos (ideal que no sea inferior al 20%) y con todo esto ya calcula los pasos a realizar entre fotos en los dos ejes. En este punto nada de nuevo.

* Acto seguido , pide si quiero trabajar en modo normal o en modo noche: La diferencia radica en que en el modo normal empieza a hacer las fotos de izquierda a derecha y al final de la linea retrocede el cabezal y sube hacia arriba un fotograma, y asi sucesivamente. Mientras que en el modo noche funciona de izquierda a derecha pero de arriba hacia abajo, con esto quiero conseguir poder fotografiar gigapanes en la hora azul, pero esto ya veremos si lo consigo...

* Finalmente nos pide si queremos implementar un retardo entre fotos. Esto creo que puede ser muy útil  sobretodo cuando hacemos fotos de larga exposición. Debido que el arduino no se entera si ha terminado de realizar una foto o no y se va despues de unos 500 mS, he decidido implementar esta función programable.

* Acto seguido empieza a realizar la rutina hasta llegar a su fín, momento en que el cabezal vuelve a posición origen y reinicia el programa de nuevo por si queremos realizar otra captura. En versiones anteriores se quedaba en el loop haciendo una secuencia sin fín dejando bloqueado el sistema completamente. 

Y si lo veis ? aqui un video 

Nota> Cuando retrocede el cabezal muestra la temperatura de los dos drivers L298N y el voltaje de la bateria, este último valor es erroneo porque falta parte de la electrónica, por eso que marca 2v. en vez de unos 12-14v

Pues bién, este sábado pasado intenté realizar una foto de la Plana de l'Om de Manresa, decidí ir a primera hora de la tarde (aun no eran ni las 3!) y ya me encontré mucha gente (vamos mal...). Empiezo a montar los cacharros y ....

Primer problema: necesito inclinar mucho la camara hacia arriba para que me aparezca completamente el edificio de Can Jorba y el robot no me deja debido a un tope de seguridad que monté. No llevo las llaves allen encima. Intento retroceder el tripode con el robot pero aun me falta angulo, pero ya que estoy decido seguir con la prueba.

Programo limites , le doy al enter y oigo un sólo click, se mueven los motores pero no se oyen mas clicks... si apago la camara y la vuelvo a encender y sólo hace una foto. Empiezo a mirar configuraciones de disparo remoto y está en principio todo bién...WTF !

Para rematar viene un personaje diciendome "me interesa el sitio y me interesas tu..." lo que me faltaba para ponerme más en tensión, una plaza llena de imigrantes y me aparece este de la nada diciendome estas cosas tan bonitas... en 4 minutos logré que se fuera a calentar la cabeza a otra gente.

Desmonté los cacharros y de vuelta a casa...

Empiezo a realizar pruebas y me funciona ahora todo bién ! Su pu...a madre! 

Fijo la camara al cabezal metálico y vuelve a fallar, momento en que voy moviendo el cable del disparo remoto a ver si "suena la flauta".

Al cabo de unos minutos sonó! el problema es la fijación de la camara al cabezal mediante el tornillo metálico que no se por qué narices (ya que se tratan de baterias totalmente independientes) que me bloqueavan el obturador.

Quito la camara del robot, la dejo encima de la mesa y cojo un trozo de cable , en un extremo al tornillo de 1/4 de fijación de la camara y en el otro al aire, la cosa funciona, toco a la estructura del robot y voilá  ya tenemos el fallo!

(después de 4 dias aun no he encontrado un sistema de fijación que me convenza...)

Acto seguido pongo un poco de cinta aislante para "aislar" el robot de la camara y pataaaaaán ! ya funciona. 

Cargo los trastos de nuevo al coche y a probar de nuevo. Esta vez me desplazo hasta el pont vell de Manresa , lugar dónde se puede fotografiar la catedral de la Seu y La Cova. Lo preparo todo y a la primera.



Un par de videos del momento...



Tras 576 capturas ( 32 columnas x 18 filas ), una cada poco mas de 3 segundos es hora de volver a casa, convertir los RAW's a JPEG (aproximadamente una hora y media) y ensamblarlos en el AutoPano Giga, hasta obtener al cabo de unas 7 horas un archivo en formato PSB/PSD manipulable con el Photoshop . 


Una vez creado este archivo de unos 80GB de peso, sólo tengo que esperar algo mas de media hora para que se me carge en mi iMac 3.6GHz con 16GB de ram... (cuando le doy a grabar aproximadamente una hora y media), y las manipulaciones para borrar matriculas y pequeñas correcciones son muy lentas y con mucho riesgo de que "pete" todo...

Luego fué momento de colgarla en el servidor de Gigapan para que tod@s podamos disfrutar. Esta vez tardo 66056 segundos (poco mas de 18 horas) para subirla a la red la gigapan de 10.31 GPx.


Deseo que os guste, y que os animeis a hacer vuestras panorámicas.


Si la quereis ver en grande...


Por cierto, hoy en twitter he leido un post sobre un suizo que hace Gigafotos macros...os dejo el enlace para que le deis un poco a la cabeza. 

Si a alguién le sobra un robot de 6 ejes que me lo diga. Mas info en la web de Robophot

Saludos!


miércoles, 12 de marzo de 2014

Robot Panoramico XS (diseño mecánico)

Todo empezó un 10 de enero con unos retales de cartón de la caja de cereales, un metro, un boli y poca cosa mas para realizar un esbozo de lo que seria mi nuevo robot panoramico XS.


Queria hacerlo lo mas pequeño posible por lo que tocaria estrujarse la cabeza en cuanto a materiales y diseño. También tocó investigar con el Sr. Google. Este robot se "parecería" al EPIC Pro pero gestionado por Arduino.

La primera medida seria el ancho del robot, por lo que coji la camara de batalla (la Nikon d5200) y medí la distancia que hay entre el centro de la rosca de 1/4" de fijación que hay en la parte inferior y el extremo de la camara más lejano en su plano horizontal (distancia d1). Esta seria la distancia a multiplicar por dos que me marcaria la mínima anchura. El punto dónde esta fijada la rosca tiene que estar en línea con el eje de rotación horizontal.



También busqué la distancia que hay desde la base de la cámara hasta el centro del sensor de la misma, esta distancia d2 corresponderá con el eje de rotación vertical.

Finalmente hay otra distancia que la determiné un poco a lo loco: la profuncidad a la que deberia colocar la camara para que quedase el punto nodal bién centrado. Si soys nuevos en este blog, buscar en post anteriores el tema del punto nodal. Yo lo dejé a la misma distancia d3 del anillo de enfoque del Nikkor 18+200 acoplado al cuerpo.



Ya tenia tres medidas claras, y un diseño del balancín.


Siguiendo en la soportación del balancín sólo faltaba determinar la altura del eje de rotación, cuanto más arriba mejor, pero eso va en contra de optimizar el tamaño. Mediante los cartones iva proyectando como quedaria todo y calculando las posibilidades. Acabé determinando una altura del eje de rotación a unos 200 mm de la base con lo que conseguiria en el peor de los casos unos 45º de inclinación verticales que deberian servir para la mayoria de los proyectos fotográficos.

Ahora queria hacer que basculara todo al estilo balancín desde el eje de rotación vertical, por lo que tenia claro que la anchura de la soportación del balancín sería algun centimetro mas de ancho, por lo que ya tendria otra cota: el ancho teorico del robot, y ya puestos si el robot tiene que girar, esta cota también deberia ser el largo del mismo. Lo friki-ideal sería una caja redonda para ganar unos grados, pero a dia de hoy aun no existen pantallas del tipo oLed que pudiera acoplar en el Arduino, por lo que pasemos a la opción basica: caja cuadrada!

Llegado este punto empecé a buscar cajas metalicas...que odisea! en las tiendas de electrónica de mi ciudad no me convencian las que tenian. De vuelta a casa y a googlear hasta encontrar cajas eléctricas por lo que luego se me pasó por la cabeza en ir a una tienda de material eléctrico pensando encontrar una caja de mi gusto cuando faltaban pocos minutos para que cerraran la misma un sábado al mediodia. Después de plantear mis exigencias al vendedor y enseñarme varios catalogos (dónde este un catalogo en papel que se quiten los buscadores de internet!) me dijo "calla...tengo el catalogo de RS!" , este me suena muuucho. Alli busquemos la caja y me apunté la referencia para luego buscarla ya filtrada en su web, y así es como elejí la caja.

En la misma web de RS-amidata pude descargar los planos mecánicos, parecia bonita, de fundición de aluminio por lo que seria fácil de mecanizar. La busco y la encuentro por eBay, ya sólo faltaba esperar. Seria una Hammond 1590HFB (en la siguiente foto aparece un poco "distorsionada" ya que es cuadrada en dos de sus ejes).


Como que me habia lanzado , también adquirí gran parte del material restante y tras unos dias de "paro"  esperando la caja fuí mecanizando el balancín.

Una vez llegó la caja y los rodamientos de bolas ya podia empezar a agujerear. Para hacer estos agujeros utilicé una broca piramidal adquirida también en eBay.


Con estas brocas es dificil provocar enganchadas , cosa que con las brocas típicas cuando hacemos agujeros en planchas con poco espesor es muy fácil que se nos enganchen los labios de esta y nos provoque unos agujeros que no sean redondos (y cuando lo haces encima del marmol de la cocina aun mas!).

Primero llegaron los rodamientos de bola, muy utiles para no cargar el peso y fricción de todo el mecanismo sobre el motor. Tras marcar el centro de la caja y realizar una circunferencia con centro a este punto ya podia empezar a agujererar la caja para colocar a estos.


En la siguiente imagen se observa el "colador" , la caja con su infinidad de agujeros.


Luego llegó un motor que tenia que servir para hacer la rotación horizontal pero tras agujerear la caja y empezar a hacer pruebas me di cuenta que no tendria fuerza suficiente, por lo que deberia pasar a buscar otro motor...problemón !


Los motores NEMA-17 suelen medir unos 42 mm de alto, y la caja tiene una altura de unos 60 mm. Habia espacio suficiente para alojarlo, lástima que yo queria un motor con reductora. Despues de buscar en stepperonline y varias webs semejantes me encontré que ninguno de ellospodia alojarlos por su altura, hasta que un dia acabé encontrando unos que me entrarian (ver el post anterior de la lista de la compra).

Luego llegó la pantalla...más bricolaje! esta vez con la caladora y con la ayuda de mi madre! La pantalla hacia la misma altura que el interior de la caja. Poco a poco , agujereando todo y quedando bastante compacto.

Llegados este punto tuve que cargarme con un "disco" (la mola) el nervio central que hay en la mitad de cada costado en su interior ya que molestaba a la hora de colocar la pantalla . Por cierto , la pantalla hace la misma altura que el interior de la caja. Hay que calcular bien para que nos quede bien centrada.


En el lateral un par de agujeros mas para colocar los conectores tipo DB-9 (los míticos del puerto série).


Luego fué hora de fijar el resto de componentes electronicos , y de hacer un agujero para que saliera la cabeza del Joystick, este lo desmonté de un nunchuck de la wii que no utilizaba, al fín y al cabo es un doble potenciometro de 10Kohms.


Otro agujero mas para el pulsador , y otros mas y la base-caja ya estaba mecanicamente lista.


Ya sólo quedaba fijar los componentes electrónicos.


Mecanicamente sólo faltaba hacer la soportación del balancín que seria igual de ancho que la caja , creo que viendo alguna imagen será más fácil que no explicarlo.



En el balancín , al igual que el soporte, tienen unos agujeros de 8mm. que es realmente el eje de rotación. En el lado del motor, hay un tornillo de M8 fijado al balancín bien estampado de manera que si en el extremo del tornillo lo movemos, también levantamos el balancín. En el otro lado del balancín el eje es un simple tornillo pasante que hace la funcion de guia. Lo ideal seria poner unos rodamientos en los ejes del soporte de balancín pero por temas de medidas acabé montando unos pasamuros de fontaneria de latón asi ganaba rigídez y precisión. Al ser de latón se pueden gastar con facilidad debido al rozamiento, pero creo que va a durar una buena temporada, y cuando den problemas, se va a por otros a la tienda de fontaneria!

Una vez tenia la soportación del balancín realizada y tras sus dobles a 90º y haber pasado la caladora para "regatear" algo de peso y darle un poco de forma más bonita fué la hora de adaptar el otro motor encargado del movimiento vertical del balancín. Para realizar la transmisión utilizaría una correa tipo GT2 (las que usan en las impresoras Prusa 3-D) , y unas poleas de 36 dientes serán las encargadas de transmitir el movimiento. Como que los ejes del motor son de 8mm al igual que el eje de rotación vertical (tornillos de M8) , las poleas tienen que ser con un diametro interior de 8.


La correa venia abierta, con una longitud de 2 metros y yo apenas necesitaba 300mm. Siempre hay la opción de comprarla cerrada y evitarnos algun problema, pero salia mucho mas cara. Yo opté por recortarla y cerrarla mediante el amigo loctite invertiendo un trozo de correa de unos 5 cms. De momento aguanta...

Finalmente habia que montar un soporte para fijar el soporte del balancín al eje del motor horizontal. Este puede ser un "redondo" al que deberiamos montar un sistema roscado a ser preferible que atacara sobre el eje del motor.


Una vez llegado a este punto, ya sólo faltaba soldar y programar, pero para ello intentaré otro dia realizar el esquema simple...eso sí , me repito: esto no es un tutorial paso a paso, podeis "copiar" pero el ingenio de cada uno hará que quede y funcione vuestro invento mejor o más bonito.


DIY !





lunes, 3 de marzo de 2014

Robot panorámico XS (la lista de la compra)

Muchas veces me da pereza transportar mi robot panorámico hasta según que paraje posiblemente porque junto a el hay que transportar las baterías, la cámara, el objetivo y un sinfín de cosas mas. Tampoco creo necesario contratar un sherpa!

A raíz de aquí me empecé a plantear la construcción de un robot más pequeño y ligero con la finalidad de poder realizar fotografías panorámicas automáticamente utilizando un zoom estandard , en mi caso la intención inicial es usar un Nikkor 18-200mm unido a la Nikon D5200. Como que no tiene nada que ver con el otro objetivo que utilizo para hacer Gigapan's (el Sigma 150-500 + teleconversor 1.4x), acaba produciéndose que el numero de fotos para realizar la toma se reduce considerablemente, el peso del objetivo y finalmente la resolución conseguida será muy inferior pero no debería tener desperdicio frente a una panorámica normal. De aqui viene su etiqueta XS.

Para ello y basándome en mis anteriores experiencias empecé con unos bocetos para ir preparando el tema. Hoy de momento dejo la lista de la compra por si alguien se anima a fabricarse uno. Deciros que no sale "regalao" y si contamos las horas dedicadas más vale que os compréis uno nuevo. Si sois amantes de la electrónica , arduino y un poco manitas os lo podréis hacer con algunas pequeñas dificultades.

Antes de comprar nada , mirad la evolución de todo el proyecto en siguientes posts para ver si estais capacitados para realizarlo sin tener grandes dificultades.  

* Plancha de 1-1.5 mm : Aproximadamente necesitamos dos retales cuya area será de aproximadamente 800 x 200 mm. Lo más complicado es doblarla.

* Caja metálica . Yo he utilizado la Hammond 1590FBK que es la misma de la foto pero pintada de negro . Es de fundición de aluminio por lo que es fácil trabajar con ella. Os dejo un plano muy útil en formato PDF. Creo recordar que en RS-amidata estaba disponible , aunque yo la acabé comprando por eBay a Italia por unos 30 euros.

* Dos motores tipo NEMA-17 con reductor. Cuidadín que me costó mucho dar con ellos! El problema es la altura de la caja que hay que buscar un motor que sea bajito, a mi me sobran pocos milímetros! Yo utilicé unos Kysan 1040222 comprados por eBay . Os dejo el link actual. Otra opción que podéis hacer es buscarlos a través del fabricante en la siguiente web de Kysan. También el plano del motor en PDF. Si soys nuevos comprando este tipo de motores os recomiendo que leais otros posts o otras webs  para que luego no tengais problemas de voltajes...Comprado en eBay por unos 50 euros cada motor.


* Un arduino MEGA, con el UNO nos quedamos justos de salidas y de memoria. El programa actual me ocupa unos 26 kb, y el UNO soporta unos 32 kb. En cuanto a salidas, necesitamos al menos 8 salidas hacia los drivers de motores y como mínimo una para el disparo remoto de la camara. También podemos utilizar un ChipKit , pero luego tendremos que tener en cuenta que las salidas son a 3.3 v en vez de los 5v. proporcionados por el Arduino clásico. Comprado un clon por menos de 15 euros en eBay.


* Una pantalla LCD i2c de 20x4 caracteres que entra justa en la altura de la caja. Cuanto más grande...más información que podremos mostrar. Si no la teneis tipo i2c necesitareis más salidas libres en vuestro Arduino. Unos 11 euros en eBay.



* Dos drivers L298N , en eBay tienen un coste de unos 3 euros la unidad si vienen de Oriente.

* Step down con entrada 12/24v y salida 5v 10A , otros 5 euros en eBay.


* Dos metros de correa tipo GT2 y dos poleas de 36 dientes por menos de 14 euros en RepRap. Son las que usa la gente para hacer sus impresoras tipo Prusa-3D.



* Bolas rodamiento , que aproximadamente salen a 1 euro el rodamiento sobre el cual se deslizará la mesa sin hacer sufrir el motor y evitando de poner unos cojinetes/rodamientos enormes. Como el resto...comprado en eBay, para variar! Otros 10 euros menos en nuestro bolsillo.



Y luego un sinfín de pequeño material como pilaretes, tornillos de M3. un pulsador, un "joystick" sacado de un nunchuck de una wii...

Ya llevamos unos doscientos euros invertidos soló en eBay y un mes de espera en recibir estos componentes fotografiados.

Como herramientas : un taladro, la caladora, juego de brocas (yo utilizo unas piramidales que van muy bién con el aluminio de la caja para hacer agujeros grandes). Con la plancha ya es más peligroso, os lo digo por experiencia.

También necesitariamos un buen lugar para hacer bricolage, yo lo hago como puedo, pero ojalá tuviera un sitio como Dios manda y no como muchas veces agujereando en el suelo de la cocina...jiji.

En siguientes posts lo dedicaré a explicar pequeños detalles de la construcción mecánica y luego la parte de electrónica, pero en ningun caso será un tutorial paso a paso.

Como dijo alguien: "Si tu sueñas que puedes hacerlo, es que tu puedes". 

DIY ! Do-It-Yourselve!