Here is the code for this project - sorry it is not too polished, but hopefully you will get the general idea of my approach. Apologies for functions not actually used in the project - these were used during my experimentation of different solutions so left in for posterity!


// Object Tracking - measure and report on an objects position using the Loc8tor. The assembly
// will move, scanning first in the x, y plane to determine the strongest signal direction and then
// in the z plane to determine vertical position. The coordinates and distance to the object
// are then outputed to an LCD display.
//
// There are 3 main components to this project:
// 1. the interface to the Loc8tor device
// 2. the motor shield and stepper motors
// 3. the interface to the LCD display
//
// 1. the Loc8tor device (www.loc8tor.co.uk) is a small credit card sized device that allows you to
// track small tags that you attach to items you don't want to misplace like your mobile phone or pet.
// Once the device starts scanning it makes an audible sound that increases in pitch as you approach
// the tag, thus allowing you to incrementally track down and locate the tag.
//
// For this project I took the device apart and made a number of modifications:
// - added two resister to act as a voltage divider to bring voltage from 5v to 3v that the device uses
// - replaced  2 tactile switches with transistors: one to power the device on and off, and one to
//   switch the scanning mode on and off
// - removed the inbuilt piezo sounder and added a wire to take the piezo sounder voltage to
//   an Arduino input
// so we have 5 wires from the device, power switch, scan switch, piezo feed, +ve and GRD.
// So in brief, I use stepper motors to move the device around and measure the relative pitch
// to locate the direction and estimate distance to the object.
//
// 2. The motor shield is the Adafruit (www.adafruit.com) kit that you need to solder up yourself.
// This allows 2 steppers motors to be attached which I've configured so that one motor sits on
// top of the other at 90 degrees. This allows movement of the entire assemblage in an x, y plane
// and then movement in the z plane at a particular x, y location. The MICROSTEP mode is choosen to
// make the motion as smooth as possible.
//
// 3. With the motor shield attached you only have 8 official Arduino ports you can use. The Loc8tor
// needs 3 (+ve and GRD go to different pins so we are ok there) and 6 are needed for a standard LCD in 4 bit mode.
// (ie. 4 for data, 1 for RS and 1 for Enable). Mine is a jhd162a - thanks to Suhas's (iamsuhasm.wordpress.com)
// for getting be going on this. So I had a a bit of a problem: 9 ports required with only 8
// available. I therefore decided to use a shift register (mine is a 74HC595) as I read you can drive an LCD
// with only 3 pins doing this. I did try some of the 3-pin LCD libraries but didn't get much success with my type
// of shift register so I decided to implement a logical port extender so with just the 3 inputs for the latch, clock and data,
// I simulate 8 ports on the data pins. I then made some modifications to the LiquidCrystal library to call my own version
// of digitalWrite() - this was the least intrusive method of modifying this library; so when LiquidCrystal
// thinks it's writing out to a pin, it will call my function that will shift out 8 bits to the register with the relevant
// bit set to 1 or 0 corresponding to the request to set HIGH and LOW respectively.
// Paul Hampton This email address is being protected from spambots. You need JavaScript enabled to view it.

#include <LiquidCrystal.h>
#include <AFMotor.h>

// set to 1 for debug output
#define DEBUG 1

// 1. Loc8tor Device parameters
// maximum number of samples to take to determine pitch
// of the piezo sounder used by the Loc8tor to indicate position
#define MAX_PIEZO_SAMPLES 4000

// the maximum average reading on the piezo pin we can expect for
// distance estimation purposes
#define PIEZO_MAX_READING 481

// the minimum average reading on the piezo pin we can expect
// distance estimation purposes
#define PIEZO_MIN_READING 425

// maximum buffer size for keyboard input on the monitor
#define MAX_INPUT_BUFFER 50

// number of readings to take to determine distance once
// the device is in place
#define READING_SAMPLES 10

// The number of steps these particular types of stepper motor has (48)
#define MOTORSTEPS 48

// The z sweep requires about a 3rd of the total rotation (18)
#define Z_MOTOR_STEPS 18

// the rotation speed of the motor in revolutions per minute
#define MOTOR_SPEED 30

// the number of steps to take on each movement
#define MOTOR_INCREMENTS 1

// these refer to the individual port numbers on the shield but
// also act as a handy enumeration for them
#define HORIZONTAL 1
#define VERTICAL 2

// This is the Piezo sounder pin number, the pitch will be used
// to approximate distance
int piezoPin = 5;


// This is the power switch pin
int powerSwitch = 17;

// The search Buttons, there are 4 all together on the Loca8tor
// Just use one for the moment
int searchButton1 = 18;

// 2. Motor related parameters
int sweepsPerSearch = 1;

AF_Stepper motorHorizontal(MOTORSTEPS, HORIZONTAL);
AF_Stepper motorVertical(MOTORSTEPS, VERTICAL);

int mode = MICROSTEP;

// 3. LCD related parameters
// initialize the library with the numbers of the interface pins
// this is modified constructor that I added to the library
int latchPin = 14;
int clockPin = 15;
int dataPin = 16;

LiquidCrystal lcd(latchPin, clockPin, dataPin, 1, 2, 3, 4, 5, 6);

void setup() {

  // 1. Loc8tor related initiliation
  pinMode(piezoPin, INPUT);
  pinMode(searchButton1, OUTPUT);
  pinMode(powerSwitch, OUTPUT);

  // set both the search and power buttons to LOW
  // as soon as possible to stop the device switching on
  // automatically when the program first runs
  digitalWrite(searchButton1, LOW);
  digitalWrite(powerSwitch, LOW);

  // 2. Motor initialisation
  motorHorizontal.setSpeed(MOTOR_SPEED);
  motorVertical.setSpeed(MOTOR_SPEED);

  // 3. LCD initilisation
  // pinMode will be called in the LiquidCrystal library
  // for the latch, clock and data pins
  // set up the LCD's number of rows and columns:
  lcd.begin(16, 2);

  lcdPrint("init...");

  delay(1000);

  // set up for serial keyboard input
  Serial.begin(9600);
  Serial.flush();
  Serial.println("ready");

  /*  lcdPrint("about to scan ...");
   delay (15000);
   doScan();  */

}

void loop() {
  if (isKeyboardInput()) {
    char readBuffer[MAX_INPUT_BUFFER];
    Serial.print ("read: ");
    getKeyboardInput(readBuffer, MAX_INPUT_BUFFER-1);
    Serial.println (readBuffer);
    Serial.flush();


    // this section allows commands to be read from the monitor
    // to influence the device behaviour

    // power the device on or off
    if (strcmp(readBuffer, "p") == 0 ){
      togglePowerSwitch();
    }

    // start/stop the scanning function
    if (strcmp(readBuffer, "s") == 0) {
      doScan();
    }


    // displat the next 20 readings from the piezo Pin
    if (strcmp(readBuffer, "d") == 0) {
      for (int i = 0; i < 20; i++) {
        Serial.println(analogRead(piezoPin));
      }

      toggleSearchSwitch();
    }

    // display the status of the Loc8tor device
    if (strcmp(readBuffer, "?") == 0) {
      Serial.print("power: ");
      Serial.println(isPowerOn() == true? "true" : "false");
      Serial.print("scanning: ");
      Serial.println(isScanning() == true? "true" : "false");
    }
  }

  char disp[16];

  if (!isPowerOn()) {
    lcdPrint("no power");
  }
  else {

    float reading = getReading();

    if (isScanning()) {
      if (reading != 0) {
        lcdPrint (dtostrf(reading, 15, 1, disp));
      }
      else
      {
        lcdPrint("no signal");
      }
    }
    else
    {
      lcdPrint("not scanning");
    }
  }

  delay (1000);
}


boolean isKeyboardInput() {
  // returns true is there is any characters in the keyboard buffer
  return (Serial.available() > 0);
}

void getKeyboardInput(char* readString, int readStringLen){
  // take a buffer readString and fill it with characters from the
  // keyboard stream up to readStringLen characters
  int index=0;
  int numChar;
  delay (500);
  while (numChar = Serial.available()) {
    while (numChar-- && (index < readStringLen)) {
      readString[index++] = Serial.read();
    }
  }

  // terminate the string
  readString[index] = '\0';
}


void printArray (float data[], char* text, int n) {
  Serial.print(text);
  for (int i = 0; i<n; i++) {
    Serial.print(" ");
    Serial.print(data[i]);
  }
  Serial.println("");
}

void printArray (int data[], char* text, int n) {
  Serial.print(text);
  for (int i = 0; i<n; i++) {
    Serial.print(" ");
    Serial.print(data[i]);
  }
  Serial.println("");
}

// report on whether the scanning function is operational
// to do this take a few readings and see if they are approximately
// the same; if they are the same then there is no scanning taking place
boolean isScanning() {

  int min = 999;
  int max = 0;

  // take several readings with a small delay and see if the
  // values are varying over time
  for (int i=0; i<20;  i++) {
    int thisReading = analogRead(piezoPin);
    if (thisReading > max) max = thisReading;
    if (thisReading < min) min = thisReading;
    delay (20);
  }

  //Serial.println(max-min);
  return  (max - min > 10) ? true : false;
}

// report on whether the power has been activated. If two consecutive readings
// of the piezo device are zero then there is no power.
boolean isPowerOn() {
  return ((analogRead(piezoPin) != 0 ) || ( analogRead(piezoPin) != 0 ));
}

void togglePowerSwitch() {
  Serial.println("power switch");
  digitalWrite(powerSwitch, HIGH);
  delay (3000);
  digitalWrite(powerSwitch, LOW);
}

void toggleSearchSwitch() {
  Serial.println("scan switch");
  digitalWrite(searchButton1, HIGH);
  delay (3000);
  digitalWrite(searchButton1, LOW);
}

// estimate the distance based on the reading from the
// piezo Pin
float getEstimatedDistance(float reading) {

  // this is a simple model of deriving distance from a reading.
  // This is based on some metrics I gathered in metres
  // and modelled using the inverse square law. Multiplying by 100 at the
  // end gives the results in cm

  // if we are out of the expected ranges then the model breaks down so return 0
  if ((reading > PIEZO_MAX_READING) || (reading < PIEZO_MIN_READING)) return 0.0;

  return sqrt(28/(PIEZO_MAX_READING - reading) - 0.5) * 100.0f;
}

float logn (float x, float r) {
  // use the fact that LOGr(x) = LOG(x)/LOG(r)
  return log(x)/log(r);
}

float getReading() {

  long averageReading = 0;

  // take the average of many readings
  for (int i=0; i < MAX_PIEZO_SAMPLES; i++) {
    averageReading+=analogRead(piezoPin);
  }
  return ((float) averageReading/(float) MAX_PIEZO_SAMPLES);
}

// lcd printing helpers
void lcdPrint (char* text, boolean clr, int xPos, int yPos) {
  if (clr == true) lcd.clear();
  lcd.setCursor(xPos, yPos);
  lcd.print(text);
}

void lcdPrint (char* text, boolean clr) {
  lcdPrint(text, clr, 0, 0);
}

void lcdPrint (char* text) {
  lcdPrint(text, true, 0, 0);
}

int sweep_x_y(int sweepCount) {

  float readings[MOTORSTEPS];
  int readingCounts[MOTORSTEPS];

  // initiliase all variables to 0
  for (int i=0; i<MOTORSTEPS; i++) {
    readings[i] = 0.0;
    readingCounts[i] = 0;
  }

  // perform sweepCount number of sweeps, taking readings as we go and
  // then return to the direction of best signal. Function returns
  // the step position it has moved to.
  // A sweep is in 4 parts:
  // - 180 degrees forward
  // - 360 backward
  // - 180 degrees forward
  // - move as appropriate to strongest signal
  // as we move through each point twice, we can take two reading
  // and average them

  for (int sweeps=0; sweeps<sweepCount; sweeps++) {

    // 180 degrees forward
    for (int i=MOTORSTEPS/2; i<MOTORSTEPS; i++) {
      readings[i] = addReading(i, readings[i], readingCounts[i], getReading());
      readingCounts[i]= readingCounts[i]+1;
      motorHorizontal.step(MOTOR_INCREMENTS, FORWARD, mode);

      //if (DEBUG) logger ("position %d was %s", i, readings[i]);

    }

    // 360 degrees backwards
    /*    for (int i=MOTORSTEPS-1; i>0; i--) {
     readings[i] = addReading(i, readings[i], readingCounts[i], getReading());
     readingCounts[i]= readingCounts[i]+1;
     motorHorizontal.step(MOTOR_INCREMENTS, BACKWARD, mode);
     // if (DEBUG) logger ("position %d was %s", i, readings[i]);

     } */

    motorHorizontal.step(MOTORSTEPS, BACKWARD, mode);

    // 180 degrees forward to return to the starting point
    for (int i=0; i<MOTORSTEPS/2; i++) {
      readings[i] = addReading(i, readings[i], readingCounts[i], getReading());
      readingCounts[i]= readingCounts[i]+1;
      motorHorizontal.step(MOTOR_INCREMENTS, FORWARD, mode);
      // if (DEBUG) logger ("position %d was %s", i, readings[i]);

    }
  }

  if (DEBUG) printArray(readings, "sweep_x_y", MOTORSTEPS);

  // now check the best 3 signals again and plump for the best out of those
  int orderedReadings[MOTORSTEPS];

  sortOrder(readings, orderedReadings, MOTORSTEPS);

  if (DEBUG) {

    Serial.println("checking out...");
    Serial.print("pos 1 ");
    Serial.print(orderedReadings[0]);
    Serial.print (" ");
    Serial.println(readings[0]);
    Serial.print("pos 2 ");
    Serial.print(orderedReadings[1]);
    Serial.print (" ");
    Serial.println(readings[1]);
    Serial.print("pos 3 ");
    Serial.print(orderedReadings[2]);
    Serial.print (" ");
    Serial.println(readings[2]);
  }
  moveToPosition(HORIZONTAL, MOTORSTEPS/2, orderedReadings[0]);
  float reading1 = getAveragedReading(READING_SAMPLES);
  displayReadingOnLCD("r1: ", orderedReadings[0], reading1);

  if (DEBUG) {
    Serial.print("pos 1 was ");
    Serial.println(reading1);
  }

  moveToPosition(HORIZONTAL, orderedReadings[0], orderedReadings[1]);
  float reading2 = getAveragedReading(READING_SAMPLES);
  displayReadingOnLCD("r2: ", orderedReadings[1], reading2);

  if (DEBUG) {
    Serial.print("pos 2 was ");
    Serial.println(reading2);
  }

  moveToPosition(HORIZONTAL, orderedReadings[1], orderedReadings[2]);
  float reading3 = getAveragedReading(READING_SAMPLES);
  displayReadingOnLCD("r3: ", orderedReadings[2], reading3);


  if (DEBUG) {
    Serial.print("pos 1 was ");
    Serial.println(reading3);
  }

  if ((reading1 < reading2) && (reading1 < reading3)) {
    moveToPosition(HORIZONTAL, orderedReadings[2], orderedReadings[0]);
    return orderedReadings[0];
  }
  else {
    if ((reading2 < reading1) && (reading2 < reading3))
    {
      moveToPosition(HORIZONTAL, orderedReadings[2], orderedReadings[1]);
      return orderedReadings[1];
    }
    else {
      // no need to move as at this position already
      return orderedReadings[2];
    }
  }
}

int sweep_z(int sweepCount) {

  float readings[Z_MOTOR_STEPS];
  int readingCounts[Z_MOTOR_STEPS];

  // initiliase all variables to 0
  for (int i=0; i<Z_MOTOR_STEPS; i++) {
    readings[i] = 0.0;
    readingCounts[i] = 0;
  }


  for (int sweeps=0; sweeps<sweepCount; sweeps++) {

    // 90 degrees forward
    for (int i=Z_MOTOR_STEPS/2; i<Z_MOTOR_STEPS; i++) {
      readings[i] = addReading(i, readings[i], readingCounts[i], getReading());
      readingCounts[i]= readingCounts[i]+1;
      motorVertical.step(MOTOR_INCREMENTS, FORWARD, mode);
    }

    // 180 degrees backwards
    /*    for (int i=Z_MOTOR_STEPS-1; i>=0; i--) {
     readings[i] = addReading(i, readings[i], readingCounts[i], getReading());
     readingCounts[i]= readingCounts[i]+1;
     motorVertical.step(MOTOR_INCREMENTS, BACKWARD, mode);
     } */

    motorVertical.step(Z_MOTOR_STEPS, BACKWARD, mode);

    // 90 degrees forward to return to the starting point
    for (int i=0; i<Z_MOTOR_STEPS/2; i++) {
      readings[i] = addReading(i, readings[i], readingCounts[i], getReading());
      readingCounts[i]= readingCounts[i]+1;
      motorVertical.step(MOTOR_INCREMENTS, FORWARD, mode);
    }
  }

  if (DEBUG) printArray(readings, "sweep_z", Z_MOTOR_STEPS);


  // now check the best 3 signals again and plump for the best out of those
  int orderedReadings[Z_MOTOR_STEPS];

  sortOrder(readings, orderedReadings, Z_MOTOR_STEPS);

  if (DEBUG) {

    Serial.println("checking out...");
    Serial.print("pos 1 ");
    Serial.print(orderedReadings[0]);
    Serial.print (" ");
    Serial.println(readings[0]);
    Serial.print("pos 2 ");
    Serial.print(orderedReadings[1]);
    Serial.print (" ");
    Serial.println(readings[1]);
    Serial.print("pos 3 ");
    Serial.print(orderedReadings[2]);
    Serial.print (" ");
    Serial.println(readings[2]);
  }

  moveToPosition(VERTICAL, Z_MOTOR_STEPS/2, orderedReadings[0]);
  float reading1 = getAveragedReading(READING_SAMPLES);
  displayReadingOnLCD("r1: ", orderedReadings[0], reading1);

  if (DEBUG) {
    Serial.print("pos 1 was ");
    Serial.println(reading1);
  }

  moveToPosition(VERTICAL, orderedReadings[0], orderedReadings[1]);
  float reading2 = getAveragedReading(READING_SAMPLES);
  displayReadingOnLCD("r2: ", orderedReadings[1], reading2);

  if (DEBUG) {
    Serial.print("pos 2 was ");
    Serial.println(reading2);
  }

  moveToPosition(VERTICAL, orderedReadings[1], orderedReadings[2]);
  float reading3 = getAveragedReading(READING_SAMPLES);
  displayReadingOnLCD("r3: ", orderedReadings[2], reading3);

  if (DEBUG) {
    Serial.print("pos 3 was ");
    Serial.println(reading3);
  }

  if ((reading1 < reading2) && (reading1 < reading3)) {
    moveToPosition(VERTICAL, orderedReadings[2],orderedReadings[0]);
    return orderedReadings[0];
  }
  else {
    if ((reading2 < reading1) && (reading2 < reading3))
    {
      moveToPosition(VERTICAL, orderedReadings[2],orderedReadings[1]);
      return orderedReadings[1];
    }
    else {
      // no need to move as at this position already
      return orderedReadings[2];
    }
  }
}


float getAveragedReading(int readingSamples) {

  float total = 0.0;

  for (int i=0; i<readingSamples; i++) {
    total+=getReading();

  }

  return total/readingSamples;
}

void moveToPosition(int motor, int current, int pos) {

  if (DEBUG) logger ("moveToPosition called current %d, position %d", current, pos);

  if (motor == HORIZONTAL) {
    motorHorizontal.step(abs(current - pos), current-pos>0?BACKWARD:FORWARD, mode);
  }

  if (motor == VERTICAL) {
    motorVertical.step(abs(current - pos), current-pos>0?BACKWARD:FORWARD, mode);
  }
}

float addReading(int i, float averagedReading, int readingCount, float reading) {

  displayReadingOnLCD("pos: ", i, reading);
 Serial.println(free_memory());

  int barLevel = ((int) reading) - 400;
  constrain(barLevel, 0, 99);

  Serial.print(i);
  Serial.print("|");
  Serial.print(reading);
  Serial.print("|");
  for (int i=0; i<barLevel; i++) Serial.print("#");
  Serial.println("");


  return (((averagedReading) *  (float) (readingCount)) + reading) / (readingCount+1);
}

int getBestSignal(float readings[], int count) {

  float minimum = 999;
  int bestPosition = 0;

  for (int i=0; i< count; i++) {
    Serial.print(readings[i]);
    Serial.print(" ");
    if (readings[i] < minimum) {

      minimum = readings[i];
      bestPosition = i;
    }
  }

  if (DEBUG) logger("best Position was %d", bestPosition);

  return bestPosition;
}

void logger (const char* logstring, int arg1, int arg2) {

  char buffer[50];
  sprintf(buffer, logstring, arg1, arg2);

  Serial.println(buffer);
  Serial.flush();
}

void logger (const char* logstring, int arg1) {

  char buffer[50];
  sprintf(buffer, logstring, arg1);

  Serial.println(buffer);
  Serial.flush();
}

void doScan() {

  if (!isPowerOn()) togglePowerSwitch();
  if (!isScanning()) toggleSearchSwitch();
  // wait for scanning to start
  delay(8000);

  // stop it waggling about when the x_y motor is moving
  //  lockMotor(VERTICAL);

  int x_y_pos = sweep_x_y(sweepsPerSearch);
  int z_pos =  sweep_z(sweepsPerSearch);

  // we are now pointing at the tag; take a good reading to
  // establish distance
  float distance = getAveragedReading(READING_SAMPLES);
  // now report the results

  char line1[16];
  char line2[16];
  char floatNum[8];

  sprintf(line1, "H: %d, V: %d", x_y_pos, z_pos);
  Serial.print("reading: ");
  Serial.print(distance);
  Serial.print(" estimated distance: ");
  Serial.println(getEstimatedDistance(distance));

  line2[0] = '\0';
  strcat(line2, "dist: ");
  strcat(line2, dtostrf(getEstimatedDistance(distance), 5, 2, floatNum));
  strcat(line2, "cm");

  Serial.println(strlen(line2));
  Serial.println(strlen(floatNum));

  lcdPrint (line1, true, 0, 0);
  lcdPrint (line2, false, 0, 1);

  if (DEBUG) logger ("back to orignal positions relative x/y = %d, z = %d", (int) MOTORSTEPS/2 - x_y_pos, (int) Z_MOTOR_STEPS/2 - z_pos);
  moveToPosition(VERTICAL, z_pos, Z_MOTOR_STEPS/2);
  moveToPosition(HORIZONTAL, x_y_pos, MOTORSTEPS/2);

  delay (5000);

  if (isScanning()) toggleSearchSwitch();
  if (isPowerOn()) togglePowerSwitch();


}

// sort an array of n integers and return the array indexes of the sorted
// array. Sort is largest to smallest
void sortOrder (float arr[], int retArray[], int n) {

  // initialise an array of int 0, 1, 2 ... n
  for (int k=0; k<n;k++) retArray[k] = k;

  if (DEBUG) {
    printArray(arr, "array to sort", n);
  }
  float tmpNum; //used to swap values of two array entries
  int tmpInt; //used to swap the associated indexes for those values
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < n - 1; j++) {
      if (arr[j] > arr[j + 1]) {

        tmpNum = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = tmpNum;

        tmpInt = retArray[j];
        retArray[j] = retArray[j + 1];
        retArray[j + 1] = tmpInt;
      }
    }
  }

  if (DEBUG) {
    printArray(arr, "sorted", n);
    printArray(retArray, "retArray", n);
  }
}

void lockMotor(int motor) {
  // simulate locking the motor by moving it back and forward

  // numbers are arbitrary as the moveToPosition call is relevant
  moveToPosition(motor, 1, 0);
  moveToPosition(motor, 0, 1);


}

void displayReadingOnLCD(char* text, int i, float reading){

  char disp[16];

  lcdPrint(text, true, 0, 0);
  lcdPrint(itoa(i,disp, 10), false, strlen(text)+1, 0 );
  lcdPrint (dtostrf(reading, 15, 1, disp), false, 0, 1);

  free (disp);
}

extern int  __bss_end;
extern int  *__brkval;
int free_memory(){
 int free_memory;
 if((int)__brkval == 0)
   free_memory = ((int)&free_memory) - ((int)&__bss_end);
 else
   free_memory = ((int)&free_memory) - ((int)__brkval);

return free_memory;
}


The modifications to the LiquidCrystal library are listed below - marked up with 'PDJH'. First the header file LiquidCrystal.h
#ifndef LiquidCrystal_h
#define LiquidCrystal_h

#include &lt;inttypes.h&gt;
#include "Print.h"

// commands
#define LCD_CLEARDISPLAY 0x01
#define LCD_RETURNHOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CURSORSHIFT 0x10
#define LCD_FUNCTIONSET 0x20
#define LCD_SETCGRAMADDR 0x40
#define LCD_SETDDRAMADDR 0x80

// flags for display entry mode
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00

// flags for display on/off control
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00

// flags for display/cursor shift
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00

// flags for function set
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00

class LiquidCrystal : public Print {
public:
  LiquidCrystal(uint8_t rs, uint8_t enable,
		uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
		uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7);
  LiquidCrystal(uint8_t rs, uint8_t rw, uint8_t enable,
		uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
		uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7);
  LiquidCrystal(uint8_t rs, uint8_t rw, uint8_t enable,
		uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3);
  LiquidCrystal(uint8_t rs, uint8_t enable,
		uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3);

  // PDJH added this constructor for 3 pin operation
  LiquidCrystal(uint8_t latch, uint8_t clock, uint8_t data,
  				uint8_t rs, uint8_t enable, uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3);
  // PDJH

  void init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t enable,
	    uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
	    uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7);

  void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS);

  void clear();
  void home();

  void noDisplay();
  void display();
  void noBlink();
  void blink();
  void noCursor();
  void cursor();
  void scrollDisplayLeft();
  void scrollDisplayRight();
  void leftToRight();
  void rightToLeft();
  void autoscroll();
  void noAutoscroll();

  void createChar(uint8_t, uint8_t[]);
  void setCursor(uint8_t, uint8_t);
  virtual void write(uint8_t);
  void command(uint8_t);
private:
  void send(uint8_t, uint8_t);
  void write4bits(uint8_t);
  void write8bits(uint8_t);
  void pulseEnable();


  uint8_t _rs_pin; // LOW: command.  HIGH: character.
  uint8_t _rw_pin; // LOW: write to LCD.  HIGH: read from LCD.
  uint8_t _enable_pin; // activated by a HIGH pulse.
  uint8_t _data_pins[8];

  uint8_t _displayfunction;
  uint8_t _displaycontrol;
  uint8_t _displaymode;

  uint8_t _initialized;

  uint8_t _numlines,_currline;


  // PDJH added this method to allow writing to pins via
  // the shift register this allowing comms with the LCD
  // display with only 3 pins
  void DigitalWrite(uint8_t, uint8_t);

  // added these variables for 3 pins that need to connect
  // to the arduino
  uint8_t _latch_pin;
  uint8_t _clock_pin;
  uint8_t _data_pin;

  // this will indicate whether to use the shift register
  // version or just a direct digitalWrite
  bool _3_pin_mode;

  // this store the current state of the register
  uint16_t shiftRegister;
  // PDJH

};

#endif

				
and the LiquidCrystal.cpp modifications
#include "LiquidCrystal.h"

#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
#include &lt;inttypes.h&gt;
#include "WProgram.h"

// When the display powers up, it is configured as follows:
//
// 1. Display clear
// 2. Function set:
//    DL = 1; 8-bit interface data
//    N = 0; 1-line display
//    F = 0; 5x8 dot character font
// 3. Display on/off control:
//    D = 0; Display off
//    C = 0; Cursor off
//    B = 0; Blinking off
// 4. Entry mode set:
//    I/D = 1; Increment by 1
//    S = 0; No shift
//
// Note, however, that resetting the Arduino doesn't reset the LCD, so we
// can't assume that its in that state when a sketch starts (and the
// LiquidCrystal constructor is called).

LiquidCrystal::LiquidCrystal(uint8_t rs, uint8_t rw, uint8_t enable,
			     uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
			     uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7)
{
  init(0, rs, rw, enable, d0, d1, d2, d3, d4, d5, d6, d7);
}

LiquidCrystal::LiquidCrystal(uint8_t rs, uint8_t enable,
			     uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
			     uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7)
{
  init(0, rs, -1, enable, d0, d1, d2, d3, d4, d5, d6, d7);
}

LiquidCrystal::LiquidCrystal(uint8_t rs, uint8_t rw, uint8_t enable,
			     uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3)
{
  init(1, rs, rw, enable, d0, d1, d2, d3, 0, 0, 0, 0);
}

LiquidCrystal::LiquidCrystal(uint8_t rs,  uint8_t enable,
			     uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3)
{
  init(1, rs, -1, enable, d0, d1, d2, d3, 0, 0, 0, 0);
}

// PDJH 3 pin mode constructor; note that rs, enable and the d0-d3 pins
// need to be between 1 and 8 for the shift register (assuming only one)
// register is being used
LiquidCrystal::LiquidCrystal(uint8_t latch, uint8_t clock, uint8_t data,
							 uint8_t rs, uint8_t enable, uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3)
							 : _latch_pin (latch), _clock_pin (clock), _data_pin (data),
							   shiftRegister(0),
							   _3_pin_mode(true)
{
  pinMode(_latch_pin, OUTPUT);
  pinMode(_clock_pin, OUTPUT);
  pinMode(_data_pin, OUTPUT);

  init(1, rs, -1, enable, d0, d1, d2, d3, 0, 0, 0, 0);
}
// PDJH


void LiquidCrystal::init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t enable,
			 uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
			 uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7)
{
  _rs_pin = rs;
  _rw_pin = rw;
  _enable_pin = enable;

  _data_pins[0] = d0;
  _data_pins[1] = d1;
  _data_pins[2] = d2;
  _data_pins[3] = d3;
  _data_pins[4] = d4;
  _data_pins[5] = d5;
  _data_pins[6] = d6;
  _data_pins[7] = d7;

  pinMode(_rs_pin, OUTPUT);
  // we can save 1 pin by not using RW. Indicate by passing -1 instead of pin#
  if (_rw_pin != -1) {
    pinMode(_rw_pin, OUTPUT);
  }
  pinMode(_enable_pin, OUTPUT);

  if (fourbitmode)
    _displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
  else
    _displayfunction = LCD_8BITMODE | LCD_1LINE | LCD_5x8DOTS;

  begin(16, 1);
}

void LiquidCrystal::begin(uint8_t cols, uint8_t lines, uint8_t dotsize) {
  if (lines > 1) {
    _displayfunction |= LCD_2LINE;
  }
  _numlines = lines;
  _currline = 0;

  // for some 1 line displays you can select a 10 pixel high font
  if ((dotsize != 0) && (lines == 1)) {
    _displayfunction |= LCD_5x10DOTS;
  }

  // SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
  // according to datasheet, we need at least 40ms after power rises above 2.7V
  // before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50
  delayMicroseconds(50000);
  // Now we pull both RS and R/W low to begin commands
  DigitalWrite(_rs_pin, LOW);
  DigitalWrite(_enable_pin, LOW);
  if (_rw_pin != -1) {
    DigitalWrite(_rw_pin, LOW);
  }

  //put the LCD into 4 bit or 8 bit mode
  if (! (_displayfunction & LCD_8BITMODE)) {
    // this is according to the hitachi HD44780 datasheet
    // figure 24, pg 46

    // we start in 8bit mode, try to set 4 bit mode
    write4bits(0x03);
    delayMicroseconds(4500); // wait min 4.1ms

    // second try
    write4bits(0x03);
    delayMicroseconds(4500); // wait min 4.1ms

    // third go!
    write4bits(0x03);
    delayMicroseconds(150);

    // finally, set to 8-bit interface
    write4bits(0x02);
  } else {
    // this is according to the hitachi HD44780 datasheet
    // page 45 figure 23

    // Send function set command sequence
    command(LCD_FUNCTIONSET | _displayfunction);
    delayMicroseconds(4500);  // wait more than 4.1ms

    // second try
    command(LCD_FUNCTIONSET | _displayfunction);
    delayMicroseconds(150);

    // third go
    command(LCD_FUNCTIONSET | _displayfunction);
  }

  // finally, set # lines, font size, etc.
  command(LCD_FUNCTIONSET | _displayfunction);

  // turn the display on with no cursor or blinking default
  _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
  display();

  // clear it off
  clear();

  // Initialize to default text direction (for romance languages)
  _displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
  // set the entry mode
  command(LCD_ENTRYMODESET | _displaymode);

}

/********** high level commands, for the user! */
void LiquidCrystal::clear()
{
  command(LCD_CLEARDISPLAY);  // clear display, set cursor position to zero
  delayMicroseconds(2000);  // this command takes a long time!
}

void LiquidCrystal::home()
{
  command(LCD_RETURNHOME);  // set cursor position to zero
  delayMicroseconds(2000);  // this command takes a long time!
}

void LiquidCrystal::setCursor(uint8_t col, uint8_t row)
{
  int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
  if ( row > _numlines ) {
    row = _numlines-1;    // we count rows starting w/0
  }

  command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
}

// Turn the display on/off (quickly)
void LiquidCrystal::noDisplay() {
  _displaycontrol &= ~LCD_DISPLAYON;
  command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal::display() {
  _displaycontrol |= LCD_DISPLAYON;
  command(LCD_DISPLAYCONTROL | _displaycontrol);
}

// Turns the underline cursor on/off
void LiquidCrystal::noCursor() {
  _displaycontrol &= ~LCD_CURSORON;
  command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal::cursor() {
  _displaycontrol |= LCD_CURSORON;
  command(LCD_DISPLAYCONTROL | _displaycontrol);
}

// Turn on and off the blinking cursor
void LiquidCrystal::noBlink() {
  _displaycontrol &= ~LCD_BLINKON;
  command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal::blink() {
  _displaycontrol |= LCD_BLINKON;
  command(LCD_DISPLAYCONTROL | _displaycontrol);
}

// These commands scroll the display without changing the RAM
void LiquidCrystal::scrollDisplayLeft(void) {
  command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
}
void LiquidCrystal::scrollDisplayRight(void) {
  command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
}

// This is for text that flows Left to Right
void LiquidCrystal::leftToRight(void) {
  _displaymode |= LCD_ENTRYLEFT;
  command(LCD_ENTRYMODESET | _displaymode);
}

// This is for text that flows Right to Left
void LiquidCrystal::rightToLeft(void) {
  _displaymode &= ~LCD_ENTRYLEFT;
  command(LCD_ENTRYMODESET | _displaymode);
}

// This will 'right justify' text from the cursor
void LiquidCrystal::autoscroll(void) {
  _displaymode |= LCD_ENTRYSHIFTINCREMENT;
  command(LCD_ENTRYMODESET | _displaymode);
}

// This will 'left justify' text from the cursor
void LiquidCrystal::noAutoscroll(void) {
  _displaymode &= ~LCD_ENTRYSHIFTINCREMENT;
  command(LCD_ENTRYMODESET | _displaymode);
}

// Allows us to fill the first 8 CGRAM locations
// with custom characters
void LiquidCrystal::createChar(uint8_t location, uint8_t charmap[]) {
  location &= 0x7; // we only have 8 locations 0-7
  command(LCD_SETCGRAMADDR | (location << 3));
  for (int i=0; i<8; i++) {
    write(charmap[i]);
  }
}

/*********** mid level commands, for sending data/cmds */

inline void LiquidCrystal::command(uint8_t value) {
  send(value, LOW);
}

inline void LiquidCrystal::write(uint8_t value) {
  send(value, HIGH);
}

/************ low level data pushing commands **********/

// write either command or data, with automatic 4/8-bit selection
void LiquidCrystal::send(uint8_t value, uint8_t mode) {
  DigitalWrite(_rs_pin, mode);

  // if there is a RW pin indicated, set it low to Write
  if (_rw_pin != -1) {
    DigitalWrite(_rw_pin, LOW);
  }

  if (_displayfunction & LCD_8BITMODE) {
    write8bits(value);
  } else {
    write4bits(value>>4);
    write4bits(value);
  }
}

void LiquidCrystal::pulseEnable(void) {
  DigitalWrite(_enable_pin, LOW);
  delayMicroseconds(1);
  DigitalWrite(_enable_pin, HIGH);
  delayMicroseconds(1);    // enable pulse must be >450ns
  DigitalWrite(_enable_pin, LOW);
  delayMicroseconds(100);   // commands need > 37us to settle
}

void LiquidCrystal::write4bits(uint8_t value) {
  for (int i = 0; i < 4; i++) {
    pinMode(_data_pins[i], OUTPUT);
    DigitalWrite(_data_pins[i], (value >> i) & 0x01);
  }

  pulseEnable();
}

void LiquidCrystal::write8bits(uint8_t value) {
  for (int i = 0; i < 8; i++) {
    pinMode(_data_pins[i], OUTPUT);
    DigitalWrite(_data_pins[i], (value >> i) & 0x01);
  }

  pulseEnable();
}

// PDJH added for 3 pin mode; DigitalWrite uses the shift register to emulate 8 additional
// pins numbered 1 to 8 (a second could theoretically be added but might be slower).
// This function determines whether we are in 3-pin mode and if so modifies the current
// shiftRegister value as if that particular logical pin had been set high or low.
// For example, if pins 1, 2 and 7 are set HIGH the shiftRegister will hold the value b01000011 ie. decimal 67
// if pin 4 is then set HIGH, the next value shifted out will be b01001011 which is decimal 75.
// @TODO: 1. allow setting of many pins simultaneously when the code is looping through
//           the _data_pins array.

void LiquidCrystal::DigitalWrite(uint8_t pin, uint8_t value) {

	if (_3_pin_mode) {

 	 //ground latchPin and hold low for as long as you are transmitting
	  digitalWrite(_latch_pin, LOW);

	  int shiftValue = round(pow(2,pin-1));

	  if (value == HIGH) {
	    shiftRegister|=shiftValue;
  		}
 		 else {
		    shiftRegister&=~shiftValue;
 	 	}
 		shiftOut(_data_pin, _clock_pin, MSBFIRST, shiftRegister);

 	 	//return the latch pin high to signal chip that it
 	 	//no longer needs to listen for information
 	 	digitalWrite(_latch_pin, HIGH);
    }
	else {
	// not in 3 pin mode so perform a normal write
	digitalWrite(pin, value);
	}
}

// PDJH

JSN Boot template designed by JoomlaShine.com