Saturday 29 June 2013

Automated Gantry Version 1.0 is complete

I am now deeming this stage that I'm at Automated Gantry Version 1.0. It can now carry out a storage and retrieval function and has all components connected and functioning. In saying this, everything is still too inaccurate for an inventory system so a the moment the storage and retrieval works off an input coordinate, not an item number input. This requires will require more consistency, which cannot be achieved by lego. In addition to that, the teethed pulley hub which was 3D printed was not printed accurately enough to get maximum grip to the timing belt. Thus to get the right tension to make it work, I had to make the x axis longer  using lots of tape to hold it in place. I was in a rush because I had to get the prototype submitted the next day for school.

Flaws exist but nonetheless the robot does function decently where it is at now. A video of this is below consisting of scenarios each at a different angle of the store function being run (to coordinate 15,5), a home function being run (so the robot has to move back to the start position, otherwise it'll just go down and pick up straight away which is no fun), and then the get function (back to coordinate 15,5).

Images of the physical changes and current state are below (The little blue thing is a blue lego brick with a piece of thin steel double sided taped to it. This is meant to represent the fridge).




Aside from the physical updates the code has also changed. On top of a few small value tweaks, a store and get function have been added. These right now run off a coordinate following the function. Eg store 18 3. The other change is that now upon start up, the robot will run routines servo up and home so that it will automatically reset its position to 0,0.

The new code is below:

Whilst blogging this, I have only been producing documentation for my IPT project. The user manual for this version as well as a few diagrams are below:

USER MANUAL:
Manual Control: This is done by use of a number of in-built commands stored in an array. When these commands are entered into the serial input, they will execute a routine which is assigned to that command and perform an action. These are in the table below (“ “) means a value must be written).

Command
Routine
Function
xf
XForward()
Move X-axis forward
xs
XStop()
Stop X-axis movement
xr
XReverse()
Move X-axis backwards
yf
YForward()
Move Y-axis forward
ys
YStop()
Stop Y-axis movement
yr
YReverse()
Move Y-axis backwards
se
ShowEncoders()
Print the current coordinates (in the form “x” “y”)
re
ResetEncoders()
Reset the coordinate value to 0,0
xy “ “ “ “
PickUp()
Move to the written x (input value in first “ “) and y coordinate (input value in second “ “)
IMPORTANT: Only values between 0 and 20 for x axis. Only values between 0 and 10 for y axis.
servo “ “
DriveServo()
Move servo to position “ “. This lowers and raises the electromagnet (The Z-Axis)
IMPORTANT: Only values between 60 (highest point) and 230 (lowest point) should be inputted.
solon
SolenoidOn()
Turn electromagnet on.
soloff
SolenoidOff()
Turn electromagnet off.
home
Home()
Move x and y axis back to starting position (0,0)

Automated Control:
Storing:
To store an item, you must tell the robot through the serial monitor input that you have an item in the docking bay which must be stored. To do this we enter the command Store. This tells the robot that it is going to the docking bay, and storing an item in an available space.
However, in accordance to the old layout of storage, each row will be dedicated to a certain model of fridge or freezer. Thus, you must also tell the robot, what row and column the fridge is being stored. This can be done by adding a space and then inputting an x y coordinate after.
The final resulting input line could for example be:
Store  15 6
Once this is typed into the input, press enter on the keyboard or click send to initiate the command. From here, the robot will complete the storing process.
Retrieving:
The retrieving process is conducted almost exactly the same as the storing process. Instead of the store command being typed into the serial input, type Get. This will tell the system it must retrieve an item and bring it back to the docking bay. However, you also need to input which model you wish to retrieve. To do this after a space, type the x y coordinate of the item you wish to retrieve.
The final resulting input line could for example be:
Get 15 6

Once it is typed, press enter or click send to start the retrieval. From here the robot will do everything else. 

CONTEXT DIAGRAM:
BLOCK DIAGRAM:
MASTER CIRCUIT:

The robot still hasn't got all the functionality I wanted so this Sunday I will begin the making of version 2.0. This will most probably have the exact same hardware and circuitry (possibly a few modifications) but will most significantly have a new structure built out of aluminium with the perfect tension for the timing belts and an inventory function which works off an item's stored location not a coordinate. 

Tuesday 18 June 2013

Cleaning up the Wires, Reworking Pulleys and Adding the Z Axis

One major problem that was actually signalling the end to the project as a whole was the pulley material. I was originally using rubber bands but they were so elastic that it resulted in a very inaccurate system. So inaccurate that there was no way it could store and retrieve items. However, after searching I managed to find a timing belt kit which worked perfectly. With this, the system drives to any point with ease and consistency. However, in using this, I had to 3D print two new wheel hubs and cable tie the timing belt to the X and Y axises.

The other thing affecting the consistency was the guiding rails. These were originally out of coat hanger but have now been replaced with hollow brass rods. These are a closer fit to the lego holes and hence give a good guide. It did have a bit of friction at the ends which was an issue but the timing belt solution overcame it.

Still, there wasn't enough resolution with my old 2 tab encoder wheels. So I have made 2 new encoder wheels with 8 tabs. This will provide much greater resolution for the coordinates but is yet to be attached. These are below:

I also finally cleaned up the wires on the robot. Originally there were wires everywhere and they'd get caught when it ran. However, they now have been trimmed, fed through containment units and guiders to the wires can now move the whole length of the X and Y without getting tangled or caught.

The Z axis is now fully functioning. It is run by a mini servo which is connected a long lego piece with cable ties and string to the servo arm. This is then attached to another long lego piece making it retract and extend. From Testing I have got a range of position 60-230 for this servo so that it doesn't lock on the Y-axis or go out of its range.

The circuit board has also been attached to the base using hex stands. This way the board can be easily screwed off at any time.

The code has also been changed now neater (separated into tabs) and more efficient in its function. This is below:

Automated Gantry: This contains all the integers (data type for number storage) to tell the program which pins to reference on the eleven for each variable. It also outlines whether each variable is an INPUT or OUTPUT.  It also contains general routines such as that to print out the location of the gantry, to move back to (0,0).
#include <Wire.h>
#include <Servo.h>

int Xdir=6;          // drive pin to X direction relay
int Ydir=4;          // drive pin to Y direction relay
int Xenable=5;       // drive pin to X enable relay
int Yenable=10;      // drive pin to Y enable relay
int Xpulse=3;              // input from X encoder
int Ypulse=2;              // input from Y encoder
int Solenoid = 9;
boolean Xdirection=false;  // true if motor forward / false if reverse
boolean Ydirection=false;  // true if motor forward / false if reverse

Servo Lift;
int Xposition=0;           // encoder count for X
int Yposition=0;           // encoder count for Y
boolean Xtravel = false;
boolean Ytravel = false;
int OldXstate, OldYstate;  // input state of encoder pins (HIGH,LOW)

String inputString = "";
boolean stringComplete=false;
String cmd = "";
int arg [4] = {0,0,0,0};
int ArgIndex;

void setup()
{
  Serial.begin(9600);
  Serial.setTimeout(2);
  inputString.reserve(200);
 
  Serial.println(" Automated Gantry Warehouse");
  pinMode(Xdir, OUTPUT);
  pinMode(Ydir, OUTPUT);
  pinMode(Xenable, OUTPUT);
  pinMode(Yenable, OUTPUT);
  pinMode(Solenoid,OUTPUT);
  pinMode(Xpulse,INPUT);
  pinMode(Ypulse,INPUT);
  digitalWrite(Solenoid,LOW);
  Lift.attach(7);
  Lift.write(128);
  OldXstate = digitalRead(Xpulse);    // get state of encoder inputs
  OldYstate = digitalRead(Ypulse);    // ...
}

void loop(){
 // serialEvent();
  if (stringComplete){
  HandleCommand();
  inputString="";
  stringComplete=false;
  } 
}

void Home(){
  MoveTo(0,0);
}
 
void MoveTo(int x, int y){
//  if ((x < 0) || (y < 0) || (x > 40) || (y > 40)){
//      Serial.println("XY commands out of bounds");
//      return;
//  }
   
  Serial.println(y);
  if (x !=Xposition){
      if (x > Xposition) XForward();
      if (x < Xposition) XReverse();
  }
  while (Xposition !=x){
      CheckEncoders();
  }
  XStop();
  ShowEncoders();
  delay (1000);
//  ShowEncoders();
//  delay (2000);
  if (y != Yposition){
      if (y > Yposition) YForward();
      if (y < Yposition) YReverse();
  }
  ShowEncoders();
  while (y !=Yposition){
      CheckEncoders();
  }
  YStop();
  ShowEncoders();
}

void PickUp(){
  MoveTo(arg[0], arg[1]);
}  

Commands: This is the code for manual control of the robot. By inputting a command from the code in the serial monitor, it will reference the array of commands, and run the routine set to that command; movement of the x, movement of the y, on and off of electromagnet, driving the servo, going to a certain coordinate, resetting and reading the encoders to see the coordinate.
void HandleCommand(){
  ParseInput();
  if (cmd == "xf") XForward();
  if (cmd == "xs") XStop();
  if (cmd == "xr") XReverse();
  if (cmd == "yf") YForward();
  if (cmd == "ys") YStop();
  if (cmd == "yr") YReverse();
  if (cmd == "se") ShowEncoders();
  if (cmd == "xy") PickUp();
  if (cmd == "re") ResetEncoders();
  if (cmd == "servo") DriveServo();
  if (cmd == "solon") SolenoidOn();
  if (cmd == "soloff") SolenoidOff();
  if (cmd == "home") Home();
}

// from the serial input separate all arguments
void ParseInput(){
  Serial.println(inputString);
  int space, slash;
  for(int x = 0; x < 4; x++){              // flush the last args
    arg[x] = 0;
  }
  ArgIndex = 0;                                      // reset number of args to zero
  space=inputString.indexOf(' ');
  slash = inputString.indexOf('\n');
  inputString = inputString.substring(0,slash);
  cmd = inputString.substring(0,space);
  inputString = inputString.substring(space+1,slash);
  while (inputString.indexOf(' ') > 0){
     space = inputString.indexOf(' ');
     arg[ArgIndex] = inputString.substring(0,space).toInt();
     inputString = inputString.substring(space+1,inputString.length());
     ArgIndex++;  
  }
  arg[ArgIndex] = inputString.toInt();
}

void serialEvent(){
//  Serial.println("l");
  while(Serial.available()){
//    Serial.println("d");
   char inChar=(char)Serial.read();
   inputString += inChar;
//   Serial.println(inputString);
   if(inChar=='\n'){
       stringComplete=true;
//       Serial.println("complete");
   }
  }
}

Encoders: This contains the code for reading the photo interrupters and then updating the coordinates in accordance. This essentially runs the function of the location system of the robot knows where it is. On top of this it contains the routines for resetting the coordinates back to (0,0) and printing out the current position.
The arduino reads the photo interrupter which depending on the starting position of the encoder wheel attached to the motor (gap or filled) will read the starting position HIGH or LOW. It will then continue to read the photo interrupter until it detects a change. Once this change is detected it must update the position of the axis. To determine whether to decrement or increment the value, it will check the variable Xdirection (in Boolean is false). It equals Xdirection (false) then the value of the X coordinate will increase by 1. Otherwise if it does not equal Xdirection, it will decrease by 1. It will then update the OldXstate so it knows where it is now. This process continuously repeats at an extremely fast rate for the X axis and Y axis to keep an update of the location of the gantry.
void CheckEncoders(){
  int X,Y;
  X = digitalRead(Xpulse);
  Y = digitalRead(Ypulse);
//  Serial.print(Xposition);
//  Serial.print("  ");
//  Serial.println(Yposition);
  delay(200);
  if (OldXstate == LOW){
      if(X ==HIGH){
          if (Xdirection) Xposition++;
          if (!Xdirection) Xposition--;
          OldXstate = X;
          goto  CheckY;
      }
    }
  if (OldXstate == HIGH){
      if (X == LOW){
          if (Xdirection) Xposition++;
          if (!Xdirection) Xposition--;
          OldXstate = LOW;
      }
  }
  CheckY:
   if (OldYstate == LOW){
      if(Y ==HIGH){
          if (Ydirection) Yposition++;
          if (!Ydirection) Yposition--;
          OldYstate = Y;
 //         Serial.println(Yposition);
          return;
      }
    }
  if (OldYstate == HIGH){
      if (Y == LOW){
          if (Ydirection) Yposition++;
          if (!Ydirection) Yposition--;
          OldYstate = LOW;
  //        Serial.println(Yposition);
      }
  }
}

// used in serial to show encoder counts
void ShowEncoders(){
    Serial.print("X= ");
    Serial.print(Xposition);
    Serial.print( " Y= ");
    Serial.println(Yposition);
}

void PrintPosition(){
  Serial.print(Xposition);
  Serial.print("  ");
  Serial.println(Yposition);
}

void ResetEncoders(){
  Xposition = 0;
  Yposition = 0;
  Serial.println("Encoders reset");
}

Motor Control: This contains the routines for the driving of the x-axis and y-axis motors. This includes forward movement, reverse movement, stopping of the motors. These are referenced in an array and are initiated by the commands outlined in the “commands” tab. When the motor is set at HIGH, it moves in a forward direction. When LOW, it moves in reverse. When the enable is LOW, the motor is off. When the enable is HIGH, the motor is on.
void SetXForward(){
 digitalWrite(Xdir,HIGH);
 Xdirection= true;
}

void SetXReverse(){
 digitalWrite(Xdir,LOW);
 Xdirection = false;
}

void XForward(){
  Xdirection=true;
  digitalWrite(Xenable,LOW);
  delay(30);
  digitalWrite(Xdir,HIGH);
  digitalWrite(Xenable,HIGH);
}

void YForward(){
  Ydirection=true;
  digitalWrite(Yenable,LOW);
  delay(30);
  digitalWrite(Ydir,HIGH);
  digitalWrite(Yenable,HIGH);
}

void XReverse(){
  Xdirection=false;
  digitalWrite(Xenable,LOW);
  delay(30);
  digitalWrite(Xdir,LOW);
  digitalWrite(Xenable,HIGH);
}

void YReverse(){
  Ydirection=false;
  digitalWrite(Yenable,LOW);
  delay(30); 
  digitalWrite(Ydir,LOW);
  digitalWrite(Yenable,HIGH);
}

void XStop(){
  digitalWrite(Xenable,LOW);
  digitalWrite(Xdir,LOW);
  Xtravel = false;
}

void YStop(){
  digitalWrite(Yenable,LOW);
  digitalWrite(Ydir,LOW);
  Ytravel = false;
}

void Test(){
  XForward();
  delay(1250);
  XStop();
  delay(1000);
  XReverse();
  delay(1250);
  XStop();
  delay(1000);

  YForward();
  delay(500);
  YStop();
  delay(1000);
  YReverse();
  delay(500);
  YStop();
  delay(1000);
}

Servo: This contains the routine for driving the servo to a specific angle. By doing so, it will lower a string with the electromagnet attached to it. This will bring it low enough to pick up an item. This code can function manually from the serial monitor. When the command for the servo is called, and followed by a value, the servo will drive to that angle.
void DriveServo(){
  Lift.write(arg[0]);
}

Solenoid: This contains the code for the on and off routines of the electromagnet. When it is written HIGH, it is on. When it is written LOW, it is off.

void SolenoidOn(){
  digitalWrite(Solenoid,HIGH);
}
void SolenoidOff(){
   digitalWrite(Solenoid,LOW);
}

In this code new commands for manual control have also been added. They are now:
Command
Routine
Function
xf
XForward()
Move X-axis forward
xs
XStop()
Stop X-axis movement
xr
XReverse()
Move X-axis backwards
yf
YForward()
Move Y-axis forward
ys
YStop()
Stop Y-axis movement
yr
YReverse()
Move Y-axis backwards
se
ShowEncoders()
Print the current coordinates (in the form “x” “y”)
re
ResetEncoders()
Reset the coordinate value to 0,0
xy “ “ “ “
PickUp()
Move to the written x (input value in first “ “) and y coordinate (input value in second “ “)
servo “ “
DriveServo()
Move servo to position “ “. This lowers and raises the electromagnet (The Z-Axis)
IMPORTANT: Only values between 60 (highest point) and 230 (lowest point) should be inputted.
solon
SolenoidOn()
Turn electromagnet on.
soloff
SolenoidOff()
Turn electromagnet off.
home
Home()
Move x and y axis back to starting position.

All that is left now is the code for the item inventory, putting on a new electromagnet (as my other one broke), adding from limit switches and drilling some new holes to connect the robot to the base (it was slightly out of alignment before). The new electromagnet, resistors (10K) for the limit circuit circuit, and limit switches are below:

This should be done on wednesday and I'll upload a video sometime after.