Saturday, September 27, 2008

GPS on Nikon - things to watchout for

There are many GPS solutions available, the details are important.


Question 1: Where does it get the power from?
The GPS does not require a lot of power but still, an external battery might make sense. Optimum obviously would be to have the option for both, camera battery or GPS own battery.

Question 2: What happens if the camera is turned off?
Most solutions keep running! No matter if it is a bluetooth solution or a GPS connected directly to it! The reason this is done that way is so you do not have to wait for multiple seconds until the GPS has a satellite fix, it is running non-stop. The downside is, you forget to turn it off - does it have a switch even??? - and a few days later your camera battery is dead. I don't like that at all. That is one of the reasons why I did not go for the blue2can but the www.foolography.com one, this one does switch off if you turn the camera off. And actually, keeping the GPS running does not make sense anyway. If you turn off the GPS it keeps the latest satellite data in memory and if it is turned on again shortly after, it takes just a few seconds to get a fix - not minutes.

Question 3: Bluetooth or direct GPS?
The bluetooth solution is times smaller, does not have any cables and can remain attached all the time. The power consumption is marginal.
The GPS receiver can be any standard one, if you want a new one no problem, if you want a GPS mouse that supports logging as well - sure, why not. You just have to pair it again. With the bluetooth solution obviously the GPS has its own battery and its own ON/OFF switch. What I have chosen is a GPS mouse that goes into a standby mode if it is not moved for 10 minutes. So I turn off the camera, turn it on again a few minutes later to take another picture - the GPS data I get immediately as the GPS receiver was never turned off. Back home I just put the camera and the GPS into its storage box, 10 minutes later the GPS shuts itself down automatically. Very neat.
The major advantage of the direct GPS is you can use one that has a built in compass and store this information as well in the EXIF data of the pictures.

Question 4: What if there is no GPS reception while taking a picture?
Imagine you walk into a house and take a few pictures there. No GPS signal shall be available here. You will see that GPS icon in the camera flashing. The alternative would be the GPS device keeps sending the last known position. Don't know what's better in such a case. Personally, if GPS information is important I check if the camera has data. And if it is not important, I do not want wrong or old data. On the other hand....

Motor Tripod - Future Enhancements

Grab the GPS signal, convert it to 5V level and send it to the Nikon Camera's 10 Pin plug. http://www.k-i-s.net/article.php?article=20 With that, whenever a picture is taken, the GPS position is part of the EXIF data.


Let the microcontroller release the Camera Shutter. This is a functionality of above 10pin plug and would allow taking pictures in intervals, e.g. every 20 seconds. Or a a button is implemented on the remote XBee so you can operate the shutter from remote.

Another otpion would be to use that tripod for Gigapixel images. The you point the camera into one direction, press start and then the camera takes one picture, moves the head by a few degrees, takes another picture, etc and at the end all pictured are merged into one image with extreme high resolution. http://www.tawbaware.com/maxlyons/gigapixel.htm


Another option would be to remove the GPS tracking function and rather remote control the tripod from the computer. The hardware for this is simple, the droids board has an optional USB connector instead of the XBee module. Or we use an USB-to-XBee adapter available from that company as well. You would send some serial commands to the board, the software parses those commands and moves the camera head accordingly. No big challange, just the parser code has to be modified. And then via Nikons Camera Control software you could view the life image on the computer. There seems to be some open libraries (http://www.gphoto.org/) as well to build an integrated solution. Or you simply use a webcam.



Apart from these enhancements there is one particular issue. The microcontroller does not know the servo's initial position. So all it can do is setting the position to central at the beginning at that results in one very quick movement. As the servos are very strong, that does not do the entire construction very good, not to mention the force on the camera. The servos do support programming and with that you can define speed or read the current servo position. And that would be all that is needed. Implementing the protocol would take a while so it will not be possible for me. Right now, I turn on the tripod, let it move to the initial position and then put the camera onto it.



Next steps:

Check with a scope the servo speed implementation works flawlessly. It is not perfect yet as you can see in the video.

See what needs to be done to take the GPS signal and send it to the camera.

Thursday, September 25, 2008

main.c file

#include <p18cxxx.h>
#include <delays.h>
#include <timers.h>
#include <stdlib.h>
#include <usart.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <adc.h>
#include <pwm.h>
#include <i2c.h>
#include "Tracker.h"

// config fuse
#pragma config OSC = HSPLL
#pragma config WDT = OFF
#pragma config PWRT = ON
#pragma config LVP = OFF
#pragma config BOREN = ON
#pragma config BORV = 1
#pragma config XINST = OFF

#define pi 3.14159265359

#define LC PORTAbits.RA4 // Led com

#define HEIGHT_MODE_VARIABLE 0
#define HEIGHT_MODE_FIX 1

int PWM1,PWM2;
int S1,S2;
long S1_zero, S2_zero, counter, S1_offset, S2_offset, S1_old, S2_old;
unsigned int last_pos_counter, pos_counter;
char str[6],TXcount,valid_row,i;
char strcom[80];
char height_mode, manual_coords;

long longitude_d, longitude_m, latitude_d, latitude_m, h;
long longitude_dc, longitude_mc, latitude_dc, latitude_mc, hc;
long dx, dy, dz, cos100_longitude, w, w_zero, v, v_zero, dw;
char key_pressed, last_key_pressed;
char buf[20];
char state, sat_in_view0, sat_in_view1, ind_quality;
unsigned char checksum;
char ignore_for_checksum;

#pragma code high_vector=0x08
void interrupt_at_high_vector(void)
{
_asm GOTO ISRgest _endasm
}
#pragma code
#pragma interrupt ISRgest


void main()
{

LATA = 0x00;
LATB = 0x00;
LATC = 0x00;

TRISA = 0b11101111; // all input, RA4 GPIO
TRISB = 0b11111100;   // 6 GPIO (RB7-RB2), 2 Servo (RB0 & RB1)
TRISC = 0b11011000; // I2C, Serial, PWM

ADCON1 = 0b00001111; // ADC off
ADCON2 = 0b10110010; // 16 TAD, Fosc/32
ADCON0 = 0x00; // ADC OFF

flashLED(5);

BAUDCONbits.BRG16 = 1; // baud rate generator a 16 bit
OpenUSART (USART_TX_INT_OFF &
USART_RX_INT_ON &   
USART_ASYNCH_MODE &
USART_EIGHT_BIT &
USART_CONT_RX &
USART_BRGH_HIGH, 1041);

// Timer
OpenTimer0( TIMER_INT_OFF & T0_16BIT & T0_SOURCE_INT & T0_PS_1_1); // servo frame
OpenTimer1(TIMER_INT_ON & T1_PS_1_1 & T1_SOURCE_INT & T1_OSC1EN_OFF); // Servo pulse 1
OpenTimer3(TIMER_INT_ON & T3_PS_1_1 & T3_SOURCE_INT & T3_OSC1EN_OFF ); // Servo Pulse 2


//I2C
OpenI2C(MASTER, SLEW_OFF); // Activate bus I2C, Master mode 100 kbits
SSPADD =0x63;  //100kHz clock(63H) @40MHz (default)

T0CONbits.TMR0ON = 0;
T1CONbits.TMR1ON = 0;
T3CONbits.TMR3ON = 0;

TMR3H=TMR3L=0;
TMR1H=TMR1L=0; 
TMR0H = 0x3C; // 20 ms
TMR0L = 0xB0; 


S1=S2=1500;
S1_offset=S2_offset=0;
S1_old=S2_old=1500;

last_pos_counter=0;
pos_counter=0;
last_key_pressed=0;

valid_row = -2;
TXcount = 0;
i=0;
height_mode = HEIGHT_MODE_VARIABLE;


/* Enable interrupt priority */
RCONbits.IPEN = 1;

/* Make receive interrupt high priority */
IPR1bits.RCIP = 1;

// interrupt
INTCON = 0;
INTCONbits.GIE = 1;
INTCONbits.PEIE = 1;

LCDClearScreen();
LCDprint2((const rom char far *)"Starting....");

Servo();

state=0;
manual_coords = 0;

// wait until GPS has acquired the current position and then turn on the LED. As long as we are in this mode and a key was pressed
// we treat that as signal to store the current GPS position and flash the LED 10 times as visual indicator.
// LED off means GPS is not ready yet
// LED on means GPS has a trusted position
while(i == 0) {
   if (valid_row == -2) {
     LCDClearScreen();
     LCDprint2((const rom char far *)"No Serial Data yet");
     LCDprintc(13);
     LCDprint2((const rom char far *)"program XBee 1: 1");
     LCDprintc(13);
     LCDprint2((const rom char far *)"program XBee 2: 2");
     valid_row = -1;
   } else if ((valid_row != -1) && (state == 0)) {
     LCDClearScreen();
     LCDprint2((const rom char far *)"Serial Data received");
     state=1;
   } else if ((pos_counter != 0) && (ind_quality == '0') && (state != 2)) {
     LCDClearScreen();
     LCDprint2((const rom char far *)"GPS Sentence found, no fix yet");
     state=2;
   } else if ((pos_counter != 0) && (ind_quality != '0') && (state < 3)) {
     LCDClearScreen();
     LCDprint2((const rom char far *)"Press 5 to lock pos");
     LCDprintc(13);
     LCDprint2((const rom char far *)"Satellites: ");
     state = 3;
   }
   key_pressed = LCDKeyboardSingleChar();
   if (key_pressed == '#') {
     EnterCoords();
     state = 2;
   } else if (valid_row == -1) {
     if (key_pressed == '1') {
       ProgramXBee((const rom char far *)"4E3A", (const rom char far *)"4E3B");
     } else if (key_pressed == '2') {
       ProgramXBee((const rom char far *)"4E3B", (const rom char far *)"4E3A");
     }
   }
   if (last_pos_counter != pos_counter || manual_coords) {
     if (manual_coords == 0) GPSParse();
     manual_coords = 0;
     LCDprintc(' ');
     LCDprintc(' ');
     LCDprintc(sat_in_view0);
     LCDprintc(sat_in_view1);
     last_pos_counter = pos_counter;
   }
   if (ind_quality != '0') {
     if (key_pressed == '5') {
       i=1;
     } else {
       LC=1;
     }
   } else {
     LC=0;
   }
}

latitude_d = latitude_dc;
latitude_m = latitude_mc;
longitude_d = longitude_dc;
longitude_m = longitude_mc;
h = hc;



LCDClearScreen();
LCDprint2((const rom char far *)"Waiting for remote  position...");


cos100_longitude = (long)(cos((((double)latitude_d) + ((double)latitude_m)/60000.00) * 3.14159265f / 180.00f)*100.00f);

S1_zero = 1500;
S2_zero = 1500;
w = 9999;
v = 9999;
w_zero = w;
v_zero = v;
counter = 1;

key_pressed=0;

T0CONbits.TMR0ON = 1;
T1CONbits.TMR1ON = 1;
T3CONbits.TMR3ON = 1;


// No we are in tracking and offset mode. You can press the keys and move the servos freely.
// The first time the GPS position is considered trusted and more than 10 meters away, the servos
// start moving automatically. If it is the first time the remote location is valid, we initialize
// the angle offset assuming the current servo position already points to the remote position.
// While the servos try to follow the GPS position you can still press the buttons to move the head manually.
while(1) {
  if ((pos_counter != last_pos_counter) || manual_coords) {
    if (manual_coords == 0) GPSParse();
    manual_coords = 0;
    last_pos_counter = pos_counter;
    if (ind_quality != '0') {
      dx = getDeltaLongitude();
      dy = getDeltaLatitude();
      if (height_mode == HEIGHT_MODE_VARIABLE) {
        dz = hc - h;
      } else {
        dz = 0;
      }

      LCDClearScreen();
      sprintf (buf, "dx=%ld   ", dx/10);
      LCDprint(buf);
      sprintf (buf, "dy=%ld", dy/10);
      LCDprint(buf);
      LCDprintc(13);
      sprintf (buf, "dz=%ld   Sats: ", dz/10);
      LCDprint(buf);
      LCDprintc(sat_in_view0);
      LCDprintc(sat_in_view1);
      LCDprintc(13);
      sprintf (buf, "counter=%d", pos_counter);
      LCDprint(buf);
      LCDprintc(13);

      if ((dx*dx + dy*dy) > 10000) { // the remote position is more than 10m away, we start updating the servo position
        w = approx_atan2(dy, dx);
        v = approx_atan2(dz, isqrt(dy*dy + dx * dx));
        if (w_zero == 9999) {
          w_zero = w;
          v_zero = v;
        }
        S1_old = S1;
        S2_old = S2;
        dw = w_zero-w;
        if (dw > 18000) dw-=36000;
        if (dw < -18000) dw+=36000;
        S1_offset=(long)((dw*86)/1000); // we have degree*100, one degree is about 7us servo impuls
        S2_offset=(long)(((v-v_zero)*86)/1000);
        counter = 1;
        LC=1;
       LCDprint2((const rom char far *)"Tracking...");
      } else {
        LC=0;
      }
    } else {
      LC = 0;
    }
  } else {
    LC=0;
  }

  // instead of setting the new servo position, we let it move slowly towards the S1 = S1_zero + S1_offset position
  if (INTCONbits.TMR0IF) {
    T0CONbits.TMR0ON = 0;
    TMR0H = 0x3C; // 5 ms
    TMR0L = 0xB0; 
    INTCONbits.TMR0IF = 0;
    T0CONbits.TMR0ON = 1;

    key_pressed = LCDKeyboard();
    if (key_pressed == '6') {
      S1_zero++;
      counter = 200; // to make sure we have an immediate movement
    } else if (key_pressed == '4') {
      S1_zero--;
      counter = 200;
    } else if (key_pressed == '2') {
      S2_zero++;
      counter = 200;
    } else if (key_pressed == '8') {
      S2_zero--;
      counter = 200;
    } else if (key_pressed == '5') { // reset pos to center
      S1_zero=1500;
      S2_zero=1500;
      w_zero = w;
      v_zero = v;
      S1_offset = 0;
      S2_offset = 0;
      counter = 200;
    } else if (key_pressed == '0' && last_key_pressed != '0') { // switch height mode
      if (height_mode == HEIGHT_MODE_VARIABLE) {
        height_mode = HEIGHT_MODE_FIX;
        LCDClearScreen();
        LCDprint2((const rom char far *)"Height is constant 0");
      } else {
        height_mode = HEIGHT_MODE_VARIABLE;
        LCDClearScreen();
        LCDprint2((const rom char far *)"Height set variable");
      }
      flashLED(10);
    } else if (key_pressed == '#') {
      EnterCoords();
    }
    if (S1_zero < 900) S1_zero = 900;
    if (S1_zero > 2100) S1_zero = 2100;
    if (S2_zero < 1250) S2_zero = 1250;
    if (S2_zero > 2100) S2_zero = 2100;
    last_key_pressed = key_pressed;


    // Timer0 fires every 0.005s, every one sec we will know the new GPS position.
    // Therefore the servo should make the enitre movement in 200 steps

    S1 = (int)(S1_old - (((S1_old - (S1_zero + S1_offset))*counter)/200));
    if (S1 < 900) S1 = 900;
    if (S1 > 2100) S1 = 2100;

    S2 = (int)(S2_old - (((S2_old - (S2_zero - S2_offset))*counter)/200));
    if (S2 < 1200) S2 = 1200; // the z-Axis can move just little
    if (S2 > 2100) S2 = 2100;

    if ((PIR1bits.TMR1IF == 0) && (PIR2bits.TMR3IF == 0) && ((counter % 4) == 0)) {
      Servo();
    }

    counter++;
    if (counter > 200) {
      counter = 1;
      S1_old = S1_zero + S1_offset;
      S2_old = S2_zero - S2_offset;
      if (S1_old < 900) S1_old = 900;
      if (S1_old > 2100) S1_old = 2100;
      if (S2_old < 1250) S2_old = 1250;
      if (S2_old > 2100) S2_old = 2100;
    }
  }
} // while
}

long approx_atan2(long dist_y, long dist_x) { // returns degree times 100, so the number 6234 means 62.34°
  // Watchout, it is rotated so that north=0°, east=90°, west=-90°
  long rad100;
  long offset;

  if (dist_x == 0) {
    if (dist_y > 0) return 0;
    else return 18000;
  } else {
    rad100 = dist_y*100/dist_x;
    if (rad100 < 0) rad100 = -rad100;

    if (rad100 > 100) {
       // atan = (PI()/2-r/(0,28+r*r))/pi*180
   // offset = ((5700*rad100/(28+(rad100*rad100/100))))/100;
   offset = ((5700*rad100/(28+(rad100*rad100/100))));
    } else {
      offset = 9000-((rad100*570000)/(10000+(28*rad100*rad100/100)));
    }

    if ((dist_x > 0) && (dist_y >= 0)) return offset;
    else if ((dist_x < 0) && (dist_y >= 0)) return -offset;
    else if ((dist_x > 0) && (dist_y < 0)) return 18000-offset;
    else return offset-18000;
  }
}

long isqrt(long x) {
  long   squaredbit, remainder, root;

   if (x<1) return 0;
  
   /* Load the binary constant 01 00 00 ... 00, where the number
    * of zero bits to the right of the single one bit
    * is even, and the one bit is as far left as is consistant
    * with that condition.)
    */
   squaredbit  = (long) ((((unsigned long) ~0L) >> 1) & 
                        ~(((unsigned long) ~0L) >> 2));
   /* This portable load replaces the loop that used to be 
    * here, and was donated by  legalize@xmission.com 
    */

   /* Form bits of the answer. */
   remainder = x;  root = 0;
   while (squaredbit > 0) {
     if (remainder >= (squaredbit | root)) {
         remainder -= (squaredbit | root);
         root >>= 1; root |= squaredbit;
     } else {
         root >>= 1;
     }
     squaredbit >>= 2; 
   }

   return root;
}

void flashLED(char times) {
  for (i=0;i<times;i++) {
    LC=!LC;
    DelayMilliSeconds(500);
  }
}

long getDeltaLatitude(void) { // returns the distance in 0.1m
  // return ((latitude_m - atol(str_latitude_minute))*pi* 6378137/180/60000 + (latitude_d - atol(str_latitude_degree)) * pi * 6378137 / 180) 
  return ((latitude_m - latitude_mc)*186 + (latitude_d - latitude_dc) * 11131949)/100;
}

long getDeltaLongitude(void) { // returns the distance in 0.1m
  // return ((longitude_m - atol(str_longitude_minute))*pi* dist/180/60000 + (longitude_d - atol(str_longitude_degree)) * pi * dist / 180) 
  return ((longitude_m - longitude_mc)*186 + (longitude_d - longitude_dc) * 11131949) * cos100_longitude / 10000;
}

void Servo (void)
{
  TMR1H = (65536 - S1*10) >> 8;
  TMR1L = (65536 - S1*10); 
  LATBbits.LATB0 = 1;
 
  TMR3H = (65536 - S2*10) >> 8;
  TMR3L = (65536 - S2*10);
  LATBbits.LATB1 = 1;

  T1CONbits.TMR1ON = 1;
  T3CONbits.TMR3ON = 1;
}



void ISRgest(void) { // I.S.R.
  char data; // buffer

  if (PIR1bits.TMR1IF) {
    LATBbits.LATB0 = 0;
   
    T1CONbits.TMR1ON = 0; 
    PIR1bits.TMR1IF = 0;
  }
  if (PIR2bits.TMR3IF) {
    LATBbits.LATB1 = 0;
   
    T3CONbits.TMR3ON = 0; 
    PIR2bits.TMR3IF = 0;
  }
  if (PIR1bits.RCIF) {
    data = RCREG;
    if (data == '$') {
      TXcount = 0;
      valid_row = 1;
      checksum = 0;
      ignore_for_checksum = 0;
    } else if (valid_row == 1) {
      if (TXcount == 4) {
        if ((strcom[0] != 'G') || (strcom[1] != 'P') || (strcom[2] != 'G') || (strcom[3] != 'G') || (data != 'A')) {
          valid_row = 0;
        } else {
          strcom[TXcount] = data;
          checksum ^= data;
          TXcount++;
        }
      } else if ((data == '\n') || (data == '\r')) {
        valid_row = 0;
        if ((((strcom[TXcount-2] - '0') << 4) | (strcom[TXcount-1] - '0')) == checksum)
          pos_counter++;
      } else if (data == '*') {
        ignore_for_checksum = 1;
      } else {

        strcom[TXcount] = data;
        if (ignore_for_checksum == 0) {
          checksum ^= data;
        }
        TXcount++;
        if ( TXcount > 79 ) valid_row = 0;
      }

    } else {  // this is used to capture input received, e.g. during XBee programming
      strcom[TXcount] = data;
      TXcount++;
      if ( TXcount > 79 ) valid_row = 79;
    }
   /* Clear the interrupt flag */
    PIR1bits.RCIF = 0;
  }
}


void I2CW(char ADDS, char N1, char d1, char d2, char d3)
{

 // N1..... Bytes to send
 // ADDS... I2C Address
 // d1..... High byte
 // d2..... Low byte
 // d3..... ???

  if (N1 == 1)
  {    
   EEByteWrite(ADDS, 0,d2); // Write byte
   EEAckPolling(ADDS); // attende per ACK
  }

 if (N1 == 2)
  {    
   EEByteWrite(ADDS, d1,d2); // Write byte
   EEAckPolling(ADDS); // attende per ACK
  }

 if (N1 == 3)
  {    
   EEByteWrite(ADDS,d1,d2); // Write byte
   EEAckPolling(ADDS); // attende per ACK
    
   EEByteWrite(ADDS,(d1+1),d2); // Write byte
   EEAckPolling(ADDS); // attende per ACK
  }
}

void LCDprint(char * string)
 {
  char char_pos;
  for (char_pos=0; (string[char_pos] != 0) && (char_pos < 80); char_pos++)
   {
    I2CW(0xc6, 1, 0, string[char_pos], 0);
   }
 }

void LCDprint2(const rom char far * string)
 {
  char char_pos;
  for (char_pos=0; (string[char_pos] != 0) && (char_pos < 80); char_pos++)
   {
    I2CW(0xc6, 1, 0, string[char_pos], 0);
   }
 }

void LCDprintc(char string)
 {
  I2CW(0xc6, 1, 0, string, 0);
 }
void LCDClearScreen()
 {
  I2CW(0xc6, 1, 0, 12, 0); // clear screen
 }

char LCDKeyboard()
 {
  unsigned char let_b[16];
  EESequentialRead(0xC6,1,let_b,2);
  EEAckPolling(0xC6);

    if (let_b[0] & 0b10000000) return '8';
    else if (let_b[0] & 0b01000000) return '7';
    else if (let_b[0] & 0b00100000) return '6';
    else if (let_b[0] & 0b00010000) return '5';
    else if (let_b[0] & 0b00001000) return '4';
    else if (let_b[0] & 0b00000100) return '3';
    else if (let_b[0] & 0b00000010) return '2';
    else if (let_b[0] & 0b00000001) return '1';
    else if (let_b[1] & 0b00000001) return '9';
    else if (let_b[1] & 0b00000100) return '0';
    else if (let_b[1] & 0b00001000) return '#';
//    else if (let_b[1] & 0b00000010) return '*';
    else return 0;
 }

char LCDKeyboardSingleChar() {
  key_pressed = LCDKeyboard();
  if ((key_pressed != 0) && (key_pressed != last_key_pressed)) {
    last_key_pressed = key_pressed;
    return key_pressed;
  } else {
    last_key_pressed = key_pressed;
    return 0;
  }
}

void I2CR()
{
 char ADDS,N1,d1,d2,i;
 unsigned char let_b[16];

 N1 = strcom[3];  // numero byte da leggere
 ADDS = strcom[2];  // I2C Address
 d1 = strcom[4]; // option

 EESequentialRead(ADDS,d1,let_b,N1);
 EEAckPolling(ADDS);

 TXREG = '@';
 while(!TXSTAbits.TRMT); 
 TXREG = 'I';
 while(!TXSTAbits.TRMT); 
 TXREG = ADDS;
 while(!TXSTAbits.TRMT); 

for (i=0;i<N1;i++)
  {
   TXREG = let_b[i];
   while(!TXSTAbits.TRMT); 
  }
}

char GPIO_read(void)
{
 return PORTB;
}

void GPIO_write(char v)
{
 LATB = v;
}


void EnterCoords() {
  char c;
  char pos;
  char bufpos;

  LCDClearScreen();
  LCDprint2((const rom char far *)"Lat: ");

  c = LCDKeyboard();
  while (c == '#') { // if the # key is still pressed, wait until it is released
    DelayMilliSeconds(100);
    c = LCDKeyboard();
  }
  pos = 0;
  bufpos = 0;
  while(c != '*') {
    if (c != 0) {
      if (pos == 0) {
        LCDprintc(c);
        buf[bufpos++] = c;
      } else if (pos == 1) {
        LCDprintc(c);
        buf[bufpos] = c;
        buf[bufpos+1] = 0;
        latitude_dc = atol(buf);
        LCDprintc(' ');
        bufpos = 0;
      } else if ((pos >= 2) && (pos < 7)) {
        buf[bufpos++] = c;
        LCDprintc(c);
        if (pos == 3) LCDprintc('.');
      } else if (pos == 7) {
        LCDprintc(c);
        buf[bufpos] = c;
        buf[bufpos+1] = 0;
        latitude_mc = atol(buf);
        bufpos = 0;
      } else if (pos == 8) {
        if (c == '2') {
          LCDprintc('N');
        } else {
          LCDprintc('S');
          latitude_dc = -latitude_dc;
          latitude_mc = -latitude_mc;
        }
        bufpos = 0;
        LCDprintc(13);
        LCDprint2((const rom char far *)"Lon:");
      } else if ((pos >= 9) && (pos < 11)) {
        LCDprintc(c);
        buf[bufpos++] = c;
      } else if (pos == 11) {
        LCDprintc(c);
        buf[bufpos] = c;
        buf[bufpos+1] = 0;
        longitude_dc = atol(buf);
        LCDprintc(' ');
        bufpos = 0;
      } else if ((pos >= 12) && (pos < 17)) {
        LCDprintc(c);
        buf[bufpos++] = c;
        if (pos == 13) LCDprintc('.');
      } else if (pos == 17) {
        LCDprintc(c);
        buf[bufpos] = c;
        buf[bufpos+1] = 0;
        longitude_mc = atol(buf);
        bufpos = 0;
      } else if (pos == 18) {
        if (c == '6') {
          LCDprintc('E');
          longitude_mc = -longitude_mc;
          longitude_dc = -longitude_dc;
        } else {
          LCDprintc('W');
        }
        LCDprintc(13);
        LCDprint2((const rom char far *)"Height:");
      } else { // Height
        LCDprintc(c);
        buf[bufpos++] = c;
        if (pos == 23) {
          buf[bufpos] = '0';
          buf[bufpos+1] = 0;
          hc = atol(buf);

          ind_quality = '1';
          sat_in_view0='X';
          sat_in_view1='X';
          valid_row = 0;

          manual_coords = 1;
          return;
        }
      }
      pos++;
    }
    c = LCDKeyboardSingleChar();
  }
}


void ProgramXBee(const rom char far * local_address, const rom char far * remote_address) {
  char c;
  LCDClearScreen();
  LCDprint2((const rom char far *)"Are you sure?\r press # for yes");
  c = LCDKeyboardSingleChar();
  while (c == 0) {
    c = LCDKeyboardSingleChar();
  }
  if (c == '#') {
    write_s_UART2((const rom char far *)"Programming...");
    DelayMilliSeconds(4000);
    if (XBeeProcessCommand("+++", 0, 0))
    if (XBeeProcessCommand("ATMY", local_address, 13))
    if (XBeeProcessCommand("ATDH", "0000", 13))
    if (XBeeProcessCommand("ATDL", remote_address, 13))
    if (XBeeProcessCommand("ATWR", 0, 13))
    if (XBeeProcessCommand("ATCN", 0, 13)) {
      LCDprint2((const rom char far *)"XBEE programmed!");
    }
  } else {
    LCDClearScreen();
  }
}

char XBeeProcessCommand(const rom char far * command1, const rom char far * command2, char command3) {
  write_s_UART2(command1);
  if (command2 != 0) {
    write_s_UART2(command2);
  }
  if (command3 != 0) {
    write_c_UART(command3);
  }
  switch (XBeeResponse()) {
    case 1:
      LCDprint2((const rom char far *)"Command ");
      LCDprint2(command1);
      if (command2 != 0) {
        LCDprint2(command2);
      }
      LCDprint2((const rom char far *)" okay\r");
      return 1;
    break;

    case 2:
      LCDprintc(13);
      LCDprint2((const rom char far *)"XBEE said: ERROR");
    break;

    case 0:
      LCDprintc(13);
      LCDprint2((const rom char far *)"XBEE did not respond");
    break;
  }
  return 0;
}

char XBeeResponse() {
  int i;
  DelayMilliSeconds(1200); // check if the XBee responds after one second
  if (TXcount != 0) {
    if (strcom[0] == 'O' && strcom[1] == 'K') {
      TXcount = 0;
      return 1;
    } else if (strcom[0] == 'E' && strcom[1] == 'R' && strcom[2] == 'R' && strcom[3] == 'O' && strcom[3] == 'R') {
      TXcount = 0;
      return 2;
    }
  }
  TXcount = 0;
  return 0;
}

void write_c_UART(char c) {
 TXREG = c;
 while(!TXSTAbits.TRMT); 
}

void write_s_UART(char * c) {
 int pos;
 for(pos=0; (pos < 10) && (c[pos] != 0); pos++) {
   write_c_UART(c[pos]);
 }
}

void write_s_UART2(const rom char far * c) {
  int pos;
  for(pos=0; (pos < 10) && (c[pos] != 0); pos++) {
    write_c_UART(c[pos]);
  }
}

void DelayMilliSeconds(int ms) {
  int x;
  for(x=0; x < ms; x++) {
    Delay10KTCYx(1);
  }
}

void GPSParse() {
  char FLDcounter;
  char curr_pos; 
  char FLDpos;
  curr_pos = 6;
  FLDcounter=1;
  for (curr_pos=6; curr_pos < 76; curr_pos++) {
    if (strcom[curr_pos] == ',') {
      switch (FLDcounter) {
        case 2:
          buf[2] = strcom[curr_pos-4];
          buf[3] = strcom[curr_pos-3];
          buf[4] = strcom[curr_pos-2];
          buf[5] = strcom[curr_pos-1];
          buf[6] = 0;
          latitude_mc = atol(buf);
        break;
        case 3:
          if (strcom[curr_pos-1] == 'S') {
            latitude_dc = -latitude_dc;
            latitude_mc = -latitude_mc;
          }
        break;
        case 4:
          buf[2] = strcom[curr_pos-4];
          buf[3] = strcom[curr_pos-3];
          buf[4] = strcom[curr_pos-2];
          buf[5] = strcom[curr_pos-1];
          buf[6] = 0;
          longitude_mc = atol(buf);
        break;
        case 5:
          if (strcom[curr_pos-1] == 'E') {
            longitude_dc = -longitude_dc;
            longitude_mc = -longitude_mc;
          }
        break;
        case 6:
          ind_quality = strcom[curr_pos-1];
        break;
        case 7:
          sat_in_view0 = strcom[curr_pos-2];
          sat_in_view1 = strcom[curr_pos-1];
        break;
        case 9:
          if (FLDpos < 10) {
            buf[FLDpos] = '0';
            buf[FLDpos+1] = 0;
          }
          hc = atol(buf);
          curr_pos = 99; // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! exit loop !!!!!!!!!!!!!!!!!!!
        break;
      }
      FLDcounter++;
      FLDpos = 0;
    } else if (strcom[curr_pos] == '.') {
      switch (FLDcounter) {
        case 2:
          buf[0] = strcom[curr_pos-4];
          buf[1] = strcom[curr_pos-3];
          buf[2] = 0;
          latitude_dc = atol(buf);

          buf[0] = strcom[curr_pos-2];
          buf[1] = strcom[curr_pos-1];
        break;
        case 4:
          buf[0] = strcom[curr_pos-5];
          buf[1] = strcom[curr_pos-4];
          buf[2] = strcom[curr_pos-3];
          buf[3] = 0;
          longitude_dc = atol(buf);

          buf[0] = strcom[curr_pos-2];
          buf[1] = strcom[curr_pos-1];
        break;
        case 9:
          buf[FLDpos] = strcom[curr_pos+1];
          buf[FLDpos+1] = 0;
          FLDpos = 10;
        break;
      }
      FLDpos++;
    } else if (FLDcounter == 9) {
      buf[FLDpos] = strcom[curr_pos];
      FLDpos++;
    }
  }
}

Tracker.h File

void GPSParse(void);
char XBeeResponse(void);
void DelayMilliSeconds(int);
void write_s_UART(char *);
void write_s_UART2(const rom char far *);
void LCDprint2(const rom char far *);
char LCDKeyboard(void);
char LCDKeyboardSingleChar(void);
void ISRgest(void); // I.S.R.
void Servo(void);
char GPIO_read(void); // GPIO
void init_sys(void); // init
void I2CW(char, char, char, char, char);
void I2CR(void); // I2C generic read
void flashLED(char);
long getDeltaLatitude(void);
long getDeltaLongitude(void);
long getHeight(void);
long approx_atan2(long, long);
long isqrt(long);
void LCDprintc(char);
void LCDprint(char *);
void LCDClearScreen(void);
void EnterCoords(void);
void ProgramXBee(const rom char far *, const rom char far *);
char XBeeProcessCommand(const rom char far *, const rom char far *, char);
void write_c_UART(char);

Motor Tripod Part II

The last test was finally a successful one. I still will need to check the distance the wireless connection can work with and the test site I used is not wide enough, just 20m away causes the GPS errors to add up a lot. And the site does not have great visibility to the sky either with trees and hills around. So much about the excuses.

So what is working? Let me show you the video first. I place the GPS receiver next to the tripod and power up both. After a few seconds, the tripod electronic will show on the LCD that it found 6 or 7 satellites so you can be sure the position to be accurate. You press "5" on the keyboard and the tripod will store this position as its own.
Next I placed the GPS receiver 15m away so the controller can lock on the remote position. Back at the tripod I then use the keyboard to pan/tilt the video head until the camera looks straight at the position where the GPS receiver is lying on the ground. And that is where the video starts...



As said, I will post another video when I have the chance to test it on open ground.


Details...
The program consists basically of two phases. One is where all the setup takes place and the tripod position is locked in. And the second phase is the tracking mode with the option to manually control the head to maintain the offsets.

In the first phase the controller will carefully print its current state onto the LCD. Did it receive any serial data yet? Did it receive a valid GPS sentence? Does the GPS sentence tell that we have a satellite fix? And finally, if all is true it will tell you can press "5" at any time and start printing the number of satellites we have in view.
In order to get a serial connection via the XBee modules, they need to be programmed first. So I had to by a prototype board with which you can connect the XBee to the USB port of the computer and using a terminal emulation (Windows Hyperterminal) you can bring the module in the programming state by typing "+++" with two seconds lead and lag time where no data is sent. Then using some commands like "ATDL0202" you can tell it to which module it should send the data to - the one with the number 0x0202, what its own address is and save all of that into the XBee memory. To simplify that task for you, when no serial data is received you can press "1" or "2" to program either the local or the remote module. It does not matter which one is what, all I do is in "1" I set the XBee module address to one value and the the remote to another, when you press "2" I do the same but now the former remote address is the local. So all you have to do is insert the first XBee module, power up the controller, wait until it says "no data received, press 1 or 2" and then you press "1". Power down the controller, remove the XBee and place it into the socket of the GPS receiver. And the other XBee is now placed into the controller and programmed with "2". From now on you have a transparent serial connection between those two XBee's and when the GPS receiver is turned on, data should be received.

Once the local position is locked in, the controller goes into the second phase, the locking and aligning. To avoid erratic movement, the tracking is only done if the GPS module is more that 10m away. The first time the tracking starts, the current video head position is used as reference zero point. So at first, the head will not move at all and then start following you. If you walked away along where the camera looked to, the position would be correct already. But very likely it is off by a bid. So you place the GPS receiver somewhere remote and leave it there, then you walk back to the tripod and using the keys "4"-"6" and "2"-"8" you can move the zero-point until the camera looks directly at the GPS device. And from now on, the camera should keep the GPS always in the same position of the viewfinder - more or less.

Another option to provide a GPS position is to manually type it in. This is only useful if no GPS data is received at that time! For example, I might not trust the home position, I want the enter it by myself. So I power up the tripod but leave the GPS unit off. At any time you can press the "#" button and then using the keyboard enter the coordinates manually. At first, you type the digits for the latitude in the format 50° 20.2342N. So you just type the number sequence 5-0-2-0-2-3-4-2-2, the last "2" stands for north. Then the latitude 004° 43.2341E and finally the height in meter 000123. This manual entry is interpreted as a first valid GPS sentence, so you can press "5" to lock it in.
More important, instead of placing the GPS unit somewhere remote and then adjust the camera to it, you can switch it off then and manually enter the coords of any landmark in view. This will be used as the remote position and using the keyboard you then turn the head towards this location to zero it in. Turn on the GPS device again and as soon you are 10m away the head will look straight to you.
This feature was particularily cool during testing and calibrating the code. Sitting at the computer, entered the GPS position taken from google earth and the head started to move.

Another feature built in is to freeze the height. This is particularily useful if the camera and the object is on the same level the entire time anyway - Windsurfing, yes? - or like in my testing the GPS is close and does not have a good fix. In this case the height information keeps wandering by 10m within minutes.


Another change I made recently was to implement servo speed. Before, the GPS did send a new position every second. As a result, the head did jump to the new angle and then remained there for the reminder of the second to jump by another degree then. The result was no smooth movement, not at all. What I do now is calculating the degrees to be moved, divide that by one second and that is the rotation speed. So we are up to one second behind the actual position but that is good enough. Some fine tuning will be in order later.

The one step that is still missing is programming the GPS. By default it sends different GPS sentences, more than we are interested in. The XBee has a buffer of 100 characters which are sent at once and one GPS position sentence fits perfectly into that. So even without flowcontrol, we have a very reliable connection. Would we send more data every second, much more sentences would be lost. So I told the GPS I am interested in the GPGGA sentences only. The other thing, by default the SIRF chip is set to static movement and track smoothing, both good for higher speed movements but not for walking - so I disabled both. To do that you have to send a command to the GPS bringing it into binary mode where no ASCII data istransferred but binary data and then you can set those advanced options. Found that too dangerous to try via the XBee so I rather used a tool called SIRFdemo. But for this the GPS has to be connected to the computer directly or you have to make sure the binary mode is using 9600Baud as well. The first time I did that wrong and the result was the XBee couldn't talk to the GPS anymore due to different baud rates. So I had to program both XBees to 57kbit rate, then bring the GPS back into ASCII mode at 9600baud and reprogram the XBees again.

Wednesday, September 17, 2008

Motorized Tripod with GPS

The idea came during a Windsurfing trip. I'd like somebody to video tape me while windsurfing. Trouble is you cannot find anybody, it is windy at the beach, nobody wants to film for an hour or more. And you need a tripod as the wind blows so strong you cannot hold the camera steady enough. What if you have a motorized pan-tilt head mounted on the tripod which receives the GPS coordinates wireless and translates that into the correct angles to keep the GPS receiver in the center of the view finder.

Given my experience with GPS you can assume - good reception given - to get the correct position from the GPS with an error margin of about 5 meters in distance, height is worse, I would expect the position wandering +-10meters. So the first limitation is, you cannot expect to follow objects very close to the camera. If the camera position is off by five meters and the GPS receiver another 5 meters in the other direction you need to be at least 20m away to make sure the camera points somewhat into your direction. For Windsufing no problem.

So the entire project can be split into the following components:
1. Receive GPS data wireless over a good distance
2. Microcontroller and its software
3. The pan-tilt mechanic

Typical GPS receivers have either an USB, a bluetooth or a serial connection to the computer. Bluetooth would be just perfect but its range is 10meter. In theory bluetooth can span up to 100meter but even that is not far enough for this application nor do you find bluetooth class2 enabled GPS recievers prebuilt. All common microcontrollers support a serial connection, the easiest method would be to go for a serial GPS receiver and some wireless replacement for a serial cable. The most current standard for that is ZigBee or XBee. Low-Cost, Low-Power, free 2.4GHz band and distances up to 1.5km.

Next question was how to connect the XBee module with the GPS receiver on one side and on the other end, XBee connected to the microcontroller. Guess what, there are controllers available already that have a plug for XBee modules built in.

Deciding on the mechanical components took the longest time. Basically I had two options. Either use a stepper motor or RC servos. Stepper motors are very precise, slower but that would be okay. And I would need to make the mechanics all by myself. A standard RC servo on the other hand is all I really needed. It is fast, cheap, it is a +-180 degree rotating movement already. But for a heavy D-SLR camera they might not be strong enough. And the question still was, how to attach the camera to the servos.

At the end I bought the following components (bought in Germany but I am sure you find equivalent offerings anywhere else).

Navilock NL-501ETTL Sirf3 TTL Modul: A GPS receiver with 3V UART interface and the SIRFIII low power chipset, costs just 30EUR.
XBee PRO Module with wire antenna: Need two items, 45EUR each.
Droids XBee Simple Board: The board connecting the GPS unit to the XBee module. As it has an integrated voltage regulator it will power both, the XBee and the GPS.
Droids Multi Interface Board PIC18F2520: A PIC microprocessor based development board with Servo output, XBee enabled and ICSP interface for programming.
The PICKit2 programmer to transfer the compiled software into the microcontroller. In theory you can do that via other ways as well, but if anything goes wrong or you need to debug the code you have to have such a thing anyway. (see microchip web site) Software is available there free of charge including the C compiler I used, it is called C18. If you search for "Clone Microchip Programmer" or "pic kit clone" in some variations at e.g. ebay you get this for 30EUR or so.
LCD03 display and keyboard: Used to interact with the user. Search for LCD03, seems to be a standard component found everywhere.

Servo bracket: With these you can screw multiple servos together and build the pan-tilt head. This bracket will hold the servo body.
Servo C-bracket: The other half of the bracket, this time for the servo head.
Metal Servo Horn: Required component for the two brackets, two pieces needed.
Manfrotto Quick release plate: Two items, one is used to attach the camera to the servo bracket, the other is between servo and tripod.
Hitec HS-5745MG servos: Again, two pieces required. I tried with analog servos first but they kept trembling even in a static position. Digital servos are much better and do not cost that much more.


...more to come...






Tuesday, September 16, 2008

Lego Mindstorms NXT and Power Functions

Lego recently came out with the Power Functions system, strong and yet small enough motors to be used nicely. You can even remote control them given you bought their IR receiver. But to connect them to Mindstorms NXT robotic system, nothing is available yet. The suggestion is to create IR signals with the NXT device to control them!?!?

The power functions connector cable is very simple, there is GND and +V on one end each and the middle two cables are used to control the motors. If there is a voltage difference of +9V it rotates at full speed in one direction, -9V the other direction and everything inbetween controls the speed. Like in any other simple motor.

So what we need is a standard H-drive for any analog motors, I picked the one from mindsensors.com.



Originally designed to connect RCX motors to it, it has two small soldering holes at each of the four motor drives. From the official lego web shop I bought some more spare power function cables, cut them in half and soldered them to the motor ports. And one cable is connected to the battery box.




As the device was designed to drive motors, the NXT-G building block is just perfect. You specify the rotation direction and speed - that's it. Works very well.
And yes, I did not connect the reference voltage so I cannot daisy-chain components. Why would I want to?

Update: Nikon D300 and bluetooth

Still very happy with the GPS solution, still sticking to the original GPS receiver. The XAiOX iTrackU did work as well, but did not provide the advantages I was hoping for. I thought I will be notified when the battery is low and yes, it does. With a loud and scratching voice. You can turn down the volume but just in three steps and it is still very loud. So I returned it.

For the summer holidays at the windy beach of Fuerteventura I installed a 18mm O-ring seal you can get at any DiY shop. The Mini-USB connector I sealed with an adhesive tape, on the image you see a white one.

In the first image you see the bluetooth adapter mounted...



Second image shows the backside of the adapter with the O-ring seal...


And from a different perspective

Monday, April 28, 2008

Nikon D300 Bluetooth GPS

After thinking about it for a longer time I finally decided to try GPS together with the D300. A cable based solution was out of the question. Not only would it be bulky to use but the choice of GPS devices with serial connectivity is very limited too. By far the most GPS receivers use bluetooth and the others USB connections. Today I am aware of two bluetooth solutions for the Nikon. One is the blue2can, a device built by a professional company with support and the like, the other is this: Foolography Unleashed built by a one-person company, the one I settled for.





Let me first introduce how all of that works before going into the details.



The Unleashed module comes with a special usb-to-serial cable which is used to program it. As usual the first thing is to install the device driver - done in a minute - then you plugin the cable to the computer, the installed drivers will be recongnized and you end up with an additional com port. Now you plug in the Unleashed module to the other end of the cable and start the Unleashed configuration program to search for all available Bluetooth devices and pair it with the GPS device. Once this step is done, you unplug the module and insert it to the 10 pin port of the camera. Turn on the camera, and two seconds later the GPS symbol is shown telling that a GPS device was found and is sending correct positional data.





This ease of use is really something that I was amazed a lot. I expected that ... I don't know, you have to turn on the GPS receiver first and then the camera or nonsense like that. Or that it takes a minute until the camera is ready. Nothing like that!
The next step was to go to the GPS menu of the camera and tell it to enable the "auto meter off". When you click on AF-S or press the shutter halfway, the 10 pin port gets power, and....if you don't give the GPS unit its five seconds to connect, you will get a picture but without the GPS coordinates. To prevent that, this menu item keeps the exposure meter "On" all the time the camera is turned on and(!) a GPS unit is connected. The second menu item is to view the live GPS coordinates the camera receives. So I was able to see I am 50° North, 0° 23' E and 60m high. Excuse me? 0° 23' East? Very unlikely. By connecting the GPS mouse to the computer and running an excellent tool called GPSDiag (e.g. found here) I could validate that the GPS receiver is working fine and tells we are 007°. Just to make sure, the degrees are wrong, not the minutes & seconds! It was when I started to add pictures to google maps and wanted to correct them manually that I figured, the problem was with viewing the GPS coordinates. The values inside the EXIF GPS fields have been correct anyway! Oh dear. I could even see from which position of the tennis court the picture was taken! I would give it an accurancy of about 2 meters? Reporting this problem to Foolography we had been able to confirm that this bug in the D300 View GPS Coordinates screen is common to all GPS receivers, it was just overlooked by now. So don't get scared if the coords are off, it is just the viewing. The one place where it really matters are the EXIF fields and there all is in order. Nevertheless I have reported the bug to Nikon.



What is particularily neat about the Skytraq100+ GPS receiver is its power management. If you turn off the camera (the bluetooth connection), it will go into a standby mode five minutes later. Turn on the camera again and it will take 5 seconds until the camera is connected to the GPS receiver, and then another n-seconds until it receives a valid GPS position - the TimeToFirstFix (short TTFF) which is between 1 second and 10 seconds from what I have seen. Depends on your GPS receiver obviously but all modern ones should be similar.



Why spend that much money? Doesn't a GPS Logger do the same job?

GPS Loogers are devices that contiously record the current position in memory and later you can read that file(s) and given the timestamp of the GPS signal and the time value the camera stored in the EXIF and some software you can merge the two pieces into a geotagged jpeg. Those GPS loggers are cheap, they record the position either every n seconds or whenever moved a certain distance. They have enough memory to store a lot of records and the software that merges the information is supposed to be easy to use too. It is the handling and the numerous things that could go wrong. You plugin the logger and download the location files via some software. Then you disconnect, connect the camera, download its images. Then you start the software to merge the two. You won't see if the GPS logger stopped working for whatever reason, whereas at the camera display you will look whenever you turn it on. The camera time might be off a few seconds or even a minute (or an hour because of daylight saving time?) and you will have no chance to correct that, whereas the bluetooth solution has all at once, it even shows the camera time plus the GPS time with atomic clock precision in the EXIF data.

When I put all that together, how many hours of manual work it will take to sync that data over the months and years, and assign labor "costs" to that, I'd say the bluetooth solution is cheaper. Might not be worth it but cheaper.


What are the differences between the two bluetooth products?

Bluetooth supports multiple pairing methods, methods to make sure that e.g. your headphone doesn't suddenly receive somebody else's calls. The Blue2Can device pairs with the next available bluetooth device supporting the SPP (serial line protocol) which very likely will be a GPS unit. Of course you could say that "stealing" the coordinates information from somebody else's GPS unit who is 10m away is no big deal. But now imagine that GPS device you are connected is moving out of range. You end up with no GPS data until you turn off the camera and turn it on again. Not too nice, is it? Another issue could be, you connect to the GPS receiver but it is used by some other computer, e.g. your handheld is using one GPS mouse, the camera is supposed to use the other. You cannot control which device the camera connects to and if it is the used one, the bluetooth error "cannot open serial port" is returned as it is used by the other device already. And that's it - no GPS data. No reconnect. Just no data and no clue why.



With the Unleashed module the GPS device is really bound to it. Yes, you can choose the other connection mode also but why would you? And this was really the key reason for myself - less troubles. Just turn on the camera and you are ready.



The concern I had however was, can you trust a one man shop? What if the programming cable has no driver for Longhorn or whatever operating system I will use in five years? Or the configuration software? Actually, the Unleashed module is using components from well established companies, just tied those together and made it work. So both software components will likely have full support, more support than a small company somewhere. The downside of this approach are higher costs. That bluetooth to serial converter used is likely this one http://www.sena.com/products/industrial_bluetooth/esd.php and it costs 50EUR already. Add the programming cable, the housing etc and just the material costs get to 150EUR or more I guess. So the price is okay, I'd say. And both products cost about the same anyway.


Usability

One concern raised often is the battery drain caused by the bluetooth plug. I do not want to argue that it does not consume much according to the specs, I rather say: First, if you turn off the camera the module gets disconnected from power and hence does not change anything from before. And second, the battery of the camera still lasts for four to five days usage. Actually, I can't see any difference compared to before.

The other thing I have read was the question how tight the plug sits. The camera would have a screw thread, but the Unleashed module does not. It just plugs in. And frankly, I would have no idea how to use the screw thread given the small dimensions of the device. It sits tight, it cannot fall off by itself, it is well protected by the camera body so no way it can dangle itself and get ripped off. And still you can unplug it with some force but without the fear of breaking something. So in that respect it is really well balanced.

And before I forget, the flash popup button is fully accessible, its control button below good enough. Given I never changed the flash setting via this lower button I wouldn't even care.


Wishlist

It seems to be sort of a personal thing, maybe my German attitude, but everything should be made perfect. No matter what it costs, no matter if there is not even reasoning behind. It is just a philosophy. Same thing with the Unleashed module - I would love to see it getting perfect. The one thing I can certainly not live with is, the camera is no longer resistant to spray water. Geotagging just sounds like synonym to exposing the camera to not-so-good weather. The plug body itself is sealed, however it has the mini-USB and the remote shutter connectors open, what makes it worse they are on the upper side. Fixing that is simple, in worst case an adhesive tape will do the trick. Or I find some plastic pieces from other devices to seal them. But the 10-pin connector to the camera is exposed. I will put some gasket around, the module will create enough pressure on to seal it without sliding slowly out over the time I am sure.


And then there are more features one can think of. I considered if having the compass heading would be important to me. Not the GPS heading which is generated by the direction the GPS receiver was being moved but the real magnetic compass heading. There are a few GPS receivers out there, one I found neat is the Wintec WSG-1000 (note, no SIRF III chip so if it works or not is unknown at the time!). But thinking about it, the heading only makes sense if the GPS receiver points into the same direction as the camera does. But that forfeits the entire idea of putting the GPS in the pocket and never look at it. So I had to say, given that restriction, what good for is the compass heading then? On the other hand, if the Unleashed module would have a magnetic sensor and mix its information into the GPS stream of data,......yeah, yeah, just dreaming. Sounds like a hell of development work, might get jammed by the camera electronics anyway and for this feature I would not spend a lot of money. But...dreaming...making something perfect.... But actually, who cares.

The other thing is the remote shutter. You have something that connects to the pins that operate the remote shutter, that thing has a wireless connection, so, can't that be used then? Would be kind of cool to open an application on the cell phone and operate the shutter from there, wouldn't it? I am not particularily sure it would make a lot of sense, certainly not for my needs but, well, you guessed it probably, ...making things perfect...philosophy...just for the sake of it. Development wise again I would guess it is rather difficult. The chip used right now is a serial to bluetooth converter. It will not support two bluetooth devices at the same time so you would need to build the bluetooth converter yourself, add some microcontroller, the module would probably grow in dimensions and price. Or we build a GPS unit with a release button, but then the advantage of using any new GPS module is gone.

Both features would be nice to have but for me, no thank you, not worth any additional costs.

Right now I am absolutely happy with the money spent, next thing I will look at is the XAiOX iTrackU DATALOGGER SiRF III. And hopefully even the GPS display on the camera will be perfect soon.