Migration robots


The migration project at Round Lake needs an automated device to raise and lower a zooplankton microcosm over the water column on a daily basis. Since we want to manipulate the depth and amplitude of the diel migration that the microcosm experiences, the project required some sort of a logic controller. Enter our first Arduino project. Using a 1/4 servo converted for continuous rotation, hall sensors for positioning and counting rotations, and a UNO Arduino board, we have made the ‘Migration Robots’. It lowers and raises microcosms on any cycle frequency, and importantly can maintain it’s depth position even in the face of unexpected forces on the line. The following sections provide schematics, code and pictures for anyone interested in this type of robot.

Pilot phase

We wrapped up the pilot stage of the project (March 2014). The figure below shows the servo, spool, magnetic sensor and Arduino board under development. The servo can draw up to 1.5 amps (stall loads), which is much greater than the Arduino board can power. To reliably power the servo, sensor and board, we build a small bit of power regulation circuitry. The schematic and a picture of the power regulation board are shown. photoschematic 2014-04-12 09.38.03


Project phase

After playing around with the prototype, we’ve made some adjustments for the full project phase. The migration robot has a 4:1 gear ratio to strike a better balance between the servo load and maximum rotation speed. It will also include a top of line magnetic sensor for self-calibration. The chassis is made from sheet PVC, and the two magnetic sensors (unidirectional hall effect sensors) are epoxied directly to the chassis. One sensor is for counting spool rotations and the second is to detect the magnet attached to the top of the line.


Chassis design The chassis is constructed from sheet PVC (6mm), a 1/4 scale servo bracket, bearing hub and small mounting plate. The hardware was purchased from ServoCity, and works well. Pics show the dimensions and the chassis with servo and bearing hub attached. We modified it a bit from these drawings, but the essence is the same.

Chasis design-page 1 Chasis design-page 2 2014-04-30 18.43.04 2014-04-30 18.43.30

 

 

 

 

 

 

 


Magnetic sensors The Hall effect sensors are epoxied directly in groves cut into the chassis. One is mounted on the top side to detect the magnet attached to the spool, and the other goes through a hole in the chassis to mount on the bottom side to detect the magnet attached to the top of each line.2014-05-01 00.00.56

 

 

 

 

 


Power and signal regulation board design The power/signal board can take anywhere from 7V to 24V to power the 2 hall effect sensors (5V) and the servo (6V). The board is constructed using a narrow solder board with 4 rows of copper as shown in the following schematic and pictures. power regulator circuit    
Time of day assembly  [more to come here…]

Arduino code There are two versions of the code that we are using depending on whether we just need to replace water in the tube (‘jigging’) or we need the full migration (‘migration’). While each is tailored to the migration robots, they give an indication of how we approached the overall logic of the system.

Jigging code without time of day function:

 #include <Time.h>
#include <Scheduler.h>
#include <Servo.h>

//NEED auto detect of forward/backward speed
//Watches for end of line magnet in both directions

const int spool_sen[]={A5,A4,A3,A2,A1,A0}; //array holding location of the read pins for the spool sensor
const int line_sen[]={2,3,4,5,6,7}; //array holding location of the read pins for the line sensor
const int servo_pins[]={8,9,10,11,12,13}; //array holding location of the write pins for the servos
int trn[6]; //will be used to hold the requested spool turns
int S=3; //number of servos on controller.
int d=5; //microsecond delay between taking sensor readings
const float spoolCirc=0.075; //spool circumference (m)
const float MaxDepth[]={0.6,0.6,0.6,0.6,0.6,0.6}; //maximum depth (m) for travel
const float JigDepth[]={1.0,1.0,1.0,1.0,1.0,1.0}; //jig depth (m) for travel

const float JigInterval=60.0; //time between successive jig cycles (min)
//const float MigrateInterval=24.0*60.0; //time between successive migration cycles (min)
const float InitInterval=24.0*60.0; //time between successive calls to initilize() (min) 

Servo myServo[6]; //servo object handlers
int servo_stop[6]; //array of signal values to stop the servo.
int stop_deg[6]; //angle that represents the servo stop value

unsigned long t24; //t24 is for timing to call initialize() every 24hrs 

void setup(){

 Serial.begin(9600);
 setTime(1401091032); //seconds since 1970
 Serial.print("day: ");Serial.println(day());
 Serial.print("hour: ");Serial.println(hour());
 Serial.print("minute: ");Serial.println(minute());
 timeStatus();

 initialize();
 for(int i=0;i<S;i++) trn[i]=-3; rotate(trn); //move magnet to water surface

 t24=millis(); //start count down for calling initialize()
}

void loop(){

 //check if initialize needs to be called again
 if(millis()>(t24+InitInterval*1000.0*60.0)){initialize();t24=millis();}
 //main jigging movement loop
 int num_rev[6];
 long t=millis();

 for(int i=0;i<S;i++) num_rev[i]= floor(JigDepth[i]/spoolCirc); //round down

 for(int i=0;i<S;i++) trn[i]=-num_rev[i]; rotate(trn);
 for(int i=0;i<S;i++) trn[i]=+num_rev[i]; rotate(trn);

 long t2=millis();

 hold(1000.0*60.0*JigInterval-(t2-t)); //wait until the jig cycle time has lapsed

}

//*** FUNCTIONS ***
void initialize(){

 //find the servo stop value
 int Up,Dw,A,STEP;
 long P=2000; //milliseconds to wait while checking for stopped servo

 for(int i=0;i<S;i++){
 Up=1000; Dw=2000;STEP=10;
 myServo[i].attach(servo_pins[i]);pinMode(line_sen[i],INPUT);pinMode(spool_sen[i],INPUT); //initialize servos and sensing pins
 myServo[i].writeMicroseconds(Dw);delay(2000); //clear top of the line magnet

 for(int j=0;j<2;j++){
 A=0;
 do{
 myServo[i].writeMicroseconds(2000); //rotate down
 do{delay(d);}while(digitalRead(spool_sen[i])==1);
 myServo[i].writeMicroseconds(Up); //rotate up
 long t=millis();
 do{delay(d);if(millis()>t+P){A=1;break;};}while(digitalRead(spool_sen[i])==0);
 Up+=STEP;
 }while(A==0);

 A=0;
 do{
 myServo[i].writeMicroseconds(Dw); //rotate down
 long t=millis();
 do{delay(d);if(millis()>t+P){A=1;break;};}while(digitalRead(spool_sen[i])==1);
 myServo[i].writeMicroseconds(1000); //rotate up
 do{delay(d);}while(digitalRead(spool_sen[i])==0);
 Dw-=STEP;
 }while(A==0);
 Up-=4*STEP;Dw+=4*STEP;STEP=1;
 }
 Serial.print("Up: ");Serial.println(Up);
 Serial.print("Dw: ");Serial.println(Dw);
 servo_stop[i]=(Up+Dw)/2; myServo[i].writeMicroseconds(servo_stop[i]);
 stop_deg[i]=myServo[i].read(); //this is the value for checking whether servo is stopped
 } 

 //rotate spools upwards until top of line magnet detected
 for(int i=0;i<S;i++){
 myServo[i].writeMicroseconds(1000);delay(d);
 do{delay(d);}while(digitalRead(line_sen[i])==1);
 centre(i);
 }

}

//*** add in a return function to hold servos while others are spinning
void rotate(int *turns){//rotate given number of revolutions in either direction. {negative,positive,0} is {rotations down,rotations up,stop} for each servo
 int dir,H,flag,cnt,A; 

 for(int i=0;i<S;i++){
 flag=0; //flag for clearing the magnet (0 is no, 1 is yes)
 cnt=0; //rotation counts
 A=0; //flag for top of the line sensor
 dir=servo_stop[i];if(turns[i]!=0){dir=1000+1000*(turns[i]<0);};myServo[i].writeMicroseconds(dir);delay(50); //rotate in the correct direction
 do{
 H=digitalRead(spool_sen[i]);
 if(flag==0 & H==1) flag=1;
 if(flag==1 & H==0) {cnt+=1;flag=0;}
 if(digitalRead(line_sen[i])==0) {A=1;} //look for top of the line signal if turning up
 }while(cnt<abs(turns[i]) && A==0);

 //try to eject the top of the line sensor
 if(A==1){
 dir=1000+1000*(turns[i]>=0);//switch direction
 myServo[i].writeMicroseconds(dir);delay(50);
 for(int j=0;j<5;j++){
 H=digitalRead(spool_sen[i]);
 if(flag==0 & H==1) flag=1;
 if(flag==1 & H==0) flag=0;
 }
 A=0;
 }

 myServo[i].writeMicroseconds(servo_stop[i]);
 }

}

void centre(int i){//this functions returns the servo to the position of the magnetic sensor. i is the servo number
 int H;

 myServo[i].writeMicroseconds(2000);delay(d);
 do{delay(d);}while(digitalRead(spool_sen[i])==1);
 myServo[i].writeMicroseconds(servo_stop[i]); 

}

void hold(unsigned long t){//t is the length of time in milliseconds to hold.
 int H;
 unsigned long t0=millis();
 boolean flag; //true is to force staying in loop, false allows exit after check that servos have stopped

 do{
 for(int i=0;i<S;i++){
 H=digitalRead(spool_sen[i]); delay(d);
 if(H==1){ flag=true;
 myServo[i].writeMicroseconds(1000);delay(50); //rotate up
 if(digitalRead(spool_sen[i])==1) myServo[i].writeMicroseconds(2000); //wrong direction
 do{delay(d);}while(digitalRead(spool_sen[i])==1);
 myServo[i].writeMicroseconds(servo_stop[i]);
 flag=false;}
 }

 }while(millis()<(t0+t) || flag); //don't leave do loop until servos are stopped
} 


Final robot The final robots have heavy gauge fishing line wound (25m) around the spool with a magnet attached to the top snap swivel of each line. The wound line is used for the migration movement, and the length of line from the snap swivel to the tube determines the depth at which the migration will occur. Here are some pics of the 40 robots out at Round Lake.

2014-05-30 18.27.43 2014-05-30 18.26.23 2014-05-30 18.26.16