Controlling a stepper motor's position with a rangefinder

Hi All-

I would like to use an ultrasonic range finder to dictate the position of a stepper motor. In essence, if an object is 1' away from the sensor and moves to 2', the motor advances 1 revolution (or 1/12th of a revolution / inch). If the object returns to 1', the motor reverses 1 revolution.

The motion should be smooth, whether through hysteresis, and or by using rate of change of distance sensor reading to determine motor velocity.

I will be using a NEMA 17 stepper and a linear ball screw assembly such as this: http://www.seeedstudio.com/depot/B04F-5V-DC-18-Stepper-Motor-p-1901.html?cPath=39_40 but bigger obvi. This will allow for the use of limit switches which I foresee being necessary without a way to keep track of motor position other than steps. Maybe I need a rotary encode for this project, I hope not.

Right now for bench testing this idea, i am using a 28BYJ-48 stepper, connected to my Arduino via a ULN2003 driver board. My Ping sensor is from radioshack.

I have gotten the stepper to work well, after messing with the pin assignments for a minute. I've determined that the rangefinder works too.

What is a good way to approach this? At the most basic level, the stepper motor control part of the code needs to be able to use realtime data from the sensor, the delay interval, to determine the amount of steps to take. With my sample code, this would be done through passing that variable into the delay function here:
myStepper.step(stepsPerRevolution); delay(500);
which does not seem ideal.

The speed of the motor:
myStepper.setSpeed(100);
would be determined by the rate of change of the delay interval, right?

It would seem to me, a code n00b, that this will be a significantly different program from the one i used to test the stepper. Should I try and adapt the test code below, or start with a clean slate? If I am to start anew, I really have no idea how to begin.

Any advice or direction is much appreciated.

all the best,

-Rev

test code for stepper:

/* 
 Stepper Motor Control - one revolution
 
 This program drives a unipolar or bipolar stepper motor. 
 The motor is attached to digital pins 8 - 11 of the Arduino.
 
 The motor should revolve one revolution in one direction, then
 one revolution in the other direction.  
 
  
 Created 11 Mar. 2007
 Modified 30 Nov. 2009
 by Tom Igoe
 
 */

#include <Stepper.h>

const int stepsPerRevolution = 200;  // change this to fit the number of steps per revolution
                                     // for your motor


 //Stepper myStepper(stepsPerRevolution,8,10,9,11); //good
 //Stepper myStepper(stepsPerRevolution,8,10,11,9); //good
 // Stepper myStepper(stepsPerRevolution,9,11,8,10);// good
 // Stepper myStepper(stepsPerRevolution,9,11,10,8);//good
 // Stepper myStepper(stepsPerRevolution,10,8,9,11); //good
 //Stepper myStepper(stepsPerRevolution,11,9,8,10); //good?
Stepper myStepper(stepsPerRevolution,11,9,10,8); //good
          

void setup() {
  // set the speed at 60 rpm:
  myStepper.setSpeed(100);
  // initialize the serial port:
  Serial.begin(9600);
}

void loop() {
  // step one revolution  in one direction:
   Serial.println("clockwise");
  myStepper.step(stepsPerRevolution);
  delay(500);
  
   // step one revolution in the other direction:
  Serial.println("counterclockwise");
  myStepper.step(-stepsPerRevolution);
  delay(500); 
}

I've gotten the Ping sensor to work with numerous sketches, such as the following:

/* Ping))) Sensor
  
   This sketch reads a PING))) ultrasonic rangefinder and returns the
   distance to the closest object in range. To do this, it sends a pulse
   to the sensor to initiate a reading, then listens for a pulse 
   to return.  The length of the returning pulse is proportional to 
   the distance of the object from the sensor.
     
   The circuit:
 * +V connection of the PING))) attached to +5V
 * GND connection of the PING))) attached to ground
 * SIG connection of the PING))) attached to digital pin 7

   http://www.arduino.cc/en/Tutorial/Ping
   
   created 3 Nov 2008
   by David A. Mellis
   modified 30 Aug 2011
   by Tom Igoe
 
   This example code is in the public domain.

 */

// this constant won't change.  It's the pin number
// of the sensor's output:
const int pingPin = 7;

void setup() {
  // initialize serial communication:
  Serial.begin(9600);
}

void loop()
{
  // establish variables for duration of the ping, 
  // and the distance result in inches and centimeters:
  long duration, inches, cm;

  // The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
  // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
  pinMode(pingPin, OUTPUT);
  digitalWrite(pingPin, LOW);
  delayMicroseconds(2);
  digitalWrite(pingPin, HIGH);
  delayMicroseconds(5);
  digitalWrite(pingPin, LOW);

  // The same pin is used to read the signal from the PING))): a HIGH
  // pulse whose duration is the time (in microseconds) from the sending
  // of the ping to the reception of its echo off of an object.
  pinMode(pingPin, INPUT);
  duration = pulseIn(pingPin, HIGH);

  // convert the time into a distance
  inches = microsecondsToInches(duration);
  cm = microsecondsToCentimeters(duration);
  
  Serial.print(inches);
  Serial.print("in, ");
  Serial.print(cm);
  Serial.print("cm");
  Serial.println();
  
  delay(100);
}

long microsecondsToInches(long microseconds)
{
  // According to Parallax's datasheet for the PING))), there are
  // 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
  // second).  This gives the distance travelled by the ping, outbound
  // and return, so we divide by 2 to get the distance of the obstacle.
  // See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
  return microseconds / 74 / 2;
}

long microsecondsToCentimeters(long microseconds)
{
  // The speed of sound is 340 m/s or 29 microseconds per centimeter.
  // The ping travels out and back, so to find the distance of the
  // object we take half of the distance travelled.
  return microseconds / 29 / 2;
}

For an application like that you should not use the delay() function as the Arduino can do nothing during the delay().

Post a link to the datasheet for the stepper motor you propose to use?
What stepper motor driver do you intend to use?
What stepper motor power supply will you use (volts and amps)?

You may find some useful background info in Stepper Motor Basics

One way to approach the problem is to calculate the "error" between your actual position and desired position. If the error is not zero move the motor one step in the appropriate direction and measure the distance again.

You might want to adjust the speed (i.e. the interval between steps) to slow the motor as the error gets near 0.

This all assumes there is time for a PING between steps. If not you may need to move several steps at a time until you get close to zero.

...R

So you want to use the ping/ultrasonic sensor to confirm that you are the the position you think you are at?

Unless you place a heavy mechanical load on the stepper (or the ballscrew jams because of dirt) the stepper is accurate. Nearly every 3D printer runs this way, and the steppers do not loose position.

On the other hand if you want to know the position at power up (where the stepper code does not know where it is) then your ping sensor seems more usefull. (Have you thought of the accuracy?)

Your program structure is (after all initialistions in the setup - like setting CurrentPosition to PingValue)

void loop() {
  // here go the 4 lines of code that actually do the ping measurment.
  Pingvalue = mmdistance*stepspermm ;
  axisStepper.step(Pingvalue-CurrentPosition) ;
  CurrentPosition = Pingvalue ;
  delay(500) ; // Wait and do nothing for 1/2 second until next adjustment

The delay is strictly speaking not needed, but if the pingvalues varies slightly the stepper motor will jiggle a little forward/backwards all the time.

Above is just principle, have fun with the details :slight_smile: !

Robin2:
For an application like that you should not use the delay() function as the Arduino can do nothing during the delay().

Post a link to the datasheet for the stepper motor you propose to use?
What stepper motor driver do you intend to use?
What stepper motor power supply will you use (volts and amps)?

You may find some useful background info in Stepper Motor Basics

One way to approach the problem is to calculate the "error" between your actual position and desired position. If the error is not zero move the motor one step in the appropriate direction and measure the distance again.

You might want to adjust the speed (i.e. the interval between steps) to slow the motor as the error gets near 0.

This all assumes there is time for a PING between steps. If not you may need to move several steps at a time until you get close to zero.

...R

Thank you. What would that code look like?

I detailed the motor and driver specs in my first post. Its a cheepo motor and I cannot find a proper data sheet. But I have gotten it working well, and I don't think that is the hurdle for me.

Best,

-R

Msquare:
So you want to use the ping/ultrasonic sensor to confirm that you are the the position you think you are at?

Unless you place a heavy mechanical load on the stepper (or the ballscrew jams because of dirt) the stepper is accurate. Nearly every 3D printer runs this way, and the steppers do not loose position.

On the other hand if you want to know the position at power up (where the stepper code does not know where it is) then your ping sensor seems more usefull. (Have you thought of the accuracy?)

Your program structure is (after all initialistions in the setup - like setting CurrentPosition to PingValue)

void loop() {

// here go the 4 lines of code that actually do the ping measurment.
  Pingvalue = mmdistance*stepspermm ;
  axisStepper.step(Pingvalue-CurrentPosition) ;
  CurrentPosition = Pingvalue ;
  delay(500) ; // Wait and do nothing for 1/2 second until next adjustment


The delay is strictly speaking not needed, but if the pingvalues varies slightly the stepper motor will jiggle a little forward/backwards all the time.

Above is just principle, have fun with the details :) !

Hi @Msquare

I think I did't word the first sentence clearly. I want the sensor to dictate the position of the motor. I know that I can reasonably keep track of the stepper position using steps. I myself own and built a rostock max v2 :).

revel:
I want the sensor to dictate the position of the motor. I know that I can reasonably keep track of the stepper position using steps.

I've been reading back through the post trying to figure out how the Ping will be attached.

a way to keep track of motor position other than steps

So you don't want to count steps? You just want the Ping to determine when to motor should stop?

BTW, It sounds like you have a real "Ping" made by Parallax. This is good since, in my experience, Ping ultrasound sensors are much more reliable than the cheap ones from China. But even with a real Ping, your resolution isn't going to be much better than 1cm.

I thought it would be that your motrized thingimajig was going to track some other thingimajig :slight_smile:

The advice I gave will still work in that setup, except for the initial setup. Here your motor needs to slowly go (say) 10 steps, then check the end sensor, then go another 10 steps until the end stop changes. (just like your delta does) Then go 1 step and check the sensor until it pops back. Now you have a good zero point, and CurrentPosition is set to 0.

I see some other interpretetaion of your requirement:

So you don't want to count steps? You just want the Ping to determine when to motor should stop?

In that case the code is along the lines of

void loop() {
Some other system (serial?) inputs a value the "target" destination.
Convert to appropiate pingvalue (microsecond-pulse). 
if ( newpositio is "above" old position ) {
  while measurevalue is < targetvalue) {
    move motor clockwise a few steps
    If (endstop is triggered ) break ;// to exit while loop.
    measure new ping value
  } else {
  while measurevalue is > targetvalue) {
    move motor counterclockwise a few steps
    If (endstop is triggered ) break ;// to exit while loop.
    measure new ping value
  }
}

As Robin2 noted, you can test if measurevalue is much above targetvalue and do more&faster steps, than if they are close whern you use fewer steps and slower speed.

Thanks guys. Yes exactly @Msquare essentially I want my motorized thingamajig to mirror another thing that the ping is tracking.

revel:
I detailed the motor and driver specs in my first post.

I can't see it.

All I can see is this

such as this: http://www.seeedstudio.com/depot/B04F-5V-DC-18-Stepper-Motor-p-1901.html?cPath=39_40 but bigger obvi

which clearly says to me that this link is not the actual motor.

...R

I found and implemented the sketch below and was wondering if it could be adapted to use a rangefinder rather than a potentiometer... what do you think?
@Msquare would this work in conjunction with the code you posted, which is essentially a 'homing procedure'?

In my little n00b brain, this code makes sense to me, it is looking at change in input values, and passing that that to the stepper motor. This, combined with a way to establish a reference position upon power up would do fine for now, I think.

If so, a few questions come to mind:
-is there a way to make the stepper motor 'rest' when there has been no change in the input values for a period of time?
-will this come work with digital input?

Thank you,

-R

/*
 * MotorKnob
 *
 * A stepper motor follows the turns of a potentiometer
 * (or other sensor) on analog input 0.
 *
 * http://www.arduino.cc/en/Reference/Stepper
 * This example code is in the public domain.
 */

#include <Stepper.h>

// change this to the number of steps on your motor
#define STEPS 768

// create an instance of the stepper class, specifying
// the number of steps of the motor and the pins it's
// attached to
Stepper stepper(STEPS, 11, 9, 10, 8);

// the previous reading from the analog input
int previous = 0;

void setup() {
  // set the speed of the motor to 30 RPMs
  stepper.setSpeed(20);
}

void loop() {
  // get the sensor value
  int val = analogRead(2);

  // move a number of steps equal to the change in the
  // sensor reading
  stepper.step(val - previous);

  // remember the previous value of the sensor
  previous = val;
}

I combined the MotorKnob code above with the basic Ping code and got something seemingly workable. I just need to tweak variables to get more steps per inch as it were. Am I close with this?

Thank you,

-Revel

/*-----( Import needed libraries )-----*/
#include <Stepper.h>
#define STEPS 768
/*-----( Declare Constants and Pin Numbers )-----*/
//ping sensor pin
const int pingPin = 13;

// create an instance of the stepper class, specifying
// the number of steps of the motor and the pins it's
// attached to

/*-----( Declare objects )-----*/
Stepper stepper(STEPS, 11, 9, 10, 8);
/*-----( Declare Variables )-----*/
long duration, inches;//variable for ping duration
int previous = 0;// the previous reading from the analog input

void setup()   /****** SETUP: RUNS ONCE ******/
{
  
  Serial.begin(9600);// initialize serial communication with ping:
  stepper.setSpeed(30);     // set the speed of the motor to 30 RPMs

}

void loop()  
{

  // The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
  // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
  pinMode(pingPin, OUTPUT);
  digitalWrite(pingPin, LOW);
  delayMicroseconds(2);
  digitalWrite(pingPin, HIGH);
  delayMicroseconds(5);
  digitalWrite(pingPin, LOW);
  
  
  // The same pin is used to read the signal from the PING))): a HIGH
  // pulse whose duration is the time (in microseconds) from the sending
  // of the ping to the reception of its echo off of an object.
  pinMode(pingPin, INPUT);
  duration = pulseIn(pingPin, HIGH);
  
    // convert the time into a distance
    inches = microsecondsToInches(duration);
    
  Serial.print(inches);
  Serial.print("in, ");
  Serial.println();
  
  delay(100);

  // move a number of steps equal to the change in the
  // sensor reading
  stepper.step(inches - previous);

  // remember the previous value of the sensor
  previous = inches;
  

}//--(end main loop )---

/*-----( Declare User-written Functions )-----*/
long microsecondsToInches(long microseconds)
{
  //return microseconds / 74 / 2;
  return microseconds / 30 / 2;
}

revel:
-is there a way to make the stepper motor 'rest' when there has been no change in the input values for a period of time?

That depends on your hardware/driver for the stepper motor. You need to send some signal (another pin) which lowers or disables the current. On some chips there is an enable - this is usally held active permanently, but by woring it to a pin and in effect sending "not enable" you save power.

However, this means the stepper is "free to rotate", and when power is applied again it may not neccessarily come to exact same step. (varies with step mode, stepper and phase of the moon). Other people put the enable signal on a PWM pin and enable/disable it with a PWM thus reducing the power, effectvly, to half or some fraction. YourMilageMayVary, depending on your drivers.

If you want to go that way, have subsidary question onthis subject matter in the motors section. Modern chip stepper drivers do an automatic lowering of power when standing still. Read Datasheets.

revel:
I combined the MotorKnob code above with the basic Ping code and got something seemingly workable. I just need to tweak variables to get more steps per inch as it were. Am I close with this?

  pinMode(pingPin, INPUT);

duration = pulseIn(pingPin, HIGH);
 
   // convert the time into a distance
   inches = microsecondsToInches(duration);
...
   stepper.step(inches - previous);

Skip the "to inches" conversion. You are really only interested in steps. So combine your stepper pitch (how many steps it takes to move an inch) together with how many microseconds for sound to travel an inch, and the "per inch" should cancel, so you get "microseconds per step", ie that 10 micrsecond more sound delay requires 34 steps (or whatever the numbers turn out to be)

Alternativly just multiply the "inches -pervious" with your step per inch, StepInch*(inches-previous) but too many intermediate conversions will loose precision (beware of long interger math instead of float - it is faster but if not done right, looses information. I usually prefer long though.)

Yes, the 'to inches' conversion is indeed superfluous for this application, thank you.

Now that I have the motor moving in response to the sensor, do you have any thoughts as to how to introduce some hysteresis into the programming?

I am going to limit the working range of the sensor, which should be fairly straight forward, and help to rule out anomalous readings. Then I would like to 'smooth out' the motion of the stepper a bit. My first thought would be to take the previous 10 (or more) pings and average them. This way, if there is an anomalous measurement it will affect the overall movement less. I would like to do this without making it more jerky though, i.e. not make the motor movement interval longer.

I can tell that this is not the ideal way to do things, for at least two reasons. The way I have coded this, if there is an anomalous reading, the motor will advance all the way to that defined point before reversing, rather that just jumping a little, which I wouldn't mind.

Also, there must be some way to ignore anomalous readings in the code before it even gets to the motor...

Eventually I will have a stepper motor capable of a much higher velocity, but even so, I would like to know how to 'shape' the data streaming in from the ping sensor to make the motor more or less responsive.

Any ideas?

Thanks,

-R

There's an example of using an ring buffer to average analog input in this post.

@DuaneDegn looks very interesting, thank you.

revel:
Now that I have the motor moving in response to the sensor, do you have any thoughts as to how to introduce some hysteresis into the programming?

If you look at the reply #6, the first test - is the difference positive/negativ? - just change that to: is the differnece more than 10 steps "above"?. Then instead of the else, change that to: is the difference more than 10 step "below"?.

That way the stepper is not going to move if the reading is within +/- 10 steps. And when it is it, for example 15 above, it will move all 15 steps. This is not strictly speaking hysterisis, it is a "deadband" around setpoint, which probably will do your job well enough.

revel:
I can tell that this is not the ideal way to do things, for at least two reasons. The way I have coded this, if there is an anomalous reading, the motor will advance all the way to that defined point before reversing, rather that just jumping a little, which I wouldn't mind.

The code you showed was a implementation of what I outlined in reply#2 (my first reply). What you are asking now is what I outlined in reply#6 (my 3rd).

Note that that outline moves in small increments approaching the destination. BUT it checks the destination on each pass, ie if you got a single way out reading it would take a small move towards that, but if the next reading is back to where it should be, then it will move back again, not having moved a lot.

In theory if you have noisy signal that algorithm should move the stepper to the average value over time. It will "jitter" doing so (if the signal is noisy) in the increment you set.

DuaneDegn mentioned averaging the input, which is another way of solving the noise problem.

The #6 code also easily is extended to do the fast/slow. Just before doing the steps (in each of the up/down sections) test of the if the difference is large then do faster steps else do slower steps.

thank you for breaking this down, man. very helpful. :slight_smile: