martes, 19 de febrero de 2013

Enterprise TrekDuino

En este proyecto he hackeado con un Arduino Mega una maqueta de la nave Enterprise de la serie Star Trek para añadirle muchas funcionalidades como luces ambientales con leds blancos, leds rojos para la alerta roja, leds RGB (de colores) para los motores, leds rojos y verdes para las luces de posición, led rojo para el deflector, un laser real para el disparo faser, efecto visual de disparo de torpedo de fotones, reconocimiento de voz con easyVR, efectos de sonidos de la nave original, sensor de temperatura y humedad, pantalla TFT para mostrar imágenes y mensajes, sensor PIR para detección de movimiento...



Tras pintar cada una de las partes de la maqueta coloqué en su interior los múltiples leds, junto con el laser y pasé todo el cableado por el interior hasta llevarlo a la base donde, tal y como se puede apreciar en las fotos está todo el sistema de control.


El interruptor lateral corta la corriente para dejarlo apago o encendido completamente. Cuando está encendido es el sensór PIR de la parte frontal el que activa todo cuando detecta movimiento.
En el lateral, el logotipo de la Federación de Planetas oculta un botón de modo que al oprimirlo se pone a la espera (apareciendo la imagen de Standby en el monitor). Tras pulsarlo pueden introducirse comandos (previamente registrados) de voz en dos etapas. Primero (al igual que en la serie) diremos la palabra Ordenador. Al reconocerla el sistema emite un beep y aguarda el comando de voz concreto. Este puede ser:
  • Alerta roja: Se escucha el sonido de alarma, en el monitor aparece la imagen de alerta roja, se cambia la iluminación interior a rojo, se emite el sonido de levantar escudos, se disparan el laser y el torpedo de fotones con su sonidos asociados, tras lo cual pasados unos segundos se pasa a alerta amarilla y posteriormente a verde con el sonido de bajar escudos y se retorna a la normalidad.
  • Levanta escudos: Emite el sonido apropiado.
  • Dispara torpedo fotones: Enciende el led que emula esta función y emite el sonido.
  • Dispara faser: Dispara el faser con su sonido
Pulsando dos veces consecutivas el pulsador también se provoca una alerta roja.

En situación normal el monitor alterna una imagen con la temperatura y humedad ambiental.








La verdad es que he disfrutado haciendo la maqueta y reuniendo en ella diversos componentes que había utilizado en anteriores proyectos junto con otros nuevos.

Estuve sopesando la posibilidad de crear la caja sobre la que se asienta la maqueta con un diseño propio más estilizado e imprimirla con una impresora 3d. Para ello aprendí a diseñar objetos tridimensionales con el programa Autodesk 123D que ofrece unas enormes posibilidades. Finalmente descarté el modelo que había preparado por el excesivo coste de su impresión. De este modo aproveché una sencilla caja de madera que perforé y pinté.

Código fuente

Puedes descargarte todo el código:
  • Mediante subversion:  
svn checkout http://trekduino.googlecode.com/svn/trunk/ trekduino-read-only
  • Navegador web: 
https://code.google.com/p/trekduino/source/browse/
  • Proyecto googlecode: 
https://code.google.com/p/trekduino/


Listado de materiales:
  • Maqueta Revell 1/600 U.S.S. Enterprise NCC-1701:



martes, 12 de junio de 2012

Mando a distancia universal sin manos o con múltiples adaptadores

Sustituye cualquier mando a distancia de infrarrojos: televisión, aire acondicionado, radio... Puede ser usado sin contacto físico, únicamente pasando la mano por encima lo que permite que personas con graves limitaciones de movimiento puedan utilizarlo. Para otras personas que puedan utilizar un joystick se le pueden instalar un mando de la WII y así tener más funcionalidades.

Está dirigido a todas aquellas personas con problemas motores a quienes sea difícil utilizar un mando a distancia convencional.

La finalidad es que al estar diseñado de forma modular se le pueden conectar diferentes accesorios como el sensor sin contacto físico, el mando de la wii, botones grandes, sensores de presión... dependiendo de las necesidades de cada usuario.

Uso sin manos, con el sensor de distancia: cada vez que se pasa la mano por encima se cambia el canal.

Uso mediante el joystick del Nunchuck de la WII. De esta manera se pueden enviar 4 códigos diferentes. Podrían utilizarse también los 2 botones que tiene.



 Para cargar el dispositivo con los 4 códigos que deseemos del mando a distancia (este número puede cambiarse en el programa) pulsaremos los dos botones simultáneamente, comenzará a parpadear el led rojo y entonces pulsaremos los botones del mando a distancia. Cada vez que se recibe un código parpadea una vez el led. Tras recibir el cuarto código vuelve a parpadear varias veces  y ya está listo para su uso. Estos códigos se almacenan en la memoria EEPROM de modo que aunque se le quite la corriente no se borrarán.

En la versión actual cuando se pasa la mano por encima se pone en pausa o en play. Si se para la mano encima suena un pitido (no he reflejado el buzzer en las fotos ni en el esquema) y entonces si se sube la mano se hace fastforwar (avance rápido) y si se baja backforward (retroceso rápido).



Nota: En las fotos no aparecen ni el capacitor ni el MAX IRLed (si figuran en el esquema)




Lo envolvemos todo con una cajita hecha con Lego



Esquema original:





Esquema mejorado:

Para aumentar el alcance de la señar infrarroja que en el esquema origina no llegaba a los 2 metros he sustituido el led infrarrojo y la correspondiente resistencia por el Max Power IR LED Kit de SparkFun.com Otra mejora es añadir un capacitor de 100μF/25v entre la toma de corriente (positivo del capacitor) y la tierra del lector de distancia de infrarrojo para estabilizar las lecturas tal y como se indica en su datasheet de especificaciones.
 No es necesario ningún cambio en el programa.




Materiales:

Si se quiere usar pasando la mano por encima:
Si se quiere usar con el Nunchuku de la WII:
Puedes descargarte todo el código:

martes, 10 de enero de 2012

CocheNunchuk el coche controlado con Nunchuk

He preparado la base de un coche con dos motores de Lego Mindstorm RCX conectados al Arduino UNO mediante el Motor Shield de Adafruit.com. Gracias al Wiichuck Adapter conectamos el Nunchuk y utilizando esta librería preparo el programa con el que controlaremos los motores.

 En el Nunchuk he configurado lo siguiente:
  • botón de parada
  • botón de arranque
  • girandole sobre el eje X giro a derecha o izquierda
  • levantándole o bajándole sobre el eje Y se regula la velocidad
  • con el joystick que lleva integrado controlo la marcha atrás




Código:

/*
 * CocheNunchukV2 Coche con ArduinoNunchuk y motores Lego
 * Mediante un nunchuk conectado mediante el cable puedo manejar un coche hecho con dos motores Lego
 * En el Nunchuk he configurado lo siguiente:
 * - botón de parada
 * - botón de arranque
 * - girandole sobre el eje X giro a derecha o izquierda
 * - levantándole o bajándole sobre el eje Y se regula la velocidad
 * - con el joystick que lleva integrado controlo la marcha atrás
 *
 * Copyleft 2011 Antonio Garcia Figueras
 *
 * Usando libreria de Gabriel Bianconi,  http://www.gabrielbianconi.com/projects/arduinonunchuk/
 *
 */

#include <Wire.h>
#include "ArduinoNunchuk.h"
#include <AFMotor.h>

#define BAUDRATE 19200
#define PORCENTAJE_MARGENX 20
#define PORCENTAJE_MARGENY 20
ArduinoNunchuk nunchuk = ArduinoNunchuk();

//Servo myservo;

 int minX;
 int maxX;
 int minY;
 int maxY;
 int minZ;
 int maxZ;
 float centroX;
 float margenX;
 int contador;
 float razonY;
 int posXServo;
 boolean swStop;
 int velocidad;

 AF_DCMotor motorIz(1, MOTOR12_64KHZ); // create motor izquierda, 64KHz pwm
 AF_DCMotor motorDe(2, MOTOR12_64KHZ); // create motor derecha, 64KHz pwm

void setup() {
  Serial.begin(BAUDRATE);

  pinMode(13, OUTPUT);    //Iniciamos el led

  Serial.println("Inicio incializacion");
  nunchuk.init();
  Serial.println("Final incializacion"); 

  calibraAccel();
 
  // Averiguamos cual es el valor del punto medio
  centroX = (float)(minX + (maxX -minX)/2);
  Serial.print("centroX:");
  Serial.println(centroX, DEC);

  // Daremos este margen desde el centro para considerar que va recto
  margenX =  (float)PORCENTAJE_MARGENX * (maxX -minX)/100;
  Serial.print("margenX:");
  Serial.println(margenX, DEC); 

//Calculo de la razon para calcular el valor del velocidad (70-255=185)
//en funcion del movimiento del nunchucku en el eje Y
  razonY = (float)185/(maxX - minX); 
  Serial.print("RazonY:");
  Serial.println(razonY, DEC);
 
  swStop = true;
}

void loop() {
     nunchuk.update();
    
     imprimeValoresNunchuk();
//     delay(2000);
    
     if (nunchuk.zButton==1){
      swStop=true;
     }else if (nunchuk.cButton==1){
      swStop=false;
     }    
    
     if (swStop){
       para();
       Serial.println("swStop es true");
     }else{
       determinaMovimiento();
       Serial.println("swStop es false");
     }
//     Serial.println("***************************************");
//     Serial.print("accelX:");
//     Serial.println(nunchuk.accelX, DEC);
    


}

void parpadea(){
  for (int cont = 0; cont < 3; cont++){
    digitalWrite(13, HIGH);   // set the LED on
    delay(500);              // wait for a second
    digitalWrite(13, LOW);    // set the LED off
    delay(500); 
  }
}
 
void determinaVelocidad(){
     // Al multiplicar la posicion Y del nunckuk por la razon
     // lo convertimos a valores entre 70 y 255 para la velocidad del motor
     velocidad = (int)((nunchuk.accelY - (maxY - minY)) * razonY)+70;
    
     motorIz.setSpeed(velocidad);   
     motorDe.setSpeed(velocidad);   
     Serial.print("accelY:");
     Serial.println(nunchuk.accelY, DEC); 
     Serial.print("Velocidad");
     Serial.println(velocidad, DEC);     
}

void determinaMovimiento(){
     determinaVelocidad();
     if (nunchuk.accelX > (centroX + margenX)){
       giroDerecha();
     }else if(nunchuk.accelX < (centroX - margenX)){
       giroIzquierda();
     }else{
       recto();
     } 
}

void giroDerecha(){
  Serial.print("giroDerecha");
  motorDe.run(RELEASE);
  if (nunchuk.analogY < 100){ 
    motorIz.run(BACKWARD);
  }else{
    motorIz.run(FORWARD);
  }   
}

void giroIzquierda(){
  Serial.print("giroIzquierda");
  motorIz.run(RELEASE);
  if (nunchuk.analogY < 100){ 
    motorDe.run(BACKWARD);
  }else{
    motorDe.run(FORWARD);
  }   
}

void recto(){
  Serial.print("recto");
  if (nunchuk.analogY < 100){
    motorIz.run(BACKWARD);
    motorDe.run(BACKWARD);
  }else{
    motorIz.run(FORWARD);
    motorDe.run(FORWARD);
  }


void para(){
  motorIz.run(RELEASE);
  motorDe.run(RELEASE);


// Calibra todos los acelerometros para saber sus
// valores minimos y maximos
void calibraAccel()  {
 parpadea(); // parpadea cuando va a comenzar la calibración
 nunchuk.update();
 minX = nunchuk.accelX;
 maxX = nunchuk.accelX;
 minY = nunchuk.accelY;
 maxY = nunchuk.accelY;
 minZ = nunchuk.accelZ;
 maxZ = nunchuk.accelZ;
 contador=0;
 
 Serial.println("Inicio calibracion");
 
 while (contador<=200){
     Serial.print("Paso=");
     Serial.println(contador);
     nunchuk.update();
     if (nunchuk.accelX < minX){
       minX=nunchuk.accelX;
     }
     if (nunchuk.accelX > maxX){
       maxX=nunchuk.accelX;
     }
    
     if (nunchuk.accelY < minY){
       minY=nunchuk.accelY;
     }
     if (nunchuk.accelY > maxY){
       maxY=nunchuk.accelY;
     }

     if (nunchuk.accelZ < minZ){
       minX=nunchuk.accelZ;
     }
     if (nunchuk.accelZ > maxZ){
       maxZ=nunchuk.accelZ;
     }    
     delay(10);
     contador++;
 }
 Serial.print("Resultado calibracion X. MinX=");
 Serial.print(minX, DEC);
 Serial.print(" MaxX=");
 Serial.print(maxX, DEC);
 Serial.print(" Diferencia=");
 Serial.println(maxX - minX);
 
 Serial.print("Resultado calibracion Y. MinY=");
 Serial.print(minY, DEC);
 Serial.print(" MaxY=");
 Serial.print(maxY, DEC);
 Serial.print(" Diferencia=");
 Serial.println(maxY - minY);
 
 Serial.print("Resultado calibracion Z. MinZ=");
 Serial.print(minZ, DEC);
 Serial.print(" MaxZ=");
 Serial.print(maxZ, DEC);
 Serial.print(" Diferencia=");
 Serial.println(maxZ - minZ);
 
 parpadea();// parpadea al finalizar la calibración
}


void imprimeValoresNunchuk(){
  Serial.print(nunchuk.analogX, DEC);
  Serial.print(' ');
  Serial.print(nunchuk.analogY, DEC);
  Serial.print(' ');
  Serial.print(nunchuk.accelX, DEC);
  Serial.print(' ');
  Serial.print(nunchuk.accelY, DEC);
  Serial.print(' ');
  Serial.print(nunchuk.accelZ, DEC);
  Serial.print(' ');
  Serial.print(nunchuk.zButton, DEC);
  Serial.print(' ');
  Serial.println(nunchuk.cButton, DEC);
}

viernes, 30 de diciembre de 2011

Detector de mentiras conArduinoNunchuk y servo

Este proyecto sirve para probar el manejo del Nunchuk de la Wii. Según se gire en el aire el Nunchukse le envía al servo la orden de giro.

La chorradita del cartel de Mentira y Verdad ha sido para divertir a mi hija pequeña y a una amiga suya.

Estuve sopesando algunas librerías para manejar el Nunchuk y al final me he decantado por http://www.gabrielbianconi.com/projects/arduinonunchuk/

Como adaptador para conectar el nunchuk a Arduino he usado el WiiChuck http://todbot.com/blog/2008/02/18/wiichuck-wii-nunchuck-adapter-available/



Código fuente:

/*
 * Detector de mentiras conArduinoNunchuk y servo
 *
 * Copyleft 2011 Antonio Garcia Figueras
 *
 * Usando libreria de Gabriel Bianconi, http://www.gabrielbianconi.com/
 *
 * Project URL: http://www.gabrielbianconi.com/projects/arduinonunchuk/
 *
 */

#include <Wire.h>
#include "ArduinoNunchuk.h"
#include <Servo.h>

#define BAUDRATE 19200

ArduinoNunchuk nunchuk = ArduinoNunchuk();

Servo myservo;

 int minX;
 int maxX;
 int minY;
 int maxY;
 int minZ;
 int maxZ;
 int contador;
 float razonX;
 int posXServo;

void setup() {
  Serial.begin(BAUDRATE);
  myservo.attach(9);  // attaches the servo on pin 9 to the servo object
  myservo.write(90);  // lo colocamos centrado
  Serial.println("Inicio incializacion");
  nunchuk.init();
  Serial.println("Final incializacion");
  calibraAccel();
 
//Calculo de la razon para calcular el valor del servo (0-180)
//en funcion del movimiento del nunchucku
  razonX = (float)180/(maxX - minX);
  Serial.print("Razon:");
  Serial.println(razonX, DEC);
}

void loop() {
     nunchuk.update();
   
     // Al multiplicar la posicion del nunckuk por la razon
     // lo convertimos a valores entre cero y 180 para el servo
     posXServo = (int)(nunchuk.accelX - (maxX - minX)) * razonX;
     Serial.print("accelX:");
     Serial.println(nunchuk.accelX, DEC);
   
     Serial.print("posXServo:");
     Serial.println(posXServo, DEC);
     if (posXServo > 180) posXServo=180; //por si por los decimales da un valor superior
     if (posXServo < 0) posXServo=0;
   
     myservo.write(posXServo);
   
     delay(10); //Para dar tiempo a que se coloque el servo
 
 
 
//  nunchuk.update();
//
//  Serial.print(nunchuk.analogX, DEC);
//  Serial.print(' ');
//  Serial.print(nunchuk.analogY, DEC);
//  Serial.print(' ');
//  Serial.print(nunchuk.accelX, DEC);
//  Serial.print(' ');
//  Serial.print(nunchuk.accelY, DEC);
//  Serial.print(' ');
//  Serial.print(nunchuk.accelZ, DEC);
//  Serial.print(' ');
//  Serial.print(nunchuk.zButton, DEC);
//  Serial.print(' ');
//  Serial.println(nunchuk.cButton, DEC);
}


// Calibra todos los acelerometros para saber sus
// valores minimos y maximos
void calibraAccel()  {
 nunchuk.update();
 minX = nunchuk.accelX;
 maxX = nunchuk.accelX;
 minY = nunchuk.accelY;
 maxY = nunchuk.accelY;
 minZ = nunchuk.accelZ;
 maxZ = nunchuk.accelZ;
 contador=0;

 Serial.println("Inicio calibracion");

 while (contador<=200){
     Serial.print("Paso=");
     Serial.println(contador);
     nunchuk.update();
     if (nunchuk.accelX < minX){
       minX=nunchuk.accelX;
     }
     if (nunchuk.accelX > maxX){
       maxX=nunchuk.accelX;
     }
   
     if (nunchuk.accelY < minY){
       minY=nunchuk.accelY;
     }
     if (nunchuk.accelY > maxY){
       maxY=nunchuk.accelY;
     }

     if (nunchuk.accelZ < minZ){
       minX=nunchuk.accelZ;
     }
     if (nunchuk.accelZ > maxZ){
       maxZ=nunchuk.accelZ;
     }   
     delay(10);
     contador++;
 }
 Serial.print("Resultado calibracion X. MinX=");
 Serial.print(minX, DEC);
 Serial.print(" MaxX=");
 Serial.print(maxX, DEC);
 Serial.print(" Diferencia=");
 Serial.println(maxX - minX);

 Serial.print("Resultado calibracion Y. MinY=");
 Serial.print(minY, DEC);
 Serial.print(" MaxY=");
 Serial.print(maxY, DEC);
 Serial.print(" Diferencia=");
 Serial.println(maxY - minY);

 Serial.print("Resultado calibracion Z. MinZ=");
 Serial.print(minZ, DEC);
 Serial.print(" MaxZ=");
 Serial.print(maxZ, DEC);
 Serial.print(" Diferencia=");
 Serial.println(maxZ - minZ);
}

lunes, 19 de diciembre de 2011

Felicitador navideño

Mediante un relé conecto Arduino a una tarjeta de felicitación navideña de las que empiezan a sonar con musiquita navideña y encienden un led.
Uso la cámara de vídeo como sensor de movimiento (no tomo ni grabo la foto) y entonces cierra el relé durante 30 segundos con lo que empieza a sonar el circuito de la tarjeta de felicitación. Pasado ese tiempo lo inactivo durante 5 minutos para que no esté sonando continuamente.


Vídeo primera parte:




Vídeo segunda parte:



Código fuente FelicitadorNavidadV1:

// Mediante un relé conecto Arduino a una tarjeta de felicitación navideña de las que empiezan a sonar
// con musiquita navideña y encienden un led.
// Uso la cámara de vídeo como sensor de movimiento (no tomo ni grabo la foto) y entonces cierra el relé durante 30 segundos con
// lo que empieza a sonar el circuito de la tarjeta de felicitación. Pasado ese tiempo lo inactivo durante
// 5 minutos para que no esté sonando continuamente.

#include <VC0706.h>
#include <SD.h>
#if ARDUINO >= 100
 #include <SoftwareSerial.h>
#else
 #include <NewSoftSerial.h>
#endif

// This is the SD card chip select line, 10 is common
#define chipSelect 10
// This is the camera pin connection. Connect the camera TX
// to pin 2, camera RX to pin 3
#if ARDUINO >= 100
SoftwareSerial cameraconnection = SoftwareSerial(2, 3);
#else
NewSoftSerial cameraconnection = NewSoftSerial(2, 3);
#endif
// pass the serial connection to the camera object
VC0706 cam = VC0706(&cameraconnection);


void setup() {
  Serial.begin(9600);
 
  // Try to locate the camera
  if (cam.begin()) {
    Serial.println("Camera Found:");
  } else {
    Serial.println("No camera found?");
    return;
  }
  // Print out the camera version information
  char *reply = cam.getVersion();
  if (reply == 0) {
    Serial.print("Failed to get version");
  } else {
    Serial.println("-----------------");
    Serial.print(reply);
    Serial.println("-----------------");
  }

  // Set the picture size - you can choose one of 640x480, 320x240 or 160x120
  // Remember that bigger pictures take longer to transmit!
 
  //cam.setImageSize(VC0706_640x480);        // biggest
  cam.setImageSize(VC0706_320x240);        // medium
  //cam.setImageSize(VC0706_160x120);          // small

  // You can read the size back from the camera (optional, but maybe useful?)
  uint8_t imgsize = cam.getImageSize();
  Serial.print("Image size: ");
  if (imgsize == VC0706_640x480) Serial.println("640x480");
  if (imgsize == VC0706_320x240) Serial.println("320x240");
  if (imgsize == VC0706_160x120) Serial.println("160x120");


  //  Motion detection system can alert you when the camera 'sees' motion!
  cam.setMotionDetect(true);           // turn it on
  //cam.setMotionDetect(false);        // turn it off   (default)

  // You can also verify whether motion detection is active!
  Serial.print("Motion detection is ");
  if (cam.getMotionDetect())
    Serial.println("ON");
  else
    Serial.println("OFF");
   
   
    pinMode(6, OUTPUT); // Controla el relay
}




void loop() {
 if (cam.motionDetected()) {
   Serial.println("Motion!");  
   cam.setMotionDetect(false);
   Serial.println("Activamos el relay!");
   digitalWrite(6, HIGH); // Activamos el relay
   delay(30000); // Mantenemos activo 30 seg
   Serial.println("Desctivamos el relay!");
   digitalWrite(6, LOW); // Desactivamos el relay
  
  Serial.println("...Done!");
  delay(300000); // Pausamos 5 min para que no vuelva a activarse demasiado pronto
  cam.resumeVideo();
  cam.setMotionDetect(true);
 }
}

lunes, 28 de noviembre de 2011

Cámara sobre servidor web con envío de tweet y detección de movimiento

El objetivo de este proyecto es utilizar una pequeña cámara que cuando detecta movimiento tome una foto, la almacene en una tarjeta de memoria SD colocada en el ethernet shield, envíe un tweet de aviso y mediante un servidor web (Tiny web server) incorporado en el propio arduino permita que el usuario desde internet se conecte y pueda ver la lista de todas las fotografías almacenadas en la tarjeta y visualizar cualquiera de ellas. También permitirá que el usuario active o desactive las funciones de envío de tweets y la de detección automática de movimiento.
Cuando se detecta moviento antes de hacer la foto se cierra un relé al cual se podría conectar una lámpara de modo que haya luz cuando se haga la foto.


// He tenido que reducir al mínimo las cadenas de texto utilizadas pues estoy en el límite de la memoria del Arduino UNO
// Para que desde el navegador nos podamos descargar un fichero que ya esta en la tarjeta simplemente
// pondremos la ip del sitio seguido de /nombrearchivo Por ejemplo: http://192.168.1.177/IMAGE10.JPG
// Si Unicamente ponemos la ip sin nada detras nos mostrara la lista de los ficheros que contiene la tarjeta
// http://192.168.1.177/1 Activaría la detección de movimiento
// http://192.168.1.177/2 Desactivaría la detección de movimiento
// http://192.168.1.177/3 Activaría el envío de tweets
// http://192.168.1.177/4 Desactivaría el envío de tweets
//
// Version V2: Le pongo activar o desactivar Motion añadiendo en la URL motion_off o motion_on
// Version V3: He quitado muchos if, quito upload file, activar o desactivar motio y lo reduzco todo al mínimo para poder
// mandar el tweet y que quepa en la memoria
// Version V4: Pruebo a almacenar cosas en SRAM en vez de en Flash. Pongo activar o desactivar envio de tweet poniendo
// en la URL tweet_on o tweet_off. Cuando se envia un tweet se pone a off automaticamene. Cada vez que se manda un comando
// se muestra la pagina index con el estado de los switches y la lista de ficheros.
// Version V5: He conectado el rele que permanecerá un intervalo de tiempo encendido desde que se enciende y así poder conectar
// una luz para que la cámara pueda sacar una foto nítida
// IMPORTANTE: He intentado poner todo el proyecto sobre Arduino Mega 2560 para terminar con los problemas de memoria
// pero no ha sido posible porque la versión 10 de
// la librería NewSoftSerial no funciona sobre Mega. He probado con la beta 11 de
// http://arduiniana.org/2011/01/newsoftserial-11-beta pero no he conseguido que funcione
// TODO: crear una cuenta de twitter especifica y habilitarla para envio de sms
// TODO: abrir puertos del PC para ver el Arduino
// TODO: posibilidad de borrar las fotografías.
// TODO: añadir opción para que pueda dispararse de manera remota una foto aunque no se haya detectado movimiento
// TODO: poner la cámara sobre unos servos de modo que puediera moverse su orientación de manera remota

//
// Cuando graba 99 fotos la nueva siempre se graba como 99, una y otra vez. Decido dejarlo así.
//
// Sobre Tiny Web Server http://www.webweavertech.com/ovidiu/weblog/archives/000484.html
// Para el envío de tweets, como es muy costoso en términos de memoria se utiliza una aplicación externa http://arduino-tweet.appspot.com
// Ethernet shield http://www.adafruit.com/products/201


Ejemplo de la pantalla con el índice de todas las fotos:


Código fuente CamaraTinyWebServerV5:

// -*- c++ -*-
// Author: Antonio García Figueras
// Date: Noviembre 2011
// Programa para controlar una microcámara que al detectar moviento active un relé, al que se puede conectar una bombilla,
// tome una foto y la guarde en la tarjeta de memoria SD que se colocaría en el ethernet shield. Seguidamente se enviaría un tweet.
// Foto con los circuitos necesarios en
// http://xp-dev.com/svn/arduino_antonio/trunk/C%C3%A1mara/Foto%20conexi%C3%B3n%20c%C3%A1mara%20a%20shiel%20ethernet%20y%20relay.jpg
// Este ethernet shield permitirá
// que el servidor web (Tiny web server) que he usado permita visualizar la lista de fotografías que se hayan ido almacenando en la tarjeta
// de memoria y que el usuario desde internet pueda visualizar cualquiera de ellas. También puede activar o desactiva la
// función de detectar movimiento de la cámara y el envío o no de tweets.
// He tenido que reducir al mínimo las cadenas de texto utilizadas pues estoy en el límite de la memoria del Arduino UNO
// Para que desde el navegador nos podamos descargar un fichero que ya esta en la tarjeta simplemente
// pondremos la ip del sitio seguido de /nombrearchivo Por ejemplo: http://192.168.1.177/IMAGE10.JPG
// Si Unicamente ponemos la ip sin nada detras nos mostrara la lista de los ficheros que contiene la tarjeta
// http://192.168.1.177/1 Activaría la detección de movimiento
// http://192.168.1.177/2 Desactivaría la detección de movimiento
// http://192.168.1.177/3 Activaría el envío de tweets
// http://192.168.1.177/4 Desactivaría el envío de tweets
//
// Version V2: Le pongo activar o desactivar Motion añadiendo en la URL motion_off o motion_on
// Version V3: He quitado muchos if, quito upload file, activar o desactivar motio y lo reduzco todo al mínimo para poder
// mandar el tweet y que quepa en la memoria
// Version V4: Pruebo a almacenar cosas en SRAM en vez de en Flash. Pongo activar o desactivar envio de tweet poniendo
// en la URL tweet_on o tweet_off. Cuando se envia un tweet se pone a off automaticamene. Cada vez que se manda un comando
// se muestra la pagina index con el estado de los switches y la lista de ficheros.
// Version V5: He conectado el rele que permanecerá un intervalo de tiempo encendido desde que se enciende y así poder conectar
// una luz para que la cámara pueda sacar una foto nítida
// IMPORTANTE: He intentado poner todo el proyecto sobre Arduino Mega 2560 para terminar con los problemas de memoria
// pero no ha sido posible porque la versión 10 de
// la librería NewSoftSerial no funciona sobre Mega. He probado con la beta 11 de
// http://arduiniana.org/2011/01/newsoftserial-11-beta pero no he conseguido que funcione
// TODO: crear una cuenta de twitter especifica y habilitarla para envio de sms
// TODO: abrir puertos del PC para ver el Arduino
// TODO: posibilidad de borrar las fotografías.
// TODO: añadir opción para que pueda dispararse de manera remota una foto aunque no se haya detectado movimiento
// TODO: poner la cámara sobre unos servos de modo que puediera moverse su orientación de manera remota

//
// Cuando graba 99 fotos la nueva siempre se graba como 99, una y otra vez. Decido dejarlo así.
//
// Sobre Tiny Web Server http://www.webweavertech.com/ovidiu/weblog/archives/000484.html
// Para el envío de tweets, como es muy costoso en términos de memoria se utiliza una aplicación externa http://arduino-tweet.appspot.com
// Ethernet shield http://www.adafruit.com/products/201

// Para el Tiny Web Server
#include <SPI.h>
#include <Ethernet.h>
#include <Flash.h>
#include <SdFat.h>
#include <SdFatUtil.h>
#include <TinyWebServer.h>

// Para la camara
#include <VC0706.h>
#include <NewSoftSerial.h>

// Para Tweeter
#include <EthernetDNS.h>
#include <Twitter.h>

boolean file_handler(TinyWebServer& web_server);
boolean index_handler(TinyWebServer& web_server);

boolean tweetOn=false;

boolean isRelayOn=false;

long previousMillis = 0;
//Intervalo en milisegundos que permanecerá el relé encendido
long interval = 10000;


// change this to match your SD shield or module;
// Arduino Ethernet shield: pin 4
// Adafruit SD shields and modules: pin 10
// Sparkfun SD shield: pin 8
#define chipSelect 4
// This is the camera pin connection. Connect the camera TX
// to pin 2, camera RX to pin 3
NewSoftSerial cameraconnection = NewSoftSerial(2, 3);
// pass the serial connection to the camera object
VC0706 cam = VC0706(&cameraconnection);

TinyWebServer::PathHandler handlers[] = {
  // Work around Arduino's IDE preprocessor bug in handling /* inside
  // strings.
  //
  // `put_handler' is defined in TinyWebServer
   {"/1", TinyWebServer::GET, &motionOn_handler },
  {"/2", TinyWebServer::GET, &motionOff_handler },
  {"/3", TinyWebServer::GET, &tweetOn_handler },
  {"/4", TinyWebServer::GET, &tweetOff_handler },
  {"/", TinyWebServer::GET, &index_handler },
//  {"/upload/" "*", TinyWebServer::PUT, &TinyWebPutHandler::put_handler },
  {"/" "*", TinyWebServer::GET, &file_handler },
  {NULL},
};

const char* headers[] = {
//  "Content-Length",
//  NULL
};

TinyWebServer web = TinyWebServer(handlers, headers);

//boolean has_filesystem = true;
Sd2Card card;
SdVolume volume;
SdFile root;
SdFile file;

static uint8_t mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };

// Don't forget to modify the IP to an available one on your home network
byte ip[] = { 192, 168, 1, 177 };

// Your Token to Tweet (get it from http://arduino-tweet.appspot.com/)
Twitter twitter("353548191-qy3gHOQDgwxiKVW8bsRihE4rRTllZIkC4CFEdGDD");

void send_file_name(TinyWebServer& web_server, const char* filename) {
  if (!filename) {
    web_server.send_error_code(404);
//    web_server << F("Could not parse URL");
  } else {
    TinyWebServer::MimeType mime_type
      = TinyWebServer::get_mime_type_from_filename(filename);
    web_server.send_error_code(200);
    web_server.send_content_type(mime_type);
    web_server.end_headers();
    if (file.open(&root, filename, O_READ)) {
//      Serial << F("Read file "); Serial.println(filename);
      web_server.send_file(file);
      file.close();
    } else {
//      web_server << F("Could not find file: ") << filename << "\n";
    }
  }
}

boolean file_handler(TinyWebServer& web_server) {
  char* filename = TinyWebServer::get_file_from_path(web_server.get_path());
  send_file_name(web_server, filename);
  free(filename);
  return true;
}


boolean tweetOn_handler(TinyWebServer& web_server){
  tweetOn=true;
  generaIndex(web_server);
  return true;
}

boolean tweetOff_handler(TinyWebServer& web_server){
  tweetOn=false;
  generaIndex(web_server); 
  return true;
}

boolean motionOff_handler(TinyWebServer& web_server) {
//  Serial.println("Recibida peticion de motion OFF");
  cam.setMotionDetect(false);
  generaIndex(web_server); 
  return true;
}
//
boolean motionOn_handler(TinyWebServer& web_server) {
//  Serial.println("Recibida peticion de motion ON");
  cam.setMotionDetect(true);
  generaIndex(web_server); 
  return true;
}

boolean index_handler(TinyWebServer& web_server) {
//  send_file_name(web_server, "INDEX.HTM");
//  web_server.send_error_code(200);
//  web_server.end_headers();
////  web_server << F("<html><body><h1>Hola. La tarjeta contiene los siguientes ficheros:</h1></body></html>\n");
//  web_server << "<html><body>motion:";
//  if (cam.getMotionDetect())
//    web_server << "ON";
//  else
//    web_server << "OFF";
//   
//  web_server << "<br>tweet:";
//    if (tweetOn)
//    web_server << "ON";
//  else
//    web_server << "OFF";
//   
//  ListFiles(web_server, LS_SIZE);
//  web_server << "</body></html>\n"; 

  generaIndex(web_server);
  return true;
}

void generaIndex(TinyWebServer& web_server){
  web_server.send_error_code(200);
  web_server.end_headers();
//  web_server << F("<html><body><h1>Hola. La tarjeta contiene los siguientes ficheros:</h1></body></html>\n");
  web_server << "<html><body>mot:";
  if (cam.getMotionDetect())
    web_server << "ON";
  else
    web_server << "OFF";
   
  web_server << "<br>tw:";
    if (tweetOn)
    web_server << "ON";
  else
    web_server << "OFF";
   
  ListFiles(web_server, LS_SIZE);
  web_server << "</body></html>";  
}

void ListFiles(TinyWebServer& web_server, uint8_t flags) {
  // This code is just copied from SdFile.cpp in the SDFat library
  // and tweaked to print to the client output in html!
  dir_t p;
 
  root.rewind();
  web_server << "<ul>";
  while (root.readDir(p) > 0) {
    // done if past last used entry
    if (p.name[0] == DIR_NAME_FREE) break;

    // skip deleted entry and entries for . and ..
    if (p.name[0] == DIR_NAME_DELETED || p.name[0] == '.') continue;

    // only list subdirectories and files
    if (!DIR_IS_FILE_OR_SUBDIR(&p)) continue;



    // print any indent spaces
    web_server << "<li><a href=\"";
    for (uint8_t i = 0; i < 11; i++) {
      if (p.name[i] == ' ') continue;
      if (i == 8) {
        web_server << ".";
      }
      web_server << p.name[i];
    }
    web_server << "\">";
   
    // print file name with possible blank fill
    for (uint8_t i = 0; i < 11; i++) {
      if (p.name[i] == ' ') continue;
      if (i == 8) {
        web_server << ".";
      }
      web_server << p.name[i];
    }
   
    web_server << "</a>";
   
    if (DIR_IS_SUBDIR(&p)) {
      web_server << "/";
    }

    // print modify date/time if requested
    if (flags & LS_DATE) {
       root.printFatDate(p.lastWriteDate);
       web_server << " ";
       root.printFatTime(p.lastWriteTime);
    }
    // print size if requested
    if (!DIR_IS_SUBDIR(&p) && (flags & LS_SIZE)) {
      web_server << " ";
      web_server << p.fileSize;
    }
    web_server << "</li>";
  }
  web_server << "</ul>";
}

//void file_uploader_handler(TinyWebServer& web_server,
//               TinyWebPutHandler::PutAction action,
//               char* buffer, int size) {
//  static uint32_t start_time;
//  static uint32_t total_size;
//
//  switch (action) {
//  case TinyWebPutHandler::START:
//    start_time = millis();
//    total_size = 0;
//    if (!file.isOpen()) {
//      // File is not opened, create it. First obtain the desired name
//      // from the request path.
//      char* fname = web_server.get_file_from_path(web_server.get_path());
//      if (fname) {
////    Serial << F("Creating ") << fname << "\n";
//    file.open(&root, fname, O_CREAT | O_WRITE | O_TRUNC);
//    free(fname);
//      }
//    }
//    break;
//
//  case TinyWebPutHandler::WRITE:
//    if (file.isOpen()) {
//      file.write(buffer, size);
//      total_size += size;
//    }
//    break;
//
//  case TinyWebPutHandler::END:
//    file.sync();
////    Serial << F("Wrote ") << file.fileSize() << F(" bytes in ")
////       << millis() - start_time << F(" millis (received ")
////           << total_size << F(" bytes)\n");
//    file.close();
//  }
//}

void inicializaCamara(){
//  Serial.println("VC0706 Camera test");
   
  // Try to locate the camera
  cam.begin();
//  if (cam.begin()) {
////    Serial.println("Camera Found:");
//  } else {
////    Serial.println("No camera found?");
//    return;
//  }
  // Print out the camera version information (Realmente no es optional)
  char *reply = cam.getVersion();
//  if (reply == 0) {
////    Serial.print("Failed to get version");
//  } else {
////    Serial.println("-----------------");
////    Serial.print(reply);
////    Serial.println("-----------------");
//  }

  // Set the picture size - you can choose one of 640x480, 320x240 or 160x120
  // Remember that bigger pictures take longer to transmit!
 
//  cam.setImageSize(VC0706_640x480);        // biggest
//  cam.setImageSize(VC0706_320x240);        // medium
  cam.setImageSize(VC0706_160x120);          // small

  // You can read the size back from the camera (optional, but maybe useful?)
//  uint8_t imgsize = cam.getImageSize();
//  Serial.print("Image size: ");
//  if (imgsize == VC0706_640x480) Serial.println("640x480");
//  if (imgsize == VC0706_320x240) Serial.println("320x240");
//  if (imgsize == VC0706_160x120) Serial.println("160x120");


  //  Motion detection system can alert you when the camera 'sees' motion!
  cam.setMotionDetect(true);           // turn it on
  //cam.setMotionDetect(false);        // turn it off   (default)

  // You can also verify whether motion detection is active!
//  Serial.print("Motion detection is ");
//  if (cam.getMotionDetect())
////    Serial.println("ON");
//  else
//    Serial.println("OFF");
}

void tomaFoto(){
//   Serial.println("Motion!");  
   cam.setMotionDetect(false);
  
   cam.takePicture();
//  if (! cam.takePicture())
////    Serial.println("Failed to snap!");
//  else
//    Serial.println("Picture taken!");
 
  char filename[13];
  strcpy(filename, "IMAGE00.JPG");
  
  for (int i = 0; i < 100; i++) {
    filename[5] = '0' + i/10;
    filename[6] = '0' + i%10;
    // create if does not exist, do not open existing, write, sync after write
    if (! root.exists(filename)) {
      break;
    }
  }
 
  SdFile imgFile;
  imgFile.open(&root, filename, O_CREAT | O_WRITE | O_TRUNC);
 
  uint16_t jpglen = cam.frameLength();
//  Serial.print(jpglen, DEC);
//  Serial.println(" byte image");
//
//  Serial.print("Writing image to "); Serial.print(filename);
 
  while (jpglen != 0) {
    // read 64 bytes at a time;
    // cambio a 32 bytes el buffer para que funciones
    uint8_t *buffer;
    uint8_t bytesToRead = min(32, jpglen);
    buffer = cam.readPicture(bytesToRead);
    imgFile.write(buffer, bytesToRead);

    //Serial.print("Read ");  Serial.print(bytesToRead, DEC); Serial.println(" bytes");

    jpglen -= bytesToRead;
  }
  imgFile.close();
//  Serial.println("...Done!");
  cam.resumeVideo();
  cam.setMotionDetect(true);
}

void enviaTweet(){
  if (twitter.post("B")){
    twitter.wait();
  }
}

void relayOn(){
    digitalWrite(6, HIGH);
    isRelayOn=true;
    previousMillis = millis();
}

void relayOff(){
    digitalWrite(6, LOW);
    isRelayOn=false;   
}
 

void setup() {
//  Serial.begin(9600);
//  Serial << F("Free RAM: ") << FreeRam() << "\n";

  // initialize the SD card
//  Serial << F("Setting up SD card...\n");
  pinMode(10, OUTPUT); // set the SS pin as an output (necessary!)
  digitalWrite(10, HIGH); // but turn off the W5100 chip!
  card.init(SPI_FULL_SPEED, 4);
//  if (!card.init(SPI_FULL_SPEED, 4)) {
////    Serial << F("card failed\n");
//    has_filesystem = false;
//  }
  // initialize a FAT volume
  volume.init(&card);
//  if (!volume.init(&card)) {
////    Serial << F("vol.init failed!\n");
//    has_filesystem = false;
//  }

  root.openRoot(&volume);
//  if (!root.openRoot(&volume)) {
////    Serial << F("openRoot failed");
//    has_filesystem = false;
//  }

//  if (has_filesystem) {
//    // Assign our function to `upload_handler_fn'.
//    TinyWebPutHandler::put_handler_fn = file_uploader_handler;
//  }

//  Serial << F("Setting up the Ethernet card...\n");
  Ethernet.begin(mac, ip);

  // Start the web server.
//  Serial << F("Web server starting...\n");
  web.begin();

//  Serial << F("Ready to accept HTTP requests.\n");
 
  inicializaCamara();
    pinMode(6, OUTPUT);
}

void loop() {

//  if (has_filesystem) {
    web.process();
//  }
 
  if (cam.motionDetected()) {
    tomaFoto();
    relayOn();
    if (tweetOn){
      enviaTweet();
      // En cuanto envio un tweet desactivo el envío
      tweetOn=false;
    }
  }
 
  if(isRelayOn && (millis()-previousMillis > interval)) {
    //Apagamos el relay si ya se ha sobrepasado encedido el intervalo estipulado
    relayOff();
  }
}