Monday, August 24, 2009

Pimp my Telescope Part 3

Some more technical data:

PCB 1:



PCB 2:



A 3D graph of the PCB:

Sunday, August 23, 2009

Pimp my Telescope Part 2

After describing the general design ideas in part 1 and how the Laser was fitted into the system, the remaining parts are less fency.
On switch controls the environmental lamp, the one the Laser flashes prior to engaging as safety warning. And two other switches turn on the LEDs for the polefinder and the finder scope. But there as well I had a problem before. Your goal is to get the selected object exactly into the center of the crosshair illuminated via the LEDS. When is it in the center? When you can't see it anymore. Didn't like that, so the microcontroller does not simply switch on the LEDs, it keeps them flashing. Simple implementation but one of the once you have seen it you do want to miss that again.
The power line is connected to one of the analog-digital converters of the microcontroller to measure the voltage of the battery and if it droppes below a threshold, the control LED on the PCB starts flashing. Oh, did I mention I use LiPo batteries with 11.1V known from RC helicopters as power source? They are compact, cheap, do not discharge when not used - very important! - but have one downside, they should never get discharged entirely. That's the reason for that warning light.

The dew shield and the temp sensor I will connect to the board later the year. And the next major improvement is to listen to the Autostar internal bus to understand its two-wire interface protocol. Then I want to respond to the autostar computer in the beginning when it is in the scanning phase and tell it is a Meade focuser. This should enable the focuser screens on the Autostar and the connected laptop. I could use these buttons then for various things like turning on the laser via the computer. Or I connect a motor to the focuser that understands the standard I²C protocol the microcontroller outputs to the RJ-11 connector. I am not that thrilled about the autostar protocol of the focuser, all you can do is selecting the speed of the focuser motor and not its absolute position but okay, we have to live with that.

Thursday, August 20, 2009

Pimp my Meade Telescope

As another project I bought an used Meade Telescope, a LXD55 8" Smith Newton with Autostar. This is a GoTo Mount, meaning once you did setup the scope it moves to the star or planet you picked by name and keeps the object centered. It was a nice learning experience however what I liked the most was enhancing it.

1. A Laser visible in the sky as a "finder".
2. The cross-hair occular should be illuminated by the main power, not a battery, and it should blink.
3. Same with the pole finder.
4. Dew shield with heater should be temperature controlled.
5. A simple light.
6. Lithium Ion Battery saver.
7. An electric focuser motor controlled by the Meade Autostar computer

Let's start with the Laser:

The problem for me was usually, what star am I looking at? During the initialization of the scope, it moves to the appoximate position of a star and your task is to center it. When you look at the sky you can see the star in question as the brightest object in that region. When you look through the main scope you can see tens of stars with similar brightness but certainly not the one you are looking for. And in the finder - a second attached but smaller scope with a bigger angle of view - there are three equally bright stars. So you have to move the scope for quite some time until you can be sure you identified the constellation and are pointing to the correct star. With a Laser it's a piece of cake.



ATTENTION: Only use Class 2 Laserpointers with <1mW power. They are strong enough to be visible in the sky given average moist weather conditions. And only these are not dangerous to humans or animals. And not dangerous does not mean completely harmless either. So never point at somebody else, neither direct nor indirect (reflections). Above image was shot with an exposure time of 8 seconds using a 1.4 aperture setting.

You can clearly see where the scope points to, you do not have to make any artistic exercises for multiple minutes when looking through the finder. The Laser is a stripped Laserpointer, the green ones work best as the human eye is most sensitive for this color.

It is placed at the telescope clamp onto a bar where I screwed in two bushings with three adjustment screws, similar to how a viewfinder is adjusted.






Not beautiful yet, but when I find the time for a CAD plan, I will ask a friend to mill that out of alloy.

One design question I asked myself was if that system should be completely independent from the telescope or plugged in to the Autostar. Given the fact that I keep forgetting to turn off the polfinder LED and hence the battery is always empty next time I need it, I certainly want to use the Autostar power. So I need to have a DC-DC converter from 12V to 3V between the AUX port of the mount and the Laser. Oh, and a switch somewhere on the mount, not the tube to avoid shaking it.
And a safety mechanism should be implemented as well to warn people the Laser is about to engage. And.....and I need a microcontroller.

I designed a PCB that
1. is connected to the Aux-Port to draw the power and listen on the Autostar internal bus
2. has rocker switches for the different functionality
3. a RJ-45 connector to connect to a daugther PCB, a tiny one distributing the signals required at the tube (Laser, Temp Sensor, Finderscope LED)
4. a RJ-11 connector for the I²C bus of the microcontroller as a future expansion connector
5. a few mini-usb plugs for the functionalities required at the mount (Polefinder LED, Lamp)
6. a connector for the dew shield heater

So when I switch on Laser, first the LED lamp starts blinking for a few seconds and then the laser engages.

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