Mostrando entradas con la etiqueta L298N. Mostrar todas las entradas
Mostrando entradas con la etiqueta L298N. Mostrar todas las entradas

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!


miércoles, 29 de enero de 2014

Pruebas con motores Paso a paso (I)

Todo y que este blog no va encaminado a ser un tutorial de arduino , he considerado , que al hablar de robots panorámicos creados mediante este hardware y ante las varias dudas que se generan, he preferido crear algun post para que la gente que quiera experimentar pueda beneficiarse de mis experiencias.

Para ello, y antes de nada explicar que un motor paso a paso (stepper) viene a ser como un motor de corriente continua pero en vez de con un solo bobinado tiene dos. Estos estan distribuidos alternativamente de tal manera que provoca que se generen varios sub-bobinados o electroimanes con lo que conseguimos un "mini-control" de posicionamiento del mismo , obviamente si no hay un encoder no podemos asegurar realmente la posición del mismo.

En la WikiPedia podeis encontrar muchos artículos sobre el funcionamiento del mismo.

Cuando adquirimos un motor Paso a Paso (de aqui en adelante PAP), hay varias características técnicas  a tener en cuenta, tales como el voltaje de funcionamiento, el numero de pulsos por vuelta (yo utilizo normalmente unos que tienen una resolución de 1.8º por impulso, es decir 200 impulsos cada vuelta entera) y también otra característica es la fuerza del mismo (torque) que será para que nos entendamos la fuerza máxima que nos puede ejercer el motor.

Como que tampoco es cuestión de escribir un máster, yo os comentaré los que utilizo, unos NEMA-17 que funcionan a 5VDC. Dentro del mundo "NEMA" existen varios tamaños 11,14,17,23... y varios voltajes. Hay que tener cuidado a la hora de adquirirlos pues almenos en los que yo tengo en ninguno de ellos viene identificado ninguna de esas tres características.


Al tratarse de pequeñas bobinas que ejercen la función de electroimanes la tensión de alimentación no debería ser critica, y siguiendo la lógica de un electroimán: a más voltaje, más fuerza = más torque, pero eso tampoco quiere decir que le podamos aplicar 25kV a los bobinados por lo que deberemos leer su datasheet, de lo contrario dañariamos el aislamiento del bobinado y acabaría este en la chatarra despues de las señales de humo. Si no lo tenemos es tan fácil como pedirle las características al comprador o buscar desde Google: "datasheet Nema-17" por ejemplo.


En todo motor PAP hay como mínimo 4 cables que corresponden a 2 bobinados, y lo que necesitamos es ir generando una serie de impulsos para accionar el motor.  Los impulsos los generaremos mediante un  hardware tipo Arduino (el ChipKit también funciona) pero este sólo es capaz de generar los impulsos pero no da "potencia" al motor, por lo que necesitaremos un driver.

Llegados a este punto tenemos tres elementos necesarios para mover el motor:
* Generador de pulsos (Arduino)
* Conversor de pulsos a potencia entregada (driver)
* Motor PAP

* Empezamos por el motor:
Hay que determinar los dos bobinados entre los 4 cables: Podemos usar o bién un multimetro (también llamado vulgarmente "téster") en modo resistencia o hacerlo sin él. Si lo hacemos con un tester deberemos elejir un cable y conectar hacia el téster, mientras que con la otra punta del tester buscaremos entre los otros 3 cables cual es el que da algo de resistencia, y sólo puede ser uno. Ya tenemos un bobinado! Ahora nos quedan dos cables sueltos que deberían corresponder al otro bobinado: miraremos si realmente también tenemos la misma resistencia entre ellos. En mi caso un bobinado corresponde a los cables rojo-azul y el otro al negro-verde.

Otra manera de hacerlo es sin el multímetro. Cogemos un cable al azar y lo unimos por la punta conductora con otro cable mientras intentamos girar el motor: Si cuando unimos los cables el motor se frena quiere decir que ambos cables pertenecen al mismo bobeando, sino se frena el motor hay que ir probando con el resto hasta conseguir determinar los dos bobinados.

Hay motores que tienen 6 cables, en este caso es lo mismo que en el de 4 cables pero los dos restantes van conectados a mediobobinado por lo que al menos con los drivers que yo utilizo no sirven de nada. Para determinar cuales cables deberiamos utilizar hay que usar la opción del múltimetro , pero con la diferencia que nos encontraremos con dos conductores que marquen resistencia, uno de ellos será la mitad del otro, es decir si entre un cable y otro marca un ohmio y con otro marca dos, deberemos elejir los cables que marcan dos ohmios y prescindir de los cables de bobinado intermedio.


También hay con 5 cables, que viene a ser como el de 6 pero con la diferencia que tienen los dos cables que vienen del mediobobinado unidos.



Como curiosidad: Si unimos los 4 conductores entre si el motor se quedará aun más frenado. Según me comentó Esteve si cogiéramos una llave dinamométrica e intentáramos hacer mover el eje del motor podríamos encontrar cual es el torque real de nuestro motor.

* El driver:
La función del driver es de convertir los pulsos provenientes en nuestro caso del Arduino a tensiones con más "potencia".
En principio el Arduino genera pulsos de unos 5VDC pero sin intensidad, por lo que si aplicaramos directamente el motor (siempre y cuando funcionara este a 5 voltios!) no se movería.

El driver viene a ser como unos transistores que activan / desactivan la potencia que va hacia los motores. Es como si fueran los interruptores de la luz activados remotamente. Por estos "interruptores" pasa la energia necesaria para hacer mover el motor aunque este funcione a 24voltios.


Cuando arduino genera un impulso, acaba excitando el transistor mediante un voltaje de 5 voltios y este transistor cierra el contacto eléctrico dejando pasar por su contacto el voltaje necesario para mover el motor.

La gracia de todo esto es que en realidad son 4 circuitos y que tiene una alta velocidad de conmutación: para que nos entendamos si un motor es capaz de girar una vuelta por segundo en un motor de 200 iml/vuelta significa que ha conmutado doscientas veces los transistores en un segundo. Obviamente esto es imposible hacerlo mediante interruptores o relés pues el tiempo de accionamiento lo superaría con creces.

Yo hasta el momento he estado utilizando drivers con el semiconductor L298N . Si buscais por eBay encontrareis varios modelos ya a punto de conectar y probar. Obviamente el L298N es el componente principal pero también son de gran importancia los diodos y el resto de componentes.

El tipo de driver que utilizo tiene varias conexiones y jumps , pero no tienen ningún misterio a la hora de conectarlo.

Mirandolo de frente y con el disipador de aluminio en la parte superior encontramos unos borneros dobles en los laterales . A cada uno de estos borneros deberemos conectar los cables de los dos bobinados que encontremos anteriormente. Tanto dá si lo conectamos en el lado derecho como en el izquierdo y tanto da si cruzamos los cables entre si (del mismo par) a la hora de conectarlos a cada uno de los dos borneros, lo único e importante es que cada par de borneros corresponda con cada par de bobinados. El único problema es que más tarde nos gire en un sentido y nosotros queriamos que girara en otro, pero seria tan fácil como cambiarlo desde software (en el programa de arduino), o bien intercambiando los cables que van desde arduino al driver (por ejemplo intercambiando a la entrada del driver o a la salida del arduino -sólo en un sitio- los cables rosa y gris o el otro par)  o bien intercambiando los cables de un bobinado a la salida del driver (es decir, en nuestro caso intercambiando el cable rojo por el azul o el verde por el negro).

Luego tenemos 4 cables que salen de las salidas del Arduino, en este caso a las salidas 8, 9, 10 y 11 que deberemos conectarlas en el mismo orden a IN1, IN2, IN3 e IN4 en el driver. Como siempre, es importante mantener el orden, como mínimo respetar los pares (la salida 8 y 9 puede ir a IN1 e IN2, tanto da el orden o si se cruzan entre ellos. También pueden ir a IN3 e IN4 , pero lo que NO debemos hacer es conectarlo a IN2 e IN3, porque luego es cuando hay que modificar el orden en las instrucciones de arduino y si tenemos algun problema la "lógica" se complica sin ninguna necesidad).

Ahora tenemos 2 jumps encargados de habilitar un bobinado o el otro marcados como ENA y ENB (de enable A y enable B - traducido al español habilitar salidas A, habilitar salidas B-) . Estos jumps deberan quedar siempre conectados haciendo el respectivo puente.

Finalmente nos queda un último triple bornero, al cual viene serigrafiado como VCC, GND y 5V. Aqui  deberemos conectar los 5V a la salida 5V del arduino, y los bornes VCC y GND hacia la fuente de alimentación necesaria para alimentar el motor (en mi caso también es de 5voltios, pero NO se debe cojer del pin del Arduino pues este no tiene energia suficiente para alimentar motores!). En este caso el VCC es el borne positivo mientras que el GND el negativo.

Finalmente uniremos las masas, el GND ground, o el negativo entre el driver y el arduino para mantenerlo todo a la misma diferencia de potencial de referencia.
Ya lo tenemos casi todo conectado! Aprovecho para deciros que NUNCA alimenteis a la vez el Arduino por el jack y por el USB a la vez...yo almenos no me fio ni un pelo...

Ahora nos tocaria cargar el programa en el Arduino. Es muy simple:

#include <Stepper.h>        // incluimos la Stepper library
#define motorStepsX 200     // Pasos motor X 360º/1.8º
Stepper myStepperX(motorStepsX, 8, 9, 10, 11); 
void setup() {
myStepperX.setSpeed(20);  // ajuste velocidad del motor
}
void loop(){
myStepperX.step(motorStepsX*5); 
delay(1500);
myStepperX.step(-motorStepsX*5); 
delay(1500);

Este programa lo unico que hace es dar 5 vueltas en un sentido, se espera 1,5 segundos y da 5 vueltas en sentido contrario. Vuelve a provocar una pausa de 1.5 segundos y asi repetidamente.

Por defecto el numero de impulsos por vuelta está definido con un valor de 200 impulsos vuelta, si teneis un motor de 64 impulsos vuelta deberéis cambiar el valor 200 por 64 en la linea #define motorStepsX 200  

Si quereis utilizar otros pins, debeis cambiar en la linea Stepper myStepperX(motorStepsX, 8, 9, 10, 11);  los números 8, 9, 10 y 11 por los pins que querais usar (preferiblemente en orden!)

Si quereis modificar la velocidad, hay que cambiar el valor 20 por otro en la linea myStepperX.setSpeed(20);

Si quereis modificar el numero de vueltas que gira, hay que cambiar el valor 5 en las lineas myStepperX.step(motorStepsX*5);  y myStepperX.step(-motorStepsX*5);  Si os fijais la unica diferencia entre ambas es el signo negativo que es lo que nos hace invertir el sentido del motor.

Con todo esto ya tendremos el test motor , si funciona: enhorabuena, de lo contrario hay que ir descartando cosas:

* ¿ la tensión de alimentación de los motores es correcta ?
* ¿ están unidas las masas GND entre arduino y el driver ?
* ¿ están bien conectados los motores ? ¿ Corresponden cada par de bobinados a cada una de las salidas del driver?
* ¿ están habilitadas las dos salidas del driver (Enable A y Enable B) ?
* ¿ está el interruptor SW1 del driver en modo funcionamiento ? (este interruptor no está en todos los drivers!)
* ¿ está conectada la señal +5 Voltios entre arduino y driver?
* ¿ Has cargado el programa ? (deberias ver debajo del software un mensaje de Transferencia completa)

Si todas las respuestas es que SI, hay que pasar al plan B: testeo funcionamiento, pero este post lo dejamos para otro día.


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) !