Sign up ×
Arduino Stack Exchange is a question and answer site for developers of open-source hardware and software that is compatible with Arduino. It's 100% free, no registration required.

My goal is to deliver very small quantities (100 microliters) of fluid to the (approximate) center of a 25mm x 75mm surface. My first idea was to repurpose an old inkjet printer for the project. I thought I'd load the fluid dispensers (probably 3ml syringes) into the unit where the ink cartridges would go and use an arduino to manipulate the linear encoder used by the printer to tell the dispenser unit where to go.

Main problem at the moment is that I'm having trouble getting the dispenser unit to go exactly where I want it to. I've tried incorporating the PID library into my code to get the head to oscillate around the desired position until it stops at the right location. However the oscillation never stops and the head always moves beyond the desired position.

I'm trying to figure out the easiest way to get this done. Maybe that'll mean moving away from using the inkjet printer but I'd like to get some feedback here before I make any decisions.

Code:

#include <Wire.h>
#include <Adafruit_MotorShield.h>
#include "utility/Adafruit_PWMServoDriver.h"
#include <PID_v1.h>

//
//// Interrupt information
//// Int 0 on pin 2 
//// Int 1 on pin 3 Not used
//
#define encoderI 2  // Interrupt 0 is use
#define encoderQ 4 // Only use one interrupt in this example
#define maxPos   3733 // left hand side
#define minPos   0    // right hand side 
#define maxSpeed 150
#define minSpeed 0
#define Freq     1600
#define maxWait  500 //12 millisec before stopping the motor
#define maxPositionError 10 //max error from actual desired position. It is approximately 0.032 of an inch
#define PIDsampleTime 25

//
//// Tuning parameters 
double kP=0.37; //Initial Proportional Gain 
double kI=2; //Initial Integral Gain 
double kD=0.020; //Initial Differential Gain 
double Drive = 0;

int outputSign;
//
double Setpoint, Input, Output;

PID myPID(&Input, &Output, &Setpoint,kP,kI,kD, DIRECT);

volatile int ActualHeadPosition; //current position will be modified with each interrupt
volatile int PreviousHeadPosition; //Previous position will be modified with each interrupt
volatile uint8_t Speed = maxSpeed;                      //Speed for the dispenser head
volatile unsigned long lastPositionTime;
volatile int setPoint;
volatile int Err;
volatile int lastError;
volatile int Integral;

// Create the motor shield object with the default I2C address
Adafruit_MotorShield AFMS = Adafruit_MotorShield(); 
//
//// Select which 'port' M1, M2, M3 or M4. In this case, M1
Adafruit_DCMotor *dispenserDCMotor = AFMS.getMotor(1);
//
void setup() {
     Serial.begin(9600);           // set up Serial library at 9600 bps also this is the baud rate per second for the input?
     pinMode(encoderI, INPUT); //pin #2 is assigned as an input
     pinMode(encoderQ, INPUT); 
     attachInterrupt(0, handleEncoder, CHANGE); 
//  
     AFMS.begin(Freq);  
//   
//    // Set the speed to start, from 0 (off) to 255 (max speed)
    dispenserDCMotor->setSpeed(Speed);
    dispenserDCMotor->run(FORWARD);
//    // turn on motor
    dispenserDCMotor->run(RELEASE);
//     
  Setpoint = 1800;
  myPID.SetSampleTime(PIDsampleTime);
  myPID.SetOutputLimits(minSpeed, maxSpeed);

    //turn the PID on
    myPID.SetMode(AUTOMATIC);

     //Initialize procedure should be the first thing
    Initialize();
}

void loop() {
  Serial.print(", ActualHeadPosition: ");Serial.print(ActualHeadPosition);Serial.print(", SetPoint: ");Serial.println(setPoint);

  setPoint = 1800;
  Input = ActualHeadPosition;
  outputSign = myPID.Compute();
  Err = setPoint - ActualHeadPosition;
  Serial.print("PID Output");Serial.print(Output);Serial.print(" Error: ");Serial.println(Err);
  Speed = Output;
  if (Err > 0){
    moveLeft();
    Serial.println("moveLeft");
  }
   else if (Err < 0){
     moveRight();
     Serial.println("moveRight");
  }

};
//
void handleEncoder(){  
  lastPositionTime = millis();
   PreviousHeadPosition = ActualHeadPosition;
   if(digitalRead(encoderI) == digitalRead(encoderQ)){
     //dispenser is moving to the left
     ActualHeadPosition++;
   }
   else {
     //dispenser is moving to the right
     ActualHeadPosition--;
}

 }

void moveLeft(){
  uint8_t i;
  dispenserDCMotor->run(FORWARD);
  dispenserDCMotor->setSpeed(Speed);  
}

void moveRight(){
  uint8_t i;

  dispenserDCMotor->run(BACKWARD);
  dispenserDCMotor->setSpeed(Speed);  
}

boolean isMotorMoving(){
  if (((millis() - lastPositionTime) > maxWait))
    return false;
  else
    return true;
}

void Initialize(){
    //Move to origin
    moveRight();
    while (isMotorMoving()){
  //    Serial.println("MOVING!");
  delay(10);
    }
    ActualHeadPosition = 0;
    PreviousHeadPosition = 0;
    dispenserDCMotor->setSpeed(0);
    dispenserDCMotor->run(RELEASE);

}
share|improve this question
    
maybe you just have to tune your pid settings – geometrikal May 27 '14 at 2:48

2 Answers 2

The reason why your slide/microarray microdispenser continually oscillates about a point rather than dispense the fluid containing your cells/DNA sample specifically at a particular specified/designated point on your slide/microarray is due to your code below:

if(digitalRead(encoderI) == digitalRead(encoderQ)){
     //dispenser is moving to the left
     ActualHeadPosition++;
   }

   else {
     //dispenser is moving to the right
     ActualHeadPosition--;
}

which tells the Arduino to actuate the motor regardless of the values stored in the vars encoderI & encoderQ.

share|improve this answer

The easiest option, using the code you have, is to spend a little time tuning the PID algorithm. The lines you are interested in are these

double kP=0.37; //Initial Proportional Gain 
double kI=2; //Initial Integral Gain 
double kD=0.020; //Initial Differential Gain

PID takes a setpoint and a current location, calculates error, and uses it to make a new output that should sent the location to the setpoint. It does this by adding

P*(error)+I*(all historic error added together)+D*(recent change in error)

By changing the multipliers P, I, and D, you change how it reacts to different situations. In your system, the D value will have very little effect (at least in my experience). You should probably keep that where it is, or even just set it to 0.

The P value controls direct response to error, and should be the main component in your setup. Try increasing this to make it move faster, and mildly decrease the oscillations.

The I value controls how much all historic error affects output. It is the factor leading to all the oscillations. Lowering it will make your output settle sooner. It also assists when fighting other constant forces, or if static friction keeps the head locked in one position too close for the P term to overpower. My guess is in your isolated and low friction system, it should be very low.

Increment these values slowly (.05 at a time maybe) and experiment, you should be able to fix your oscillations and make the system behave how you want.

share|improve this answer
    
Based on the description, the system is underdamped, and increasing D may help a lot. Decreasing I can help too - I is most useful in correcting standoff errors - when it never actually reaches the target. – AShelly May 28 '14 at 20:18
    
"I" is useful for such errors, but I suggest lowering it because the inkjet printer rails should not have much problem with friction or outside forces. In my experience, "D" is nearly inconsequential in similar systems, but I encourage experimentation. In any case, an underdamped system should need the damping ratio increased to 1, but increasing "D" would lower the damping ratio if my math is correct, based on this article: en.wikipedia.org/wiki/… – BrettAM May 28 '14 at 21:14
    
I haven't worked with inkjet printers, so I may be offbase. I was working off my memory, reinforced by this As I understand it, when the error is dropping quickly as you approach the target, D dials down the gain, helping prevent overshoot. – AShelly May 28 '14 at 21:42
    
I was referencing a model of PID as altering the coefficients on a 2nd order Diff EQ, but your link directly on the tuning of PID could be more accurate, depending on the actual characteristics of his system. experimentation is key. – BrettAM May 28 '14 at 21:57

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.