Quizás, y para estar un poco a la moda le podría llamar 2.0
y hasta 3.0 , aunque en realidad el programa es el mismo de la versión
2011-2012 del robot original, este ha sufrido ligeros cambios y correcciones.
En realidad tampoco le debería llamar 1.7, le podría decir versión 2013 y no
por el año en que estamos sino por los pequeños cambios que he hecho desde
finales del 2011 semana tras semana hasta que funcionara y tuviera un mejor aspecto. A dia de hoy creo que me
funciona todo y que es posible que deba de corregir cosas, pero para los
inquietos ya pueden ir copiando y haciendo sus modificaciones pertinentes.
Actualmente está volcado en un ChipKit 32 y si no recuerdo
mal, excede de los 34kb.
Algunos de los errores corregidos respecto la versión
inicial han sido :
· * Cuando el lcd mostraba el % del proyecto llegaba
a un punto en que este valor de volvía loco. @engeeknyer me dio la solución :
cambia el int por un long int .
· * Ya no utilizo mi “keypad” casero, ahora todas
las entradas de posicionamiento y validación funcionan mediante el LCD Shield
con el que gano espacio, peso y orden.
· * Antes el robot se pasaba un paso al final de
cada línea , se ha corregido modificando el programa.
· * Antes cuando terminaba toda la secuencia de fotografías,
el robot al cabo de un rato reiniciaba sólo el ciclo. Ahora se quita todo del loop y al final se queda el sistema
bloqueado hasta que le provoquemos un reset.
·
* Optimización de tiempos de espera
entre movimientos. Aproximadamente se ha “regateado” entre 1-1.5 segundos/foto.
Las nuevas implementaciones:
·
* * Se programa mediante el uso de varias
subrutinas, trabajando el programa en diferentes bloques, según como se mire es
más fácil de entender dónde empieza y dónde acaba cada cosa.
· * Se puede modificar la focal, antes era focal
fija definida por variable.
· * Se puede elegir el formato de cámara entre 4
modelos diferentes y comunes de
formato que hay en el mercado. Viendo el programa es fácil modificar e incluir
otros.
* Dado que tenemos más posibilidad de definir parámetros
mediante el uso del Shield LCD, provoca que existan más variables
“automatizadas” como:
· * Calculo automático del ángulo , provocando que
se deba auto-definir los impulsos que deben dar hacia los motores paso a paso.
· * Calculo automático del tiempo de duración del
proyecto ya que el tiempo entre capturas no es el mismo al variar los ángulos y
tiempos de desplazamiento.
NOTA
> Por si hay alguien que se quiere “copiar” el proyecto, informaros que
debido a un problema con la Shield LCD tuve que insertar una resistencia entre
el pulsador de SELECT y la entrada AnalogRead(A0)
ya que no me respondía esta tecla.
Cabecera
del programa: Sirve para anotar informaciones que no afectaran al
funcionamiento del programa pero que nos puede servir para tenerlas junto al
programa cuando lo grabamos. Muchas veces nos puede servir para hacernos
recordar que es lo que hace el programa, sobretodo cuando no ponemos títulos
muy claros a la hora de guardar el programa.
/*
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 13 Feb. 2013
v1.7
by XavierGP
*/
Habilitar
uso de librerias y constantes: Una librería es un conjunto de instrucciones
que no viene por defecto en la programación de Arduino y que nos simplificaran
mucho el trabajo y tamaño del proyecto. En este caso incluimos la librería Stepper.h que hace referencia al movimiento
de los motores paso a paso , y la librería LiquidCrystal.h
que es la encargada de facilitarnos la faena a la hora de mostrar caracteres en
la Shield LCD.
También
aprovechamos para definir las siguientes constantes:
·
_360div2xPI
la utilizaremos para convertir los angulos de radianes a formato estandar de
una vuelta de 360º.
·
motorStepsX
es el numero de impulsos que debe dar nuestro motor paso a paso para dar una
vuelta, es un valor que en principio nos lo debe dar el fabricante. En este
caso lo usaremos para el motor del movimiento horizontal (eje X). En nuestro
caso corresponde a 200 impulsos.
·
motorStepsY
lo mismo que la anterior constante motorStepsX, pero en este caso es para el
motor del movimiento horizontal.
·
fan se
trata del numero de salida del Arduino que da orden de funcionamiento del
ventilador de refrigeración de los drivers del motor (2x L298N)
#include <Stepper.h> // incluimos la Stepper
library
#include <LiquidCrystal.h>
// incluimos la LiquidCrystal LCD library
#define _360div2xPI 57.29577951
#define motorStepsX 200 // Pasos motor X 360º/1.8º
#define motorStepsY 200 // Pasos motor Y 360º/1.8º
#define fan 29
// Pin OUT 29 habilita tensión en ventilador disipador
Definición
de variables: definimos las variables que vamos a utilizar. A diferencia de
las constantes esto son valores que puede ser que se modifiquen en el
transcurso del programa.
·
pulsosvueltaejeX
: es el numero de pulsos que debemos dar al motor para que de una vuelta entera
el eje X. En teoria deberia ser 200 multiplicado por la reducción que tengamos.
Yo preferí generarme un programa para determinar con exactitud el numero de
impulsos para dar una vuelta entera.
· pulsosvueltaejeY: idem , como en
pulsosvueltaejeX pero para el motor del movimiento vertical (eje Y). En mi caso fueron 6444 impulsos.
· impX es el numero de impulsos que
corresponden a 1 grado del ejeX
· impY es el numero de impulsos que
corresponden a 1 grado del ejeY
· pasoX son los impulsos que realizará
entre capturas del eje X y que tiene relación entre los grados que hay en el
eje horizontal entre diferentes capturas.
· pasoY idem pasoX pero para el eje vertical.
· xact posición actual en grados del eje
X.
· yact posición actual en grados del eje
Y.
· xmax hace referencia a la posición
máxima de la captura expresada en grados del eje X.
· xmin hace referencia a la posición
mínima de la captura expresada en grados del eje X.
· ymax hace referencia a la posición
máxima de la captura expresada en grados del eje Y.
· ymin hace referencia a la posición
mínima de la captura expresada en grados del eje Y.
· xtemp posición actual del robot en
grados del eje X.
· ytemp posición actual del robot en
grados del eje Y.
· nshot numero de foto actual del
proyecto.
· filasX (debería de ser columnas en realidad…)
es el numero de fotos a realizar en cada fila horizontal.
· filasXX usada como variable temporal.
· columnasY es el numero de todas las
filas.
· columnasYY usada como variable temporal.
· previsionShot numero total de
fotografías a realizar durante el proyecto.
· segundos duración del proyecto expresada
en segundos.
· minutos duración del proyecto en
minutos.
· horas duración del proyecto en horas.
· pause variable para mantener o reanudar
una pausa.
· analogico0 es el valor correspondiente a
la lectura analógica numero 0 correspondiente a los valores entregados por los
diferentes pulsadores de la Shield LCD.
· temperatura valor de temperatura
corregido.
· voltage es el valor entregado por la
bateria usando un divisor de tensión para tener un control sobre la misma.
· valorTemp lectura del LM35DT encargado
de mostrarnos la temperatura del disipador de los drivers L298N.
· progreso es el valor en % del proyecto
realizado.
· abajo es el valor de la entrada
analogica 0 correspondiente al pulsador DOWN.
· arriba es el valor de la entrada
analogica 0 correspondiente al pulsador UP.
· enter es el valor de la entrada analogica 0 correspondiente al
pulsador SELECT.
· left es el valor de la entrada analogica
0 correspondiente al pulsador LEFT.
· right es el valor de la entrada
analogica 0 correspondiente al pulsador RIGHT.
· margen es el valor de la tolerancia que
le damos al valor de los pulsadores.
· d1 es el valor del lado largo del sensor
fotografico CMOS expresado en mm.
· d2 es el valor del lado alto del sensor
fotografico CMOS expresado en mm.
· diagonal_sensor es la diagonal
resultante (hipotenusa) del triangulo d1 d2.
· grados_horizontales es el valor
resultante a la variables focal y factor (multiplicación o recorte) en
horizontal.
· grados_verticales es el valor resultante
a la variables focal y factor (multiplicación o recorte) en vertical
· factor es el factor multiplicación o
recorte del sensor CMOS.
· focal es el valor de la focal del
objetivo que utilizaremos.
· paso es una variable que nos ayudará a
ir más rápido a la hora de definir la focal. Cuanto mas grande es el factor
focal , más incrementa el paso (para no subir todo el rato en pasos de un
milímetro)
· menu es una variable para memorizar el
tipo de formato de cámara usado.
· logo1a – logo1h sirven para definir el
gif de la presentación del inicio, en grupos de 5 columnas x 8 filas de
pixeles, dónde un 0 es píxel apagado, y un 1 píxel oscuro.
int pulsosvueltaejex=10180; //
200impulsos x reduccion mecanica
int pulsosvueltaejey=6444;
// 200impulsos x reduccion mecanica
float impX=pulsosvueltaejex/360; // número de impulsos que
corresponden a 1º del eje X
51.9*200/360
float impY=pulsosvueltaejey/360; // número de impulsos que
corresponden a 1º del eje Y
32.2*200/360
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 filasX;
// número de filas eje X >> (xmax-xmin)/pasoX
int filasXX;
int columnasY;
// número de columnas eje Y >> (ymax-ymin)/pasoY
int columnasYY;
int previsionShot; // prevision
número shots filasX *
columnasY
int segundos ;
int minutos ;
int horas ;
int pause;
int analogico0; // variable to store the value
coming from the keypad
float temperatura; //
variable to store the value of radiator
float voltage; // variable to store the voltage
battery supply
float valorTemp;
int progreso;
int abajo=450; //definimos valores teclas
int arriba=210;
int enter=945;
int left=680;
int right=0;
int margen=50;
//abans era 30
float d1; // distancia 1
sensor CMOS (ancho)
float d2; // distancia 2
sensor CMOS (alto)
float diagonal_sensor;
float grados_horizontales;
float grados_verticales;
float factor;
// en su defecto : FF = 1, Nikon DX=1.5 , Canon DX=1.6 , Olympus= 2
float focal = 700;
int paso;
int menu=2;
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};
Inicialización de comandos:
Direccionamos las entradas/salidas que corresponden a cada librería y a cada
salida. En este caso definimos las dos salidas de los Steppers y luego las
salidas correspondientes al LCD.
variables que vamos a utilizar.
Seguidamente le decimos que el pin 28 lo utilizaremos como una salida al igual
que el pin fan (definido en variables como numero 29), y acto seguido le
decimos que por defecto queremos que estén en LOW (salidas apagadas). Acto
seguido habilitamos la velocidad de los dos steppers al valor de 20 y también
“arrancamos” el LCD.
Stepper myStepperX(motorStepsX, 31, 32, 30, 33); // initialize the library motor X
Stepper myStepperY(motorStepsY, 38, 41, 39, 40); // initialize the
library motor Y
LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // initialize the library with the numbers of the interface
pins
void setup() {
pinMode(28, OUTPUT); // Output digital shot
pinMode(fan, OUTPUT); // Output digital fan
digitalWrite(28, LOW);
digitalWrite(fan,LOW);
myStepperX.setSpeed(20);
// set the motor X speed at 15 RPMS:
myStepperY.setSpeed(20);
// set the motor Y speed at 10 RPMS:
lcd.begin(16, 2);
Creamos los caracteres en base a las
variables logo1a,1d,1e y 1h:
lcd.createChar(1, logo1a);
lcd.createChar(4, logo1d);
lcd.createChar(5, logo1e);
lcd.createChar(8, logo1h);
Ejecutamos las subrutinas presentacioninicio y defineparametro: lease
presentacioninicio y defineparametro . Estas subrutinas se encargan de que nos
aparezca el mensaje de bienvenida con el logo de la cámara fotográfica y de
definir los parámetros por el usuario correspondiente al formato de cámara y
focal que utilizaremos.
presentacioninicio();
defineparametro();
Determinación de los puntos inicio y
final de la panorámica: mediante la ayuda de dos subrutinas buscaorigen y buscafinal servirá para determinar la cobertura de nuestra foto. El
programa está pensado para que el valor final tanto en X como de Y sea superior
al final , es decir si entendemos que la posición inicial seria la esquina
inferior izquierda de la fotografía y el punto final seria la esquina superior
derecha. El programa NO está pensado para trabajar en otras opciones (habría
que mejorarlo o probarlo como responde). Empezamos borrando la pantalla y
apareciendo mediante un scroll lateral las instrucciones para localizar el
punto de inicio (esquina inferior izquierda): mover mediante flechas y validar
con tecla Select. Una vez validado nos aparecerá un mensaje de confirmación, y
nos invitará a buscar el punto final (esquina superior derecha) mediante la subrutina
buscafinal. De toda esta parte acabaremos obteniendo el valor de filas y
columnas totales.
lcd.clear();
lcd.print("Define posicion origen mediante flechas");
delay (1000);
for (int positionCounter = 0; positionCounter < 23;
positionCounter++) {
lcd.scrollDisplayLeft(); // scroll one position left:
delay(350);
// wait a bit:
}
delay (1000);
lcd.setCursor(23,1);
lcd.print("[SELECT]
valida");
delay (3000);
buscaorigen();
lcd.clear();
lcd.print("Define posicion final mediante flechas");
delay (1000);
for (int positionCounter = 0; positionCounter < 22;
positionCounter++) {
lcd.scrollDisplayLeft(); // scroll one position left:
delay(350);
// wait a bit:
}
delay (1000);
lcd.setCursor(22,1);
lcd.print("[SELECT]
valida");
delay (3000);
buscafinal();
xact=-xmin;
yact=-ymin;
filasX=((xmax-xmin)/pasoX)+1;
filasXX=filasX;
columnasY=((ymax-ymin)/pasoY)+1;
columnasYY=columnasY;
Una vez tenemos el numero de filas y
columnas es fácil determinar el numero de capturas (previsionShot) pues es una
simple multiplicación, y a continuación calcularemos el tiempo requerido para
realizar el proyecto en segundos.
previsionShot=filasX*columnasY;
segundos=previsionShot+(0.5*(columnasY-1))+(0.04*(((columnasY-1)*pasoY)+((filasX-1)*columnasY*pasoX)));
Nuestro robot se mueve hacia el punto de
origen mientras el LCD se borra varias veces y nos informa del numero de
capturas, filas, columnas y grados correspondientes.
lcd.clear();
lcd.print("Moviendo a posicion de inicio ");
lcd.setCursor(0,1);
lcd.print("<<< << < < < < < < < ");
delay(500);
for (int positionCounter = 0; positionCounter < 23;
positionCounter++) {
lcd.scrollDisplayLeft(); // scroll one position left:
delay(350);
// wait a bit:
}
lcd.clear();
lcd.print("espera...");
myStepperX.step(-(filasX-1)*pasoX);
myStepperY.step(-(columnasY-1)*pasoY);
lcd.clear();
lcd.print("Home position !");
delay(3000);
lcd.clear();
lcd.print(previsionShot);
lcd.print(" capturas:");
lcd.setCursor(0, 1);
lcd.print(filasX);
lcd.print("H * ");
lcd.print(columnasY);
lcd.print("V");
delay (5000);
lcd.setCursor(0,1);
lcd.print((-xmin+xmax)/impX);
lcd.print(char(223));
lcd.print("H ");
lcd.print((-ymin+ymax)/impY);
lcd.print(char(223));
lcd.print("V ");
delay (5000);
Ejecutamos la subrutina conversiontiempo
para que nos pueda expresar la duración del proyecto en horas, minutos y
segundos . Léase conversiontiempo.
conversiontiempo();
Tenemos todas las variables definidas y
nuestro robot a punto, sólo falta que pulsemos SELECT para iniciar. El LCD nos
informa de cómo empezar y como pausar el proyecto.
lcd.clear();
lcd.print("Pulse [SELECT] para iniciar y para pausar ");
lcd.setCursor(0,1);
lcd.print("
");
delay (1000);
for (int positionCounter = 0; positionCounter < 24;
positionCounter++) {
lcd.scrollDisplayLeft();
// scroll one position left:
delay(350);
// wait a bit:
}
delay(2000);
lcd.clear();
lcd.setCursor(0,1);
lcd.print("[SELECT] = start");
pause=1;
while(pause!=0){
if((analogRead(0)/margen)==(enter/margen)){ // Tecla SELECT
pause=0;
}
}
xmax=xmax-xmin;
ymax=ymax-ymin;
xmin=0;
ymin=0;
xact=0;
yact=0;
Rutina de captura de fotografía:
movimiento de motores: Empieza el ciclo disparando una foto y acto seguido
el robot se mueve en sentido hacia la derecha un PasoX (grados resultantes de la focal y factor cámara definidos) y
a continuación otra foto hasta llegar al valor máximo programado como valor
máximo de X (numero de filasX),
momento en que retrocederá el robot hasta el punto de inicio e incrementará el
ángulo en el eje vertical un PasoY
antes de empezar otra vez a realizar todas las fotos de la misma fila.
for(int i=0; i<columnasY; i++){ // empieza secuencia panoramica
delay(1000);
for(int j=0; j<(filasX-1); j++){ //FILASX
Analizamos la temperatura del disipador
(señal del LM35) y decidimos si activamos el ventilador (temperatura superior a
33ºC) o si lo paramos (temperatura inferior a 29ºC)
temperatura=analogRead(A6); // comprobamos
temperatura en disipador LM35DT
valorTemp = ((500 * temperatura * 5 )/1024.0);
if(valorTemp<290){digitalWrite(fan,LOW);}
// deshabilita tensión en ventilador - energy saving - si temperatura
< 29ºC
if(valorTemp>330){digitalWrite(fan, HIGH);} // habilita tensión en ventilador si
temperatura > 33ºC
Activamos la señal hacia el disparador
remoto de la camara durante 500 ms.
digitalWrite(28, HIGH);
delay(500);
// original 500 aquest timer es pot reduir !!!!!!!!!!!!!!!
nshot=nshot++;
mientras ejecuta el proyecto, en el LCD nos
muestra el progreso, numero de foto , la fila y la columna.
lcd.clear();
progreso=((nshot*100)/(previsionShot));
lcd.print(progreso);
lcd.print("%");
lcd.setCursor(7,0);
lcd.print("X:
Y:");
lcd.setCursor(0,1);
lcd.print(nshot);
lcd.setCursor(7,1);
lcd.print(j+1);
lcd.setCursor(12,1);
lcd.print(i+1);
desactivamos la señal del disparador remoto
y analizamos si se está pidiendo de realizar una pausa en el proyecto
(subrutina peticiopausa)
digitalWrite(28, LOW);
peticiopausa();
delay(500);
if(j!=(filasX+0)){
myStepperX.step(pasoX);
xact=xact+(pasoX);
}
delay(500);
// original 750 aquest timer es pot reduir !!!!!!!!
}
nshot=nshot++;
digitalWrite(28, HIGH);
lcd.clear();
progreso=((nshot*100)/(previsionShot));
lcd.print(progreso);
lcd.print("%");
lcd.setCursor(7,0);
lcd.print("X:
Y:");
lcd.setCursor(0,1);
lcd.print(nshot);
lcd.setCursor(7,1);
lcd.print(filasX);
lcd.setCursor(12,1);
lcd.print(i+1);
delay(500);
// original 500 aquest timer es pot reduir !!!!!!!!!!!!!!!
digitalWrite(28, LOW);
delay(500);
una vez llegamos a la ultima captura de
cada fila, el LCD nos mostrará la temperatura del disipador y el voltage de la
bateria mediante una llamada a la subrutina pantallatemperatura
a la vez que el robot retorna al origen en X y acto seguido incrementa un paso
en el eje Y.
pantallatemperatura();
myStepperX.step((-(filasXX)+1)*pasoX); //////////////+0
xact=0;
if(i!=columnasY-1){myStepperY.step(pasoY);}
yact=yact+(pasoY);
delay(1000);
}
Una vez se han completado todas las filas,
columnas y fotos, tan sólo nos queda esperar a que el robot vuelva a la posición
inicial y nos aparezca el mensaje de “Panorámica Completa ! “ y acto seguido
entraremos en un loop del cual es imposible salir hasta que no pulsemos el
botón de reset, con esto evitaremos reinicios automáticos como hacia la versión
anterior de este programa. Ahora ya sólo nos queda procesar todas las capturas
en nuestro ordenador.
lcd.clear();
lcd.print("Panoramica");
lcd.setCursor(5,1);
lcd.print("completa !!");
myStepperY.step((-columnasYY+1)*pasoY);
}
void loop(){
}
Subrutina defineparametro: En esta subrutina vamos a definir el tipo de
cámara réflex que usamos. En principio se han programado los 4 tipos más
conocidos según el formato de recorte (Full Frame, Nikon DX, Canon DX y Olympus
4/3). Es muy fácil insertar más tipos, tan solo tocará buscar las medidas de
los sensores CMOS y calcular su diagonal. En esta programación por defecto
parto de que utilizaré una Nikon DX y un zoom de 700mm. Si queréis definir otra
tan sólo hay que cambiar los valores en las variables al inicio del programa.
void defineparametro(){ // rutina definimos tipo de camara y focal
lcd.clear();
lcd.print(" Formato camara");
lcd.setCursor(2, 1);
lcd.print("[UP] & [DOWN]");
delay(1500);
while((analogRead(0))/margen!=(enter/margen)){ //enter
delay(300);
if((analogRead(0)/margen)==(arriba/margen)){ //arriba
menu=menu+1;
if(menu>=4){menu=4;}
lcd.clear();
}
if((analogRead(0)/margen)==(abajo/margen)){ //abajo
menu=menu-1;
if(menu<=1){menu=1;}
lcd.clear();
}
if(menu==1){ // FF
lcd.print("Full Frame 35 mm.");
d1=35.8; //canon 5d
d2=23.9; //canon 5d
factor=1;
diagonal_sensor=43.0447441623248;
}
if(menu==2){ // Nikon DX
lcd.print(" Nikon DX
1.5x");
factor=1.5;
d1=23.6; // distancia 1
sensor CMOS (ancho) Valor de
la Nikon d90 !
d2=15.8; // distancia 2
sensor CMOS (alto)
Valor de la Nikon d90 !
diagonal_sensor=28.40070421662111;
}
if(menu==3){ // Canon DX
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.print("Olympus 4/3
2x");
factor=2;
d1=17.3; // e-520
d2=13.0; // e-520
diagonal_sensor=21.6400092421422;
}
lcd.setCursor(0, 1);
lcd.print("[SELECT]
valida");
}
En la segunda parte de esta subrutina,
definiremos la focal que vamos a utilizar. Aquí se utiliza la variable paso que
tiene la función de cambiar el incremento de la focal a medida que va creciendo
o decreciendo el valor con la finalidad de tener velocidad a la hora de ajuste
sin perder precisión en el valor.
lcd.clear();
// rutina definimos focal
lcd.print("Longitud
focal:");
lcd.setCursor(2, 1);
lcd.print("[UP] & [DOWN]");
delay(1500);
while((analogRead(0))/margen!=(enter/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);
lcd.clear();
}
if((analogRead(0)/margen)==(abajo/margen)){ //abajo
if(focal>1){focal=focal-(paso);}
lcd.clear();
}
lcd.print(focal);
lcd.print(" mm.");
lcd.setCursor(0, 1);
lcd.print("[SELECT]
valida");
}
// diagonal_sensor=sqrt((d1*d1)+(d2*d2));
//factor =35/d1;
Seguidamente y haciendo uso de las
variables de focal y el formato de cámara, el programa calculará la equivalencia
en grados tanto en vertical como en horizontal (en principio la programación
esta pensada en que la cámara esté
situada en horizontal!), y acto seguido recalculará cuantos impulsos debe
ejercer en cada uno de los dos motores para lograr un solape optimo).
grados_horizontales= 2*
(atan(diagonal_sensor/(2*focal*factor))*_360div2xPI);
grados_verticales = grados_horizontales * d2 / d1;
pasoX=(pulsosvueltaejex*(grados_horizontales/360));
pasoY=(pulsosvueltaejey*(grados_verticales/360));
lcd.clear();
lcd.print ("Focal equivalente");
lcd.setCursor(2, 1);
lcd.print(focal*factor);
lcd.setCursor(10, 1);
lcd.print("mm.");
delay(3000);
}
Subrutina presentacioninicio: básicamente es la presentación del inicio
dónde nos da la bienvenida el LCD y acto seguido muestra la versión del
programa y un gif animado de una cámara haciendo una foto. Dentro de esta
subrutina, llamaremos a otras dos logo2
y logo3 que es dónde contiene parte
de la programación del gif. Según mis pruebas sólo pueden haber 8 caracteres
definidos por el usuario a la vez, es por ello que utilizo subrutinas para
acortar el programa.
void presentacioninicio(){
lcd.clear();
lcd.print(" Hola XavierGP!");
delay(3000);
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Panorama");
lcd.setCursor(0,1);
lcd.print("Robot");
logo3();
delay(800);
logo2();
delay(400);
logo3();
lcd.setCursor(6,1);
lcd.print("v1.7");
delay(800);
logo2();
delay(400);
logo3();
delay(3000); // fin GIF
}
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);
}
Subrutina conversiontiempo: Tal y como dice el nombre, esta subrutina
sólo tiene la finalidad de convertir el tiempo de duración del proyecto que
teníamos en formato segundos a formato horas, minutos y segundos.
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.print("Duracion prevista");
lcd.setCursor(0,1);
lcd.print(horas);
lcd.print("hrs ");
lcd.print(minutos);
lcd.print("min ");
lcd.print(segundos);
lcd.print("seg");
delay(5000);
}
Subrutina pantallatemperatura: esta es la pantalla que nos mostrará el
LCD cuando está retornando el robot a la columna de inicio. Podremos ver la
temperatura del disipador y el voltaje de la batería a modo de información. En
el valor de voltaje he tenido de corregirlo para que se asemejara lo más
posible a mi realidad.
void pantallatemperatura(){
lcd.clear();
lcd.print("Voltage ");
voltage=analogRead(A7)*1.212;
lcd.print(voltage/100);
lcd.print(" v.");
lcd.setCursor(0,1);
lcd.print("Temp.:
");
lcd.print(valorTemp/10);
lcd.print(" ");
lcd.print(char(223));
lcd.print("C");
}
Subrutina peticiopausa: preguntamos al pic si hay la entrada analógica 0
en valor de Select durante 10 veces y si es asín provocamos una pausa forzada
de 10 segundos antes no podamos volver a pulsar la misma tecla para reprender
con nuestro proyecto fotográfico. El por que de las 10 veces es porque me he
encontrado que o el pic corre mucho y “no se entera” o el valor no era el
exacto al programar la pausa.
void peticiopausa(){
for (int k=0; k<10; k++){ //prova pausa
if((analogRead(0)/margen)==(enter/margen)){ // tecla SELECT si apretem provoquem una
pausa fins que no apretem ENTER
lcd.clear();
lcd.print("Pausa!");
lcd.setCursor(0,1);
lcd.print("[SELECT]=reStart");
delay(10000); //abans no
hi era i em rearrancaba
pause=2;
while(pause>1){
if((analogRead(0)/margen)==(enter/margen)){ // tecla SELECT
pause=0;
lcd.clear();
lcd.print("SHOT
X: Y:");
delay(1000);
}
}
}
} //prova pausa
}
Subrutina buscaorigen: hasta que no pulsemos select no nos validará la
posición que deseemos y que podemos desplazar mediante el uso de los pulsadores
up, down, left y right mediante la subrutina movimiento. A continuación memorizará estos valores como la xmin y
la ymin.
void buscaorigen(){
while((analogRead(A0)/margen)!=(enter/margen)){
movimiento();
}
xmin=xact;
ymin=yact;
lcd.clear();
lcd.print("Posicion
origen");
lcd.setCursor(0, 1);
lcd.print(" - CONFIRMADA - ");
delay(3500);
}
Subrutina buscafinal: lo mismo que en buscainicio
pero definiremos el punto final de nuestras capturas, memorizándolos como xmax
e ymax.
void buscafinal(){
while((analogRead(A0)/margen)!=(enter/margen)){
movimiento();
}
xmax=xact;
ymax=yact;
lcd.clear();
lcd.print("Posicion
final");
lcd.setCursor(0, 1);
lcd.print(" - CONFIRMADA - ");
delay(3500);
}
Subrutina movimiento: tiene la función de
hacer mover el robot manualmente mediante los pulsadores up, down, left y right
y acto seguido informándonos en el LCD la posición del mismo.
void movimiento(){
analogico0 = analogRead(A0);
if((analogRead(A0)/margen)==(right/margen)) {xact=xact+pasoX;} // derecha +X
if((analogRead(A0)/margen)==(left/margen)) {xact=xact-pasoX;} //
izquierda -X
if((analogRead(A0)/margen)==(abajo/margen)) {yact=yact-pasoY;} // abajo -Y
if((analogRead(A0)/margen)==(arriba/margen)){yact=yact+pasoY;} // arriba +Y
lcd.clear();
lcd.print("X: Y:");
lcd.setCursor(0, 1);
lcd.print(xact/impX);
lcd.print(char(223));
lcd.setCursor(8, 1);
lcd.print(yact/impY);
lcd.print(char(223));
myStepperX.step(xact-xtemp);
xtemp=xact;
myStepperY.step(yact-ytemp);
ytemp=yact;
delay(20);
}
Con
todo esto ya tenemos el programa enterito y funcional, aunque seguro que muchos
de vosotros podéis optimizarlo mucho más y corregir errores, todo es cuestión
de pasarse horas pensando , modificando y probando.
Lógicamente
no lo he probado con todas las focales y ni mucho menos con los 4 formatos de
factor recorte , pero en teoría y repito: en teoría debería ir…
Personalmente
creo que hay un error al no definir el solape entre capturas pero después de
varias pruebas, aun no lo veo claro ! y como que es un proyecto personal sin
ningún animo de lucro ni de proyecto de carrera ni nada que se le parezca creo
que ya está bastante bien, al menos era la intención , pero todo conlleva mucho
tiempo, horas ya sea haciendo agujeros, buscando material por eBay, documentando
y buscando fórmulas, haciendo videos y luego editarlos, fotos, entrada al blog…na
que hay un buen curro que espero que algunos sepáis valorar.
Finalmente
os dejo un video en que podéis medio ver como funciona el tema de pantallas,
deseo que os guste.
Tengo
previsto alojar el programa entero en Internet, pero aun no tengo claro dónde
para poder tener un control a título personal del número de descargas…¿alguna
idea/propuesta?
¿
Alguien se anima a crear la versión 2.0 ?
Saludos
a todos los visitantes!