Thursday, October 11, 2012

RemoteCamera

This is an Arduino-based camera that sends images wirelessly to your computer. In addition to the Grove base shield - used to connect the camera module, a button and an LED light to the Arduino - we need an SD card shield to save the photos we take, and a Bluetooth shield to send the images off.

A Processing sketch on the computer receives the sent picture, saves it to the hard drive and displays it in the program window. In addition to using the hardware button connected to the Arduino, photos can be taken directly from the computer using the on-screen button or the space bar.

This camera will be part of a garden explorer robot that I'm building, inspired by the Mars Curiosity rover. I'll be adding a time-lapse feature and motion trigger.

Parts:
-Ardiuno Uno R3
-Seeed SD Card Shield
-Seeed Bluetooth Shield
-Grove Base Shield
-Grove Serial JPEG Camera
-Grove Button
-Grove LED
-Grove 4-Pin Cables
-Battery Pack
Suppliers:
Trossen Robotics
Epic Tinker
Seeed Studios

Make sure your portName in the Processing sketch matches the blueToothName in Arduino. And, please make sure this name sticks out - don't just name them "Bluetooth" - or the automatic selector might open the wrong port.

The Arduino sends out a series of 3-letter commands to let Processing know how to update the on-screen status and when to start and stop saving the received jpeg ("CAM" when the camera has finished taking its picture, "PIC" when it starts sending jpeg data and "EOF" at the end of the file). I plan on having error messages sent this way, too.

Photos are saved sequentially in a folder called "rcamera" on the SD card. The Processing sketch creates a new time-stamped folder for each session. These are also kept in "rcamera" within the remotePhoto sketch folder.


Arduino:
 #include <SD.h>  
 #include <SoftwareSerial.h>
  
 File photoFile, textFile;
  
 const int blueToothRx = 6;  
 const int blueToothTx = 7;  
 SoftwareSerial blueToothSerial(blueToothRx,blueToothTx);
 String blueToothName = "DreaduinoBluetooth";
  
 const int photoButtonPin = 5;  
 const int ledPin = 4;  
 const int sdCardPin = 10;               // cs pin of sd card shield
  
 const int photoBufferSize = 96;  
 unsigned int photoSize = 0;  
 int readData = 0;  
 boolean firstPhoto = true;  
 int photoNumber = 0;  
 char photoName[] = "rcamera/pic00000.jpg";  

 char cameraReset[] = {  
  0x56,0x00,0x26,0x00};                  // reset camera  
 char cameraCapture[] = {     
  0x56,0x00,0x36,0x01,0x00};             // take picture  
 char cameraContinue[] = {        
  0x56,0x00,0x36,0x01,0x02};             // take next picture  
 char cameraGetSize[] = {  
  0x56,0x00,0x34,0x01,0x00};             // read jpg file size  
 char cameraGetData[] = {                // read jpg data  
  0x56,0x00,0x32,0x0c,0x00,0x0a,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0a};

   
 void setup()  
 {  
  Serial.begin(115200);
   
  pinMode(blueToothRx, INPUT);  
  pinMode(blueToothTx, OUTPUT);  
  pinMode(photoButtonPin,INPUT);  
  pinMode(ledPin,OUTPUT);  
  pinMode(sdCardPin,OUTPUT);
   
  blueToothSetup();  
  sdCardSetup();  
  checkLastFile();  
  readLastFile();  
  cameraSetup();  
  ledFlash();   
 }   


 void loop()  
 {   
  readPhotoPin();  
  readBlueTooth();   
 }  
 void readPhotoPin()   
 {   
  if(digitalRead(photoButtonPin) == HIGH){  
   delay(50);                                //debounce  
   if(digitalRead(photoButtonPin) == HIGH){  
    takePhoto();    
   }  
  }   
 }

  
 void readBlueTooth()  
 {   
  if (blueToothSerial.available()) {   
   readData = blueToothSerial.read();  
   if (readData == 80)takePhoto();   
  }  
 }  


 void takePhoto()   
 {  
  if(firstPhoto) {  
   cameraCaptureImage();  
   firstPhoto = false;  
  }  
  else cameraContinueCapture();
  
  ledFlash();  

  cameraReadData(); 
 
  ledFlash();  
 
  sendPhoto(); 
 
  ledFlash();   
 }    


 void savePhoto(File &photoFile,int toBeReadSize)  
 {  
  char readSize = 0;  
  for(char i = 0;i < 5;i ++){      //read "ready to send" signal  
   Serial.read();  
  }  
  while(readSize < toBeReadSize){  //read and store the jpeg data  
   photoFile.write(Serial.read());  
   readSize++;  
  }  
  for(char i = 0;i < 5;i ++){      //read "sussessfully sent" signal  
   Serial.read();  
  }  
 }  


 void sendPhoto()  
 {   
  Serial.println("sending photo");      
  blueToothSerial.print("PIC"); 
 
  File photoFile = SD.open(photoName);
  
  if (photoFile) {  
   ledOn();  
   while (photoFile.position() < photoFile.size()) {  
    blueToothSerial.write(photoFile.read());     
   }  
   photoFile.close();
    
   Serial.println("photo sent");      
   blueToothSerial.print("EOF");
  
   saveLastFile(); 
 
   ledOff();   
  }   
  else {  
   Serial.println("error sending photo");  
  }           
 }  


 ////////////////////////////////////////////////////////////////////////////  
 // bluetooth  


 void blueToothSetup()  
 {  
  blueToothSerial.begin(115200);  
  blueToothSerial.print("\r\n+STBD=115200\r\n ");   
  blueToothSerial.print("\r\n+STWMOD=0\r\n");                 // set bluetooth to work in slave mode   
  blueToothSerial.print("\r\n+STNA="+blueToothName+"\r\n");   // set bluetooth name  
  blueToothSerial.print("\r\n+STOAUT=1\r\n");                 // permit paired device to connect  
  blueToothSerial.print("\r\n+STAUTO=0\r\n");                 // auto-connection should be forbidden here  
  delay(2000);                                                // required delay  
  blueToothSerial.print("\r\n+INQ=1\r\n");                    // make bluetooth inquirable   
  Serial.println("dreaduino bluetooth is inquirable");   
  delay(2000);                                                // required delay  
  blueToothSerial.flush();  
 }

  
 ////////////////////////////////////////////////////////////////////////////  
 // sd card  


 void sdCardSetup()  
 {   
  Serial.println("initializing sd card"); 
  
  if (!SD.begin(sdCardPin)) {  
   Serial.print("initialzation failed");  
   return;  
  }   
  Serial.println("sd initialization done");   
 }  
 void checkLastFile()   
 {  
  if (!SD.exists("rcamera/lastfile.txt")) {  
   SD.mkdir("/rcamera");  
   textFile = SD.open("rcamera/lastfile.txt", FILE_WRITE);  
   textFile.seek(0);    
   textFile.write(photoName);  
   textFile.close();   
  }    
 } 

 
 void readLastFile()  
 {  
  textFile = SD.open("rcamera/lastfile.txt", FILE_READ);  
  for (int i=0; i < 16; i++){  
   photoName[i] = textFile.read();     
  } 
  textFile.close(); 
  photoNumber = ((photoName[11]-48)*10000);  
  photoNumber = photoNumber + ((photoName[12]-48)*1000);  
  photoNumber = photoNumber + ((photoName[13]-48)*100);  
  photoNumber = photoNumber + ((photoName[14]-48)*10);  
  photoNumber = photoNumber + ((photoName[15]-48));      
  photoNumber ++;   
 } 

 
 void saveLastFile()  
 {  
  textFile = SD.open("rcamera/lastfile.txt", FILE_WRITE);  
  if(!textFile){  
   Serial.println("failed to open text file");  
  }  
  else{  
   textFile.seek(0);    
   textFile.write(photoName);  
  }  
  textFile.close();   
 }

  
 ////////////////////////////////////////////////////////////////////////////  
 // serial camera 

 
 void cameraSetup()  
 {   
  sendCameraCmd(cameraReset,4);  
  delay(1000);  
  while(Serial.available() > 0)Serial.write(Serial.read());  
  Serial.println("camera initialization done");  
 } 

 
 void cameraCaptureImage()  
 {   
  sendCameraCmd(cameraCapture,5);  
  delay(50);  
  while(Serial.available()){  
   Serial.read();  
  }  
 }  


 void cameraContinueCapture()  
 {  
  sendCameraCmd(cameraContinue,5);  
  delay(50);  
  while(Serial.available() > 0){  
   Serial.write(Serial.read());  
  }  
 }  


 void cameraReadData()  
 {  
  unsigned int photoSize;  
  sendCameraCmd(cameraGetSize,5);  
  delay(50);  
  for(char i = 0; i < 7; i++){  
   Serial.read();  
  }  
  photoSize = 0;  
  int high = Serial.read();  
  photoSize |= high ;  
  int low = Serial.read();  
  photoSize = photoSize << 8 ;  
  photoSize |= low ;
  
  unsigned int addr = 0;  
  cameraGetData[8] = 0;  
  cameraGetData[9] = 0;
  unsigned int count = photoSize / photoBufferSize;  
  char tail = photoSize % photoBufferSize;  
  cameraGetData[13] = photoBufferSize;  

  photoName[11] = photoNumber/10000 + '0';  
  photoName[12] = photoNumber/1000 + '0';  
  photoName[13] = photoNumber/100 + '0';  
  photoName[14] = photoNumber/10 + '0';  
  photoName[15] = photoNumber%10 + '0'; 
 
  Serial.println("saving photo");      
  blueToothSerial.print("CAM"); 
 
  photoFile = SD.open(photoName, FILE_WRITE);  
  
  ledOn();
  
  if(!photoFile){  
   Serial.println("failed to open photo file");  
  }  
  else{  
   for(char i = 0; i < count; i++){      //get and save count*photoBufferSize data  
    sendCameraCmd(cameraGetData,16);  
    delay(10);  
    savePhoto(photoFile, photoBufferSize);  
    addr += photoBufferSize;  
    cameraGetData[8] = addr >> 8;  
    cameraGetData[9] = addr & 0x00FF;  
   }  
   cameraGetData[13] = tail;               //get rest of the image data  
   sendCameraCmd(cameraGetData,16);  
   delay(10);  
   savePhoto(photoFile, tail);     
  }  
  photoFile.close();
  
  ledOff();  

  photoNumber ++;    
 }  
 void sendCameraCmd(char cmd[] ,int cmd_size)  
 {  
  for(char i = 0; i < cmd_size; i++)Serial.print(cmd[i]);   
 }

  
 ////////////////////////////////////////////////////////////////////////////  
 // led light  


 void ledOn()  
 {  
  digitalWrite(ledPin,HIGH);   
 }  


 void ledOff()  
 {  
  digitalWrite(ledPin,LOW);   
 }  


 void ledFlash()  
 {  
  digitalWrite(ledPin,HIGH);   
  delay(60);   
  digitalWrite(ledPin,LOW);   
  delay(60);   
  digitalWrite(ledPin,HIGH);   
  delay(60);   
  digitalWrite(ledPin,LOW);  
  delay(60);   
 }  

Processing:
 import processing.serial.*;  
 import controlP5.*;
  
 Serial dreadPort;  
 OutputStream output;  
 PImage img;

 ControlP5 controlP5;  
 Button photoButton;  
 PFont font = createFont("Arial", 14);
  
 String photoName;  
 String photoNameNumber;  
 int photoNumber = 1;  
 boolean cameraSave = false;  
 boolean photoOpen = false;  
 boolean photoSwitch = false;
  
 String filePath = "remoteCamera";  
 String fileStatus;  
 String folderName = timeStamp();

 int frameLocationX = 50;  
 int frameLocationY = 50;  
 int frameLocationSet = 0;
    
 String[] serialString;  
 String serialCheck;  
 String portName = "DreaduinoBluetooth";  
 int portNumber;  
 int serialIndex;
  
 int dataIn = 0;  
 int charC = 67;  
 int charA = 65;  
 int charM = 77;  
 int charP = 80;  
 int charI = 73;  
 int charE = 69;  
 int charO = 79;  
 int charF = 70;  
 int[] checkData = new int[3];  
 int[] checkEOF = new int[3]; 

 
 void setup()  
 {  
  size(640, 580);  
  background(40);   
  frame.setResizable(true);
  
  findSerialPort();
  
  dreadPort = new Serial(this, Serial.list()[portNumber], 115200);  
  dreadPort.clear();
  
  setupControlP5();
  
  statusUpdate(); 
 
  println("remoteCamera");  
  println("ready");  
 }  


 void draw()   
 {  
  if (frameLocationSet < 2) {  
   frame.setLocation(frameLocationX, frameLocationY);  
   frameLocationSet ++;  
  }   
  if (photoSwitch) remotePhoto();  
 } 

 
 void serialEvent(Serial myPort)   
 {   
  dataIn = myPort.read();
  
  checkData[2] = checkData[1];  
  checkData[1] = checkData[0];  
  checkData[0] = dataIn; 
 
  if (checkData[2] == charC && checkData[1] == charA && checkData[0] == charM) {    
   photoNameNumber = nf(photoNumber, 5);  
   photoName = ("img" + photoNameNumber + ".jpg");  
   filePath = (folderName + "/" + photoName);  
   println("saving");  
   cameraSave = true;  
   statusUpdate();  
  } 
 
  if (checkData[2] == charP && checkData[1] == charI && checkData[0] == charC) {    
   println("sending");  
   openPhoto();  
   return;  
  } 
 
  if (photoOpen) writePhoto();  
 }  


 void findSerialPort()  
 {  
  serialString = Serial.list(); 
 
  println(serialString);   

  for (int i = serialString.length - 1; i > 0; i--) {  
   serialCheck = serialString[i];  
   serialIndex = serialCheck.indexOf(portName);
  
   if (serialIndex > -1) portNumber = i;  
  }  
 }  


 void statusUpdate()   
 {  
  noStroke();  
  fill(40);   
  rect(0, 481, 640, 100);
  
  fill(255);  
  text(filePath, 300, 512);
   
  if (cameraSave) fileStatus = "saving";
  
  if (photoOpen) fileStatus = "sending"; 
 
  if (!cameraSave && !photoOpen) fileStatus = "ready";
   
  fill(255);  
  text(fileStatus, 300, 547);  
 }  


 void openPhoto()   
 {  
  if (!photoOpen) {  
   cameraSave = false;  
   photoOpen = true;  
   output = createOutput("rcamera/" + filePath);  
   statusUpdate();  
  }  
 }  


 void writePhoto()   
 {  
  try {  
   output.write(dataIn);  

   checkEOF[2] = checkEOF[1];  
   checkEOF[1] = checkEOF[0];  
   checkEOF[0] = dataIn;  

   if (checkEOF[2] == charE && checkEOF[1] == charO && checkEOF[0] == charF) {    
    closePhoto();  
    println(photoName + " received");  
    println("ready");  
    photoOpen = false;  
    statusUpdate();  
   }  
  }  
  catch (IOException e) {  
   e.printStackTrace();  
  }  
 }  

 
 void closePhoto()   
 {  
  try {  
   output.flush();  
   output.close();
  
   photoOpen = false; 
 
   statusUpdate();
  
   photoNumber ++;
  
   displayPhoto();  
  }   
  catch (IOException e) {  
   e.printStackTrace();  
  }  
 }  


 void displayPhoto()   
 {  
  noStroke();  
  fill(40);   
  rect(0, 0, 640, 480); 
 
  img = loadImage("rcamera/" + filePath);  
  image(img, 0, 0);  
 } 

 
 void setupControlP5()  
 {  
  controlP5 = new ControlP5(this);  
  controlP5.setControlFont(font); 
 
  photoButton = controlP5.addButton("photo", 0, 120, 500, 58, 58);  
  photoButton.setSwitch(photoSwitch);  
  photoButton.setColorBackground(color(150, 0, 0, 255));  
 } 

 
 void controlEvent(ControlEvent theEvent)  
 {  
  if (theEvent.controller().name()=="photo") remotePhoto();  
 }  


 void remotePhoto() {  
  dreadPort.write('P');
  
  photoSwitch = false;  
 }  


 void keyPressed() {  
  if (key == ' ' && !photoSwitch) {  
   
   photoSwitch = true; 
 
   remotePhoto();  
  }  
 } 


 String timeStamp() {  
  String timestamp = year()+"_"+month()+"_"+day()+"_"+hour()+"_"+minute();  
  return timestamp;  
 }   

Wednesday, October 10, 2012

Arduino + Processing: Automatic Serial Port Selection

I find that using Bluetooth to connect an Arduino with Processing on a MacBook can be frustrating. The serial port number tends to jump around on me as I disconnect and reconnect. Since I want the first port on the serial list with my Arduino's name, I scan the list backwards - counting down in the 'for()' loop - using 'indexOf()' to search each line for the Arduino's name. If the line contains the text I'm looking for, that line number becomes the chosen port number. Once I've checked each port, the last port number assigned is the first Arduino on the list.

Processing:
 import processing.serial.*;  
   
 Serial dreadPort;  
   
 String[] serialString;  
 String serialCheck;  
 String portName = "DreaduinoBluetooth";  
 int portNumber;  
 int serialIndex;  
    
   
 void setup() {
  
  findSerialPort(); 
 
  dreadPort = new Serial(this, Serial.list()[portNumber], 115200);  
  dreadPort.clear();  
 }  
   
     
 void draw() {  
 }  
   
     
 void findSerialPort() {
  
  serialString = Serial.list();  
   
  println(serialString);   
   
  for (int i = serialString.length - 1; i > 0; i--) {  
   
   serialCheck = serialString[i];  
   serialIndex = serialCheck.indexOf(portName);  
   
   if (serialIndex > -1) portNumber = i;  
  }  
 }    

Sunday, October 7, 2012

Arduino + Processing: JPEG Serial Transfer

I had a hard time figuring out how to send a jpeg file, over serial, from Arduino's SD card to Processing on my laptop. In the end, I found something called 'createOutput' in Processing and that seems to get the job done:

-Run the Processing sketch.
-Press the Arduino button to start sending jpeg.
-When transfer is complete (LED turns off), press any key in Processing sketch to finish saving file.

Arduino:
 #include <SD.h>   
  
 File photoFile;   
 const int buttonPin = 7;   
 const int ledPin = 5;   
   
   
 void setup(){   
   
  Serial.begin(115200);   
   
  pinMode(buttonPin,INPUT);   
  pinMode(ledPin,OUTPUT);   
   
  //Serial.println("initializing sd card");   
  pinMode(10,OUTPUT);     // CS pin of SD Card Shield   
   
  if (!SD.begin(10)) {   
   Serial.print("sd initialzation failed");   
   return;   
  }   
  //Serial.println("sd initialization done");   
 }   
   
   
 void loop(){   
   
  while(1){   
   // Serial.println("press the button to send picture");   
   Serial.flush();     
   
   while(digitalRead(buttonPin) == LOW);   
   if(digitalRead(buttonPin) == HIGH){   
    delay(50);   
   
    if(digitalRead(buttonPin) == HIGH){   
     delay(200);   
     File photoFile = SD.open("pic02.jpg");   
   
     if (photoFile) {   
      while (photoFile.position() < photoFile.size()) {   
   
       digitalWrite(ledPin,HIGH);              
       Serial.write(photoFile.read());   
      }   
   
      photoFile.close();   
      digitalWrite(ledPin,LOW);    
     }    
   
     else {   
      Serial.println("error sending photo");   
     }         
    }   
    //Serial.println("photo sent");    
   }   
  }   
 }     

Processing:
 import processing.serial.*;  
   
 Serial myPort;  
 OutputStream output;  
   
   
 void setup() {  
   
  size(320, 240);  
   
  //println( Serial.list() );  
  myPort = new Serial( this, Serial.list()[0], 115200);  
  myPort.clear();  
   
  output = createOutput("pic02.jpg");  
 }  
   
   
 void draw() {  
   
  try {   
   while ( myPort.available () > 0 ) {  
    output.write(myPort.read());  
   }  
  }   
  catch (IOException e) {  
   e.printStackTrace();  
  }  
 }  
   
   
 void keyPressed() {  
   
  try {   
   output.flush(); // Writes the remaining data to the file  
   output.close(); // Finishes the file  
  }   
   
  catch (IOException e) {  
   e.printStackTrace();  
  }  
 }    

Dreaduino: Tinkering With Open-Source Hardware

Ciao. I have been playing with Arduino, the tiny Italian computer, for a few months now, and enjoying it thoroughly. This single-board microcontroller is meant to be easily modified into electronic hardware of your own design. Just plug in a few electronic inputs (buttons, knobs, motion sensors, etc.) and outputs (lights, buzzers, screens). Then, upload a program to the board’s flash memory telling Arduino what to do when a certain button is pressed or sensor reaches a specific reading.

The standard size and placement of the board’s connectors means that anyone can extend the functionality of Arduino by creating a “shield” – another circuit board that fits snugly on top and adds specific input and display options.

More importantly, Arduino is an open-source project with the board’s design and accompanying software given away freely. Anyone may build or program an Arduino clone or shield without risk of patent or copyright infringement.

I decided to begin my hardware adventures with a preassembled Arduino Uno R3 and Seeed Studio’s Grove base shield that makes use of 4-pin JST connectors to simplify the entire process of building a working electronic circuit. This way, I don’t need to bother with a soldering iron to get my prototypes working.

I have been learning a great deal as I struggle with a few projects that I’ll try to share through video and bits of code that I have managed to stitch together.