domingo, 24 de noviembre de 2013

Slide dolly - electrónica y arduino

Buenas tardes! O tarde de perros en la calle por lo que aprovecho para postear un poco.

Ahora toca la parte de electrónica y el programa de Arduino. La electrónica es muy similar a la utilizada en el robot panorámico, sólo que esta vez utilizo un Arduino Uno en vez del ChipKit, y que ahora sólo hay un motor que mover, por lo que sólo hay un driver L298N. Esta vez no incluyo ningún sensor de temperatura LM35 y todo y que no aparece en el siguiente esquema, hay un ventilador pequeñito de 12v que funciona constantemente para refrigerar el driver del motor NEMA-17. Otra pequeña diferencia es el tele, esta vez alimentado a 5 v y finalmente el tema LCD. En la anterior aplicación usé una shield LCD con pulsadores incorporados , mientras que esta vez usé un LCD I2C con el cual se gana en "disponibilidad de salidas" del Arduino. El I2C es un protocolo de comunicaciones que usa 4 hilos (2 para alimentar el dispositivos y 2 de comunicación que van a los pins SDA y SCL). Si tubiéramos otro dispositivo I2C lo deberíamos conectar en paralelo a los 4 cables sin "gastar" más señales de Arduino. Cada dispositivo tiene una "dirección" que deberemos usar en nuestro programa para definir y que todo funcione. Si no sabemos que dirección deberemos usar programas tipo I2C-Scanner  . Finalmente use 3 pulsadores para seleccionar en los diferentes menús.  Todo ello alimentado por una bateria de 12v y un regulador tipo StepDown de 12 a 5 v.



Id siguiendo los colores, que aunque parezca un lio es muy fácil !

Y ahora el programa de Arduino:

/*
 Dolly lapse
 by XavierGP

 */

Incluimos las librerías Wire, Stepper y LiquidCrystal_I2C

// define the pins used
#include <Wire.h>  // Comes with Arduino IDE
#include <Stepper.h>        // incluimos la Stepper library
#include <LiquidCrystal_I2C.h>

Definimos el numero de pasos del motor, en mi caso es de 200 pasos cada vuelta.

#define motorStepsX 200     // Pasos motor X 360º/1.8º


Definimos los pins digitales de entradas del Arduino, en mi caso utilizo los 5, 6 y 7 que corresponden a cada uno de los 3 pulsadores

left=7;
int right=5;
int enter=6;

Definimos las variables:
* Espacio = recorrido inicial de la dolly (luego en el programa ejecutable la podemos modificar)
* minutos_secuencia = el tiempo que va a durar el time-lapse 
* minimo_minutos_secuencia = es el tiempo mínimo que puede durar el time-lapse, está calculado en base al tiempo necesario en realizar las fotos y todos los movimientos.
* segundos_secuencia = es el tiempo de espera que hay desde que acaba de hacer un movimiento hasta que realiza una toma.(inicialmente está definida como una simple multiplicación en base a los minutos)
* paso = número de pasos a realizar el motor entre diferentes tomas.
* foto = es el pin de salida que nos da señal para hacer una foto (de aqui al relé y luego del rele lo que queráis)
* shot = número de fotos del time-lapse (luego en el programa ejecutable la podemos modificar)
* a = es un bit que utilizo como memoria en los bucles internos del programa.
* porcentaje = es el % del proyecto realizado.

int espacio=500;        // recorrido dolly 
int minutos_secuencia;   // Variable pre definida
int minimo_minutos_secuencia;
float segundos_secuencia=minutos_secuencia*60;
int paso;
float posicion_actual;
int foto=12;  // salida remoto camara
int shot=100;    //variable pre definida
float tiempo_reloj;
int a=0; // bucles
float porcentaje;

Definimos los pins de salida del arduino hacia el driver L298N, en este caso serán el 8,9,10 y 11

Stepper myStepperX(motorStepsX, 8,9,10,11); // libreria motor X   3,13,12,11

Definimos la dirección I2C del la LCD

 LiquidCrystal_I2C lcd(0x20, 16,2);  // Set the LCD I2C address


Realizamos el set-up para que entienda el Arduino como vamos a utilizar los pins, en este caso left (pin 7) , right (pin 5) , enter (pin 6) son entradas y foto (12) es una salida. 

void setup() {
pinMode(left, INPUT);
pinMode(right, INPUT);
pinMode(enter, INPUT);
pinMode(foto, OUTPUT);


Inicializamos en LCD

lcd.init();

Definimos la velocidad del motor 

myStepperX.setSpeed(12); // definimos velocidad impulsos motor

Encendemos iluminación de la pantalla LCD

lcd.backlight();

Mensaje de Bienvenida !

lcd.setCursor(0,0); 
lcd.print("Hello, XavierGP!");
delay (1000);
lcd.clear();

Insertamos la primera parte del "gif casero" (cámara fotos)

logo0();
logo1();

Y continuamos con el resto de "presentación de bienvenida":

lcd.setCursor(2,0);
lcd.print("Slide");
delay(700);
logo2();
lcd.setCursor(0,1);
lcd.print("dolly v2.0");
delay(700);
logo1();
delay (2000);
lcd.clear();

Llamamos a la subrutina define_espacio (dónde definiremos la longitud del movimiento de nuestra dolly)

define_espacio();

Llamamos a la subrutina define_fotos (dónde definiremos el número de fotos que queremos realizar)

define_fotos();

En base al numero de fotos y distancia a recorrer se gestiona el tiempo mínimo necesario para realizar las tomas. 

minutos_secuencia=(espacio/125)+1+(0.02*shot);  // original minutos_secuencia=(espacio/410)+1+(0.02*shot)
minimo_minutos_secuencia=minutos_secuencia;
segundos_secuencia=minutos_secuencia*60;

Una vez tenemos definido el tiempo mínimo, lo podremos incrementar en la subrutina define_tiempo

define_tiempo();


Ahora es el tiempo de establecer el tiempo entre fotos (desde que acabamos un movimiento y hasta que realizamos una nueva foto) y también establecemos el número de pasos a realizar por el motor entre las diferentes capturas

lcd.clear();
segundos_secuencia=(minutos_secuencia*60)/(shot+1);
paso=(12800/2000)*(espacio)/(shot);  //originalment /2100

segundos_secuencia=(segundos_secuencia-1-(paso*0.075)); //original *0.024

Nos preparamos para empezar. Sólo hay que pulsar sobre ENTER para que empieze la secuencia

lcd.print("[ENTER] = Start");
lcd.setCursor(0,1);

Hacemos un barrido de puntos (mariconadas...jeje)

for (int k=0 ; k<16; k++){
  lcd.print(".");
  delay(100);
}

Provocamos una pausa mientras a=0 (hasta que pulsemos ENTER), momento en que a será 1 y saldremos del bucle.

a=0; // provoquem pausa
while(a==0){
if(digitalRead(enter)==HIGH){
a=1;
}
}

Empezamos la sequencia!

lcd.clear();

Definimos el numero de veces a realizar el bucle (numero de fotos menos 1)

for(int i = 1; i< shot; i++){
    
Mostramos un gif de camara de fotos, y mostramos la información en la pantalla

    logo0();
    logo1();

Comprobamos cuantas fotos hay tomadas , si es inferior a 100 ponemos un espacio en blanco, y si es inferior a 10, ponemos dos espacios en blanco, asi queda todo siempre ordenado!
    
    lcd.setCursor(1,0);
    if(i<=99){
    lcd.print(" ");
    }
    if(i<=9){
    lcd.print(" ");
    }
    
    lcd.print(i);
    lcd.print("s");
    
    Mostramos el porcentaje de ejecución del proyecto
    
    lcd.setCursor(7,0);
    lcd.print(i*100/shot);
    lcd.print("%");
  
    En el momento de realizar una captura nos aparece la palabra FOTO! en el LCD, cambia el gif de la cámara como si cerrara el diafragma y activamos el relé durante 0.3 segundos. Acto seguido, desactivamos el relé del comando remoto y cambiamos el gif.

 lcd.setCursor(0,1);
 lcd.print("FOTO!"); 
  digitalWrite(foto, HIGH);
  logo2();
  delay(300);
  digitalWrite(foto, LOW);
  delay(300);
  logo1();

 Ahora es hora de mover la dolly, en la LCD nos muestra la palabra MOVE! y se desplaza el carro. Una vez llegado a destino y parado, desaparece la palabra MOVE! y modificamos la variable  del valor de la posición del carro.

 lcd.setCursor(0,1);
 lcd.print("MOVE!");
 myStepperX.step(paso);
 lcd.setCursor(0,1);
 lcd.print("     ");
  posicion_actual=(posicion_actual+(paso*0.165));
  
Mostramos en la pantalla el tiempo restante hasta la siguiente foto en unidades de décima de segundo, junto a los valores de posiciones

 for (int j=0; j<(segundos_secuencia*10); j++){
    
    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,1);

Insertamos espacios en el valor de posicionamiento del carro (al igual que hicimos anteriormente con el valor de numero de fotos)

    if(posicion_actual<1000){
      lcd.print(" ");
    }
    if(posicion_actual<100){
      lcd.print(" ");
    }
    lcd.print(posicion_actual,0);
    lcd.setCursor(10,1);
    lcd.print("mm");
    
  }
}

Ahora es la hora de realizar la última toma y de mostrarnos el mensaje de MotionLapse Completo!

logo0();
logo1();
lcd.setCursor(0,1);
lcd.print("     ");
digitalWrite(foto, HIGH);
  logo2();
  delay(500);
  digitalWrite(foto, LOW);
  delay(100);
  logo1();
  lcd.setCursor(0,0);
  lcd.print("MotionLapse");
  lcd.setCursor(0,1);
  lcd.print(" completo!  ");
}

Aqui es dónde debería estar el programa para que fuera cíclico, pero como que no interesa, lo dejo en vacío, así una vez terminado todo el ciclo se queda todo "bloqueado" hasta que no quitemos tensión evitando ningún movimiento sorpresa.

void loop(){             // ********************* loop ***********
     
}

Subrutina dónde definimos el espacio a recorrer, con los pulsadores LEFT,RIGHT desplazaremos el punto final del carro (el inicial es dónde lo tenemos aparcado cuando damos tensión inicial al sistema). Finalmente validaremos con ENTER. Está "capado" a 2000 mm ! Al igual que en otras partes del programa se insertan espacios en blanco cada vez que pasamos de valores de miles a centenares o a décimas del valor.

void define_espacio(){
a=0;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Define  espacio"); 
lcd.setCursor(3,1);
if(espacio<1000){
  lcd.print(" ");
}
lcd.print(espacio);
lcd.setCursor(9,1);
lcd.print("mm.");
delay(200);
while(a==0){
if(digitalRead(enter)==HIGH){
a=1;
}
delay(150);
if(digitalRead(right)==HIGH){
if (espacio<2000){       // recorrido máximo, modificar esta variable si es necesario.
espacio=espacio+100;
lcd.setCursor(3,1);
if(espacio<1000){
  lcd.print(" ");
}
lcd.print(espacio);
lcd.setCursor(7,1);
lcd.print("  mm.  ");
}
}

if(digitalRead(left)==HIGH){
if (espacio>100){
espacio=espacio-100;
lcd.setCursor(3,1);
if(espacio<1000){
  lcd.print(" ");
}
lcd.print(espacio);
lcd.setCursor(7,1);
lcd.print("  mm.  ");
}
}
}

Subrutina de definición del tiempo de la secuencia total. Aqui al igual que en la subrutina anterior, podemos incrementar (existe el valor mínimo explicado anteriormente) o decrementar el tiempo necesario. Validaremos también con ENTER.

void define_tiempo(){
a=0;
lcd.clear();
lcd.setCursor(1,0);
lcd.print("Define  tiempo"); 
lcd.setCursor(5,1);
lcd.print(minutos_secuencia);
lcd.setCursor(8,1);
lcd.print("min.");
delay(200);
while(a==0){

if(digitalRead(enter)==HIGH){
a=1;
}
delay(150);
if(digitalRead(right)==HIGH){
if (minutos_secuencia<250){
minutos_secuencia=minutos_secuencia+1;
lcd.setCursor(4,1);
if(minutos_secuencia<100){
  lcd.print(" ");
}
lcd.print(minutos_secuencia);
lcd.print(" min.  ");
}
}

if(digitalRead(left)==HIGH){
if (minutos_secuencia>minimo_minutos_secuencia){
minutos_secuencia=minutos_secuencia-1;
lcd.setCursor(4,1);
if(minutos_secuencia<100){
  lcd.print(" ");
}
lcd.print(minutos_secuencia);
lcd.print(" min.  ");
}
}
}

Subrutina dónde definimos el número de tomas a realizar, más de lo mismo, la lógica utilizada es la misma que en las anteriores subrutinas.

void define_fotos(){
a=0;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Define num fotos"); 
lcd.setCursor(4,1);
lcd.print(shot);
lcd.setCursor(8,1);
lcd.print("fotos");
delay(200);
while(a==0){

if(digitalRead(enter)==HIGH){
a=1;
}
delay(150);
if(digitalRead(right)==HIGH){
if (shot<250){
shot=shot+1;
lcd.setCursor(4,1);
if (shot<100){
  lcd.print(" ");
}
lcd.print(shot);
lcd.setCursor(8,1);
lcd.print("fotos");
}
}

if(digitalRead(left)==HIGH){
if (shot>10){
shot=shot-1;
lcd.setCursor(4,1);
if (shot<100){
  lcd.print(" ");
}
lcd.print(shot);
lcd.setCursor(8,1);
lcd.print("fotos");
}
}
}

En las siguientes sub-rutinas void logoX() es dónde defino los caracteres especiales correspondiente al "gif casero" en que aparece una cámara fotográfica realizando foto. No tiene mucho misterio , cada una de las 7 lineas que hay en cada caracter está compuesta de 5 columnas. Si queremos que aparezca un pixel marcado deberemos poner un 1 de lo contrario un 0.

void logo0(){
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};
lcd.createChar(1, logo1a);
lcd.createChar(4, logo1d);
lcd.createChar(5, logo1e);
lcd.createChar(8, logo1h);
}
  
void logo1(){
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);
}

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);
}

Un pequeño video de como funciona el programa:



Sólo os faltan buscar las librerías que no tengáis por internet ! Y el programa entero (mejor para hacer un copiar/pegar no ? ):

/*
 Dolly lapse
 by XavierGP

 */

// define the pins used
#include <Wire.h>  // Comes with Arduino IDE
#include <Stepper.h>        // incluimos la Stepper library
#include <LiquidCrystal_I2C.h>
#define motorStepsX 200     // Pasos motor X 360º/1.8º
int left=7;
int right=5;
int enter=6;
int espacio=500;        // recorrido dolly 
int minutos_secuencia;   // Variable pre definida
int minimo_minutos_secuencia;
float segundos_secuencia=minutos_secuencia*60;
int paso;
float posicion_actual;
int foto=12;  // salida remoto camara
int shot=100;    //variable pre definida
float tiempo_reloj;
int a=0; // bucles
float porcentaje;

Stepper myStepperX(motorStepsX, 8,9,10,11); // libreria motor X   3,13,12,11
LiquidCrystal_I2C lcd(0x20, 16,2);  // Set the LCD I2C address
void setup() {
pinMode(left, INPUT);
pinMode(right, INPUT);
pinMode(enter, INPUT);
pinMode(foto, OUTPUT);

lcd.init();
myStepperX.setSpeed(12); // definimos velocidad impulsos motor
lcd.backlight();
lcd.setCursor(0,0); 
lcd.print("Hello, XavierGP!");
delay (1000);
lcd.clear();
logo0();
logo1();
lcd.setCursor(2,0);
lcd.print("Slide");
delay(700);
logo2();
lcd.setCursor(0,1);
lcd.print("dolly v2.0");
delay(700);
logo1();
delay (2000);
lcd.clear();


define_espacio();
define_fotos();
minutos_secuencia=(espacio/125)+1+(0.02*shot);  // original minutos_secuencia=(espacio/410)+1+(0.02*shot)
minimo_minutos_secuencia=minutos_secuencia;
segundos_secuencia=minutos_secuencia*60;

define_tiempo();

lcd.clear();
segundos_secuencia=(minutos_secuencia*60)/(shot+1);
paso=(12800/2000)*(espacio)/(shot);  //originalment /2100

segundos_secuencia=(segundos_secuencia-1-(paso*0.075)); //original *0.024

lcd.print("[ENTER] = Start");
lcd.setCursor(0,1);
for (int k=0 ; k<16; k++){
  lcd.print(".");
  delay(100);
}

a=0; // provoquem pausa
while(a==0){
if(digitalRead(enter)==HIGH){
a=1;
}
}
lcd.clear();
 for(int i = 1; i< shot; i++){
    
    logo0();
    logo1();
    lcd.setCursor(1,0);
    if(i<=99){
    lcd.print(" ");
    }
    if(i<=9){
    lcd.print(" ");
    }
    
    lcd.print(i);
    lcd.print("s");
    lcd.setCursor(7,0);
    lcd.print(i*100/shot);
    lcd.print("%");
  
 lcd.setCursor(0,1);
 lcd.print("FOTO!"); 
  digitalWrite(foto, HIGH);
  logo2();
  delay(300);
  digitalWrite(foto, LOW);
  delay(300);
  logo1();
 lcd.setCursor(0,1);
 lcd.print("MOVE!");
 myStepperX.step(paso);
 lcd.setCursor(0,1);
 lcd.print("     ");
  posicion_actual=(posicion_actual+(paso*0.165));
  
  for (int j=0; j<(segundos_secuencia*10); j++){
    
    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,1);
    if(posicion_actual<1000){
      lcd.print(" ");
    }
    if(posicion_actual<100){
      lcd.print(" ");
    }
    lcd.print(posicion_actual,0);
    lcd.setCursor(10,1);
    lcd.print("mm");
    
  }
  
}
logo0();
logo1();
lcd.setCursor(0,1);
lcd.print("     ");
digitalWrite(foto, HIGH);
  logo2();
  delay(500);
  digitalWrite(foto, LOW);
  delay(100);
  logo1();
  lcd.setCursor(0,0);
  lcd.print("MotionLapse");
  lcd.setCursor(0,1);
  lcd.print(" completo!  ");
}

void loop(){             // ********************* loop ***********
     
}

void define_espacio(){
a=0;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Define  espacio"); 
lcd.setCursor(3,1);
if(espacio<1000){
  lcd.print(" ");
}
lcd.print(espacio);
lcd.setCursor(9,1);
lcd.print("mm.");
delay(200);
while(a==0){
if(digitalRead(enter)==HIGH){
a=1;
}
delay(150);
if(digitalRead(right)==HIGH){
if (espacio<2000){
espacio=espacio+100;
lcd.setCursor(3,1);
if(espacio<1000){
  lcd.print(" ");
}
lcd.print(espacio);
lcd.setCursor(7,1);
lcd.print("  mm.  ");
}
}

if(digitalRead(left)==HIGH){
if (espacio>100){
espacio=espacio-100;
lcd.setCursor(3,1);
if(espacio<1000){
  lcd.print(" ");
}
lcd.print(espacio);
lcd.setCursor(7,1);
lcd.print("  mm.  ");
}
}
}

void define_tiempo(){
a=0;
lcd.clear();
lcd.setCursor(1,0);
lcd.print("Define  tiempo"); 
lcd.setCursor(5,1);
lcd.print(minutos_secuencia);
lcd.setCursor(8,1);
lcd.print("min.");
delay(200);
while(a==0){

if(digitalRead(enter)==HIGH){
a=1;
}
delay(150);
if(digitalRead(right)==HIGH){
if (minutos_secuencia<250){
minutos_secuencia=minutos_secuencia+1;
lcd.setCursor(4,1);
if(minutos_secuencia<100){
  lcd.print(" ");
}
lcd.print(minutos_secuencia);
lcd.print(" min.  ");
}
}

if(digitalRead(left)==HIGH){
if (minutos_secuencia>minimo_minutos_secuencia){
minutos_secuencia=minutos_secuencia-1;
lcd.setCursor(4,1);
if(minutos_secuencia<100){
  lcd.print(" ");
}
lcd.print(minutos_secuencia);
lcd.print(" min.  ");
}
}
}

void define_fotos(){
a=0;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Define num fotos"); 
lcd.setCursor(4,1);
lcd.print(shot);
lcd.setCursor(8,1);
lcd.print("fotos");
delay(200);
while(a==0){

if(digitalRead(enter)==HIGH){
a=1;
}
delay(150);
if(digitalRead(right)==HIGH){
if (shot<250){
shot=shot+1;
lcd.setCursor(4,1);
if (shot<100){
  lcd.print(" ");
}
lcd.print(shot);
lcd.setCursor(8,1);
lcd.print("fotos");
}
}

if(digitalRead(left)==HIGH){
if (shot>10){
shot=shot-1;
lcd.setCursor(4,1);
if (shot<100){
  lcd.print(" ");
}
lcd.print(shot);
lcd.setCursor(8,1);
lcd.print("fotos");
}
}
}

void logo0(){
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};
lcd.createChar(1, logo1a);
lcd.createChar(4, logo1d);
lcd.createChar(5, logo1e);
lcd.createChar(8, logo1h);
}
  
void logo1(){
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);
}

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);
}

Si tenéis dudas, ya sabeis, pedid que yo intentaré responder cuando pueda!
Saludos y DIY (Do IT Yourself) !





viernes, 1 de noviembre de 2013

Silde dolly para motion-lapses



Todo empezó viendo varios time-lapses por Internet, entre los de www.xatakafoto.com como los de www.luiscaldevilla.com que me entraron ganas de aparcar las gigapanes temporalmente para empezar con nuevos proyectos .

Algunas de mis exigencias con las gigapanes es que aparezcan unos cielos bonitos y si tiene que aparecer algo de vegetación que sea de color verde por lo que me limita a realizar estas en periodo primaveral. También suele ser una época en que la visibilidad es buena y eso hace más interesante en las fotografias en que aparecen detalles. Una vez llega el verano , los verdes se transforman en amarillo, los paisajes más áridos y encima hay que estar almenos 3 horas aguantando el sol, por lo que en pleno verano es un descarte fijo. En invierno los colores son muy fríos, las nieblas generan mala profundidad de campo y perdida de definición. A los time-lapses también le afectan las inclemencias meteorológicas pero al menos el ordenador no sufre tanto a la hora de renderizar. 

El objetivo final es realizar un time-lapse con movimiento (motion-lapse) en el que la cámara se desplaza lentamente mientras toma fotografías dándole un poco más de vida al time-lapse.

Tiempo atrás me fabrique una Dolly que era guiada mediante dos guías de perfíl, pero no me gustaba el movimiento pues era muy basto al no tener ningún tipo de reducción en el motor, tampoco podía ponerla en vertical.



Ahora la idea era hacer una slide Dolly con una sola barra ,reduciendo el peso, sobre la cual se deslizaria el carro empujado por un motor paso a paso y gestionado por Arduino. También queria que que pudiera trabajar tanto en horizontal como en vertical (al menos mecánicamente, si no puede el motor es otro cantar).

Mecánicamente empecé reciclando varios trozos de perfileria de aluminio de 30mm2. del tipo Bosch utilizados en anteriores proyectos. Este tipo de perfil tiene en su centro un agujero justo para roscar a M8 por lo que para unir varios tramos sólo necesitaba prisioneros largos, creo recordar que eran de M8x80 los cuales con fijaba con loctite en sólo en un extremo. Así de fácil podría hacer una Dolly larga y fácilmente desmontable y transportable. La idea inicial era hacer una de 3 metros de largo, por lo que uní hasta 4 tramos. Luego le puse unas patas.




Ahora quedaban dos elementos clave: El carro deslizable dónde se fijaria la cámara y el soporte del motor con el tambor enrrollable.

Aprovechando las ranuras del perfil coloqué dos tornillo allen de cabeza redonda de M8 en el interior de la ranura, asi conseguia que la plancha de soportación no pudiera salirse y quedara continuamente guiada. 


Para reducir la fricción de la plancha con la guia usé un policarbonato y fijé los tornillos por la parte superior de la plancha. Aqui empezó lo que más tarde seria uno de los tantos problemas: Los tornillos no podia fijarlos fuertes si no queria provocar un sandwitch entre el perfil, el metacrilato y la plancha de aluminio. Al tener que dejar cierta holgura , en un futuro me provocaria un juego inaceptable de todo el sistema, que en ese momento no le di importancia.


Encima de la plancha-colador (a dia de hoy tiene 27 agujeros varios después de reutilizar esta plancha para varias cosas) , coloqué una rotula de bola Manfrotto 496RC2 para poder fijar cualquier camara de manera rápida.

Seguí montando la tercera parte mecánica: El motor de arrastre !Una pletina de aluminio con tres agujeros de diámetro 6 para fijar al perfil, luego otros agujeros para fijar el motor NEMA-17 el cual tiene acoplado un engranaje metálico de 12 dientes de módulo 1.




Este engranaje actua directamente sobre otro engranaje de 60 dientes dónde está fijado el "tambor" que será el encargado de enrrollar el cable acerado de 2.5 mm. Todo este conjunto esta soportado por un tornillo de M12 y gira locamente, eso quiere decir con otras palabras que hay que fijar el tornillo a la pletina de aluminio por lo que deberemos hacer un agujero y luego roscarlo a M12. ¿Por que 12 ? Porque era diametro del agujero original del engranaje.

El cable de acero queda fijado por unos prisioneros , aunque luego para asegurarme lo acabé re-fijandolo con una brida metálica tipo Mikalor.





Con esto ya podia enrrollar el cable de acero pero... ¿como aseguro que no se monten las diferentes vueltas del cable de acero una encima de otra ? Mi compañero de faena me dio con la solución: habia que hacer mover otro eje que se desplazara lateralmente y después de unos calculos se llegó a la conclusión que habia que utilizar un tornillo de M8 (paso de 1.25 mm) y que a cada vuelta del tambor este debería dar dos, así conseguiria que a cada vuelta del tambor se desplazara el eje centrador 2.5 mm, justo lo que hacia el cable de diametro. Entonces si el tambor tenia un engranaje de 60 dientes aqui necesitaba uno de 30z siempre respectando el paso de módulo 1.




En este tornillo de M8 coloqué una tuerca sobre la que hay un pequeño tubo soldado que cumple la función de guiador.

En la versión original estaban los tres ejes centrados (motor, tambor y guiador). Las distancias entre ellos son un poco a ojo pues sabia que si no dejaba holgura entre engranajes tendria problemas. 

Ya tenia la parte mecánica clara (en principio): El conjunto tractor situado en la parte inferior de la guía enrollaría el cable de acero en el tambor a medida de que el motor paso a paso giraria. Este cable pasaria por una polea en la parte opuesta a la guía para pasar a la parte superior. 



Una vez allí pasaba por entre medio de tres "tensores" que eran tres tornillos que tenían la función de frenar un poco el cable para que quedara siempre tenso así lograría unos avances iguales. En el camino de vuelta del cable habría el carro que se deslizaria por encima de la guía.


Un video que vale más que mil imágenes:





Realicé pruebas en casa (sin tomar capturas) y parecia que funcionaria por lo que decidí sacar el invento a la calle y esperar obtener un buen resultado.

Pues no ! Game Over ! El cable hacia un gesto muy forzado cuando pasaba por el guiador  previo al tambor . También había veces que el cable se tensaba quedaba gripado por la polea y dejaba de moverse el invento. Y cuando no fallaba de esto , el conjunto de la cámara y objetivo (Una Nikon d5200 + Sigma 17-50 f2.8 o Sigma 8-16) al quedar desequilibrado de peso forzaba tanto al metacrilato del sandwitch y esto unido al "juego" que habia entre tornillo de guia de perfíl provocaba que el conjunto mecánicamente no funcionara.

Entonces tocó hacer un reestiling total y habia que modificar casi todo! Empecé sustituyendo los perfiles reciclados por una sola barra-guía nueva. Esta la podéis comprar en RS-amidata, aunque yo la compré directamente al importador para España, una empresa situada en el pueblecito de Olius llamada Promakfil a un precio de 9.90 euros el metro lineal. Me atendieron muy bien y me cortaron la longitud necesaria. Previamente tuve que calcular que entrara dentro del coche ya que a partir de ahora el invento ya no seria desmontable...


Luego eliminé la polea final , y reducí a la mitad el cable de acero ( no era necessario ir por debajo para luego volver por arriba!) asi reducia cable "inútil" y evitaria que se gripara este en la polea, resultado satisfactório. Modifiqué la posición del eje del centrador para que quedara en linea con un extremo del tambor , asi el cable llegaria recto al bobinado. 

Ahora quedaba lo complicado: eliminar el juego que tenia todo el "carro". Después de ver videos en youtube decidí montar unas ruedas de patinete en linea. Fuí al Decathlon esperando encontrar este accesorio y asi fue. Elejí las mas baratas y modifique la plancha para que se apoyaran dos ruedas a cada  lateral de la guia. Aqui también tuve de realizar múltiples agujeros hasta encontrar el punto que las ruedas hicieran su función correctamente sin sufrir mucho pero que garantizara la posición. Todo parecia que debía ir bién pero de nuevo otro chasco ! 

El carro volvia a cabecear ! Después de analizar , el problema provenia de los rodamientos "made in china" de mala calidad. Fué cuestion de sustituirlos por unos de fabricacion europea y las ruedas ya quedaron centradas.


Era hora de probar el invento dentro de casa, primero plano , y luego a 45 º con camara incluida obteniendo un resultante bastante satisfactorio. 



Ahora sólo faltaba sacarlo a la calle para probarlo de nuevo...round 2!



Aprendí una cosa difícil de explicar: Si queréis hacer motion-lapses poned un elemento en primer plano para que se note el movimiento de la dolly! Si esta se mueve 2 metros lateralmente y estamos fotografiando una cosa que esta a muchos metros de distancia (en este caso el Poble Vell de Súria) situado a 200 metros , la única diferencia (excepto las nubes) entre la primera y la última captura es que la imagen sólo se desplaza dos metros (dicho de otra manera ...si hay 40 casas, se desplaza el ancho de una ventana!). En cambio si ponemos algo a un metro de distancia si que notaremos el movimiento respecto a este objeto y ya nos dará el efecto deseado. Si me aburro ya os haré un video para que lo entendáis...


La cosa funcionó medio bién...se quedo el mecanismo gripado unas veces debido a que antes de salir a la calle estuve desmontando el conjunto del motor y provoqué pequeños desperfectos en los engranajes que con la única lima de casa pude arreglar y aquí otro video de la última prueba doméstica.




Soy consciente de que la falta de grasa en los engranajes provoca ruidos y pequeños gripajes, pero a dia de hoy prefiero asumirlos. Si pongo grasa se que acabaré ensuciando el coche cuando lo transporte y poco a poco todo queda "pringado".


Si a dia de hoy tuviera que fabricar otra dolly daría un paso atrás y volvería a hacer la misma pero con dos guías y para asegurar mucho más la estabilidad del invento. 


Obviamente en esta explicación falta la parte electrónica / Arduino que os la explicaré otro dia...