top of page
Search
  • Writer's pictureTimothy Bukowski

Linear Drive

Updated: Oct 3, 2020

September 2020

Overview

I made a linear drive that moves a plate in one dimension. A stepper motor powers the drive, and is controlled by an Arduino. This motor spins a screw with allows it to make a platform move a desired distance. While this is a simple one dimensional linear drive, two or three drives can be combined to allow for precise movement in two or three dimensions.


Objectives:

  • Learn how a stepper motor works

  • Develop code to control the two coil sets so that the motor spins using either half or full steps

  • Build a limit switch to be used for homing the platform

How it works

The drive is able to move desired distances in one direction by spinning a screw. A collar around the screw is connected to the platform and as the screw spins it makes the collar move along its length. We can find the lead of the screw, which is how much the collar moves per rotation of the screw. Since we can find the angle we need to rotate the motor, we can then spin it a desired number of steps. Step motors work in steps as the magnetic fields created by coils of wire are alternated to spin a permanent magnet rotor. Different motors have different numbers of magnets, which leads to different number of steps to complete a full rotation. The steps can also be cut in half by having two coils on simultaneously, causing the magnet to fall between them. This allows us to work with much finer accuracy.



Design and Construction


Platform

I decided to make as many parts of my design press fits as possible, since it would make re-using parts for future projects easier and I found that my glue did not stick well to the metal pieces. In the pictures above you can see the development of the platform with the proof of concept followed by the design and construction of the platform.


Homing button


The linear drive's reference point was at one end of the drive, where there is a button. When the drive hits the button it the controller knew that it was at the end of the rail and could move a desired distance from that point. In my design I decided to leave the button on the bread board and built a swinging arm that allowed the platform to press the button at a 90 degree angle.


Code


In the main loop I have the quick routine I used to test the motor, which includes the use of a couple of functions


moveTo - moves the platform a desired distance in mm

goHome - sends the platform back to its home position


As for the variables the step duration essentially controls the speed of the platform movement. The power variables control the power sent to the coils to prevent overheating of the motor shield. The variable st determines whether the motor is spinning with half or full steps. In both the moveTo and goHome functions as you can see there is a option for either full or half steps. The information is sent to the motor using the coilProperties function which sends either a high or low signal which makes corresponds to the direction created by the coil. The setCurrents function then sends current to the desired coil to create the magnetic field and make the motor move. This current is limited using fast switching similar to pulse width modulation, which keeps the motor and motor shield from overheating when the step sizes are larger.



Testing

To test the motor I first did a simple two way movement. I then did a more complex routine which used a combination of half and full steps and different movement speeds.


Angle Displacement vs Time


The angular velocity, which is shown in the following plots as the slop of the lines, decreases as the step duration increases. Also when using half steps the angular velocity about double, since the motor has to move two steps for every full step. However, in the image below you can see that the half step actually takes a little bit longer than double the full step. This is due to the fact that the step duration only accounts for the time that current is running through the coils, however, it does not account for the time that it takes for the Arduino code to run through the functions and write the data to the serial, which is not perfectly instantaneous. So since more steps are required for half stepping, the extra time added accumulates.


Coil Current vs Time

For the full step you can see the current in the coils switches as the movement direction switches. For the half step you can see how both coils are on at once. This does not occur for the full step.


Routine movement



Here I had the motor move first to the home position where it zeroed, then it moved 5mm and returned to home, then moved 30 mm.






Final Product







Arduino Code


const int N=1;

const int S=2;

const int OFF=0;



void setup() {

// Coil A:

pinMode(12, OUTPUT); //Dir

pinMode(3, OUTPUT); // On/Off

pinMode(9, OUTPUT); // Brake

// Coil B:

pinMode(13, OUTPUT);

pinMode(11, OUTPUT);

pinMode(8, OUTPUT);

// Switch

pinMode(10, INPUT);


shutdown_all();

Serial.begin(115200);

}



// ********************** USE THESE SETTINGS TO PREVENT OVERHEATING *********************

int power_stall=50; // Just enough to move. Use 7 for half-stepping, 50 for full-stepping

int power=80; // Set from 30 to 100 depending on required torque. Higher values make the motor shield hotter.

// Use values <= 50 for half-stepping (because both coils are on simultaneously)

int step_duration=1500; // For very long step_duration (>7000 microseconds) use low power to prevent overheating the shield.

// If motor gets stuck (especially for step_duration around 5500 microseconds), increase power.


// ALWAYS CHECK THE MOTOR SHIELD FOR OVERHEATING !!

// **************************************************************************************


int st=1; //step size: full 1, half step 2 CHANGE POWERSTALL!

int s=200; // Steps/rev (200 full,400 half step)


int ct=0;

double t0,t;

float tt,tstart;

float theta=0;

const float l=8; // mm/rev


void loop() {

//Press Switch to start motion

while(digitalRead(10)){} //Switch is pull up, goes to LOW when pressed

delay(1000); //delay to let you move your finger

//Send drive home

goHome();

theta=0;

//Move to location

tstart=micros();

step_duration=3000;

power=80;

moveTo(5,1); //(Dist,Dir)


power=30;

st=2;

goHome();


moveTo(30,1);

shutdown_all();

while(true);

}


void moveTo(float dist,int dir){ // (Distance in mm, Direction)

//Send drive to a desired location

if(st==1)s=200;

if(st==2)s=400;

int n = round(dist*s/l);

if(dir==1&st==1){//positive direction, full step

for (ct=1;ct<=round(n/4);ct++){

coilPolarities(S,OFF);

coilPolarities(OFF,S);

coilPolarities(N,OFF);

coilPolarities(OFF,N);

theta+=4*360/s;

Serial.println(String(micros()-tstart)+"\t"+String(theta)); //print dist/time

}

}

if(dir==2&st==1){

for (ct=1;ct<=round(n/4);ct++){

coilPolarities(N,OFF);

coilPolarities(OFF,S);

coilPolarities(S,OFF);

coilPolarities(OFF,N);

theta-=4*360/s;

Serial.println(String(micros()-tstart)+"\t"+String(theta)); //print dist/time

}

}

if(dir==1&st==2){

for (ct=1;ct<=round(n/8);ct++){

coilPolarities(S,OFF);

coilPolarities(S,S);

coilPolarities(OFF,S);

coilPolarities(N,S);

coilPolarities(N,OFF);

coilPolarities(N,N);

coilPolarities(OFF,N);

coilPolarities(S,N);

theta+=8*360/s;

Serial.println(String(micros()-tstart)+"\t"+String(theta)); //print dist/time

}

}

if(dir==2&st==2){

for (ct=1;ct<=round(n/8);ct++){

coilPolarities(S,OFF);

coilPolarities(S,N);

coilPolarities(OFF,N);

coilPolarities(N,N);

coilPolarities(N,OFF);

coilPolarities(N,S);

coilPolarities(OFF,S);

coilPolarities(S,S);

theta-=8*360/s;

Serial.println(String(micros()-tstart)+"\t"+String(theta)); //print dist/time

}

}


}

void goHome(){ //send drive to home, 1 for full step, 2 for half step

switch(st){

case 1: // Full step

while(digitalRead(10)){

coilPolarities(N,OFF);

coilPolarities(OFF,S);

coilPolarities(S,OFF);

coilPolarities(OFF,N);

theta-=4*360/s;

Serial.println(String(micros()-tstart)+"\t"+String(theta)); //print dist/time

}break;

case 2: //Half step

while(digitalRead(10)){

coilPolarities(S,OFF);

coilPolarities(S,N);

coilPolarities(OFF,N);

coilPolarities(N,N);

coilPolarities(N,OFF);

coilPolarities(N,S);

coilPolarities(OFF,S);

coilPolarities(S,S);

theta-=4*360/s;

Serial.println(String(micros()-tstart)+"\t"+String(theta)); //print dist/time

}break;

}

}

void coilPolarities(int polarityA, int polarityB){

int powerA=power, powerB=power;


switch(polarityA){

case N:

digitalWrite(12, HIGH);

break;

case S:

digitalWrite(12,LOW);

break;

default:

digitalWrite(12,LOW);

powerA=0;

}

switch(polarityB){

case N:

digitalWrite(13, HIGH);

break;

case S:

digitalWrite(13,LOW);

break;

default:

digitalWrite(13,LOW);

powerB=0;

}

setCurrents(powerA, powerB);

}


void setCurrents(int powerA, int powerB){

if(st==1)power_stall=50;

if(st==2)power_stall=7;

long int t0=0, PWM_cycle_duration=50;

long int PWM_high_A=0, PWM_high_B=0, dutyA=0, dutyB=0;

int first_pin=0, second_pin=0;

long int first_time=0, second_time=0;

float iA=0, iB=0;

if (powerA>100) powerA=100;

if (powerA<0) powerA=0;

if (powerB>100) powerB=100;

if (powerB<0) powerB=0;


dutyA=power_stall+int(powerA*(100-power_stall)/100);

dutyB=power_stall+int(powerB*(100-power_stall)/100);

PWM_high_A=int(PWM_cycle_duration*dutyA/100);

PWM_high_B=int(PWM_cycle_duration*dutyB/100);


if (PWM_high_A>=PWM_high_B){

first_pin=11;

second_pin=3;

first_time=PWM_high_B;

second_time=PWM_high_A;

}

else{

first_pin=3;

second_pin=11;

first_time=PWM_high_A;

second_time=PWM_high_B;

}


t0=micros();

while(micros()-t0<step_duration){

digitalWrite(first_pin, HIGH);

digitalWrite(second_pin, HIGH);

delayMicroseconds(first_time);

//

// iA= float(analogRead(0)) /1023.0/ 1.65; // Print Current in coils

// iB= float(analogRead(1)) /1023.0 / 1.65;

// Serial.print(String(micros()-tstart)+" ");

// Serial.print(iA);

// Serial.print(" ");

// Serial.println(iB);

digitalWrite(first_pin, LOW);

delayMicroseconds(second_time-first_time);

// iA= float(analogRead(0)) /1023.0/ 1.65; // Print Current in coils

// iB= float(analogRead(1)) /1023.0 / 1.65;

// Serial.print(String(micros()-tstart)+" ");

// Serial.print(iA);

// Serial.print(" ");

// Serial.println(iB);

digitalWrite(second_pin, LOW);

}

}


void shutdown_all(){

digitalWrite(12,LOW);

digitalWrite(3,LOW);

digitalWrite(9,LOW);


digitalWrite(13,LOW);

digitalWrite(11,LOW);

digitalWrite(8,LOW);

}


23 views0 comments

Recent Posts

See All
bottom of page