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 <inttypes.h>
#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 <stdio.h>
#include <string.h>
#include <inttypes.h>
#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