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