Weird result from a running code

Hi, I have built a running project that uses an ultrasonic sensor, a 28byj-48 stepper motor, and a servo, all connected and operating at the same time. The program starts with the stepper rotating at set speed, as an object gets closer to the ultrasonic sensor and breaks the threshold distance, the stepper stops rotating, and the servo rotates from position 0 to position 160, I also turn off the on-board led as an indicator that I have reached the threshold distance. So up to this point everything works just perfectly. When I remove the object that crossed the threshold, the servo goes back to it's home position, the led turns on, but the stepper rotates at 1/4 the original set speed. Then when I break the threshold again and again, the stepper speed increases a bit each time until it reaches it's top set speed. Why doesn't it just go back to it's top speed right away?

Thanks for any feed back.
I can post the code if required.

Cheers,
TT

my crystal ball is in the dishwasher ...

hint: post your code and description of the circuit

3 Likes

Without the code there is no possibility of providing help. Don't forget to use code tags when you post your code

#include <AccelStepper.h>
#define motorPin1 2
#define motorPin2 3
#define motorPin3 4
#define motorPin4 5
#define MotorInterfaceType 4
AccelStepper stepper = AccelStepper(MotorInterfaceType, motorPin1, motorPin3, motorPin2, motorPin4);
#include <Servo.h>;
Servo myservo;
int pos1 = 0;
int pos2 =160;
const int TRIG_PIN  = 9;     
const int ECHO_PIN  = 10;     
const int DISTANCE_THRESHOLD = 25.4; // centimeters
float duration_us, distance_cm;  

void setup() {
  myservo.attach(11);
  stepper.setMaxSpeed(1000);
  stepper.setSpeed(500);
  pinMode(TRIG_PIN, OUTPUT);   
  pinMode(ECHO_PIN, INPUT);    
  pinMode(LED_BUILTIN,OUTPUT); 
 }

void loop() {
  // generate 10-microsecond pulse to TRIG pin
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
 
  duration_us = pulseIn(ECHO_PIN, HIGH);
  distance_cm = 0.017 * duration_us;

  //print the value to Serial Monitor
  //Serial.print("distance: ");
  //Serial.print(distance_cm /2.54);
  //Serial.print(" in");
  
  if(distance_cm > DISTANCE_THRESHOLD){
    digitalWrite(LED_BUILTIN,HIGH); 
    stepper.runSpeed();
    myservo.write(pos1);
  }
    
  if(distance_cm < DISTANCE_THRESHOLD){
   digitalWrite(LED_BUILTIN,LOW); 
   stepper.stop();
   myservo.write (pos2);
  }
  
}

Do as the professional guys do! Install Serial.print telling key variables in the code and watch the debug output in serial monitor.

You have left out the "timeout" parameter, so with no immediate echo return, the function waits for the default 1 second.

Hi @tonytnt69,

you may try this:

#include <AccelStepper.h>
#define motorPin1 2
#define motorPin2 3
#define motorPin3 4
#define motorPin4 5
#define MotorInterfaceType 4
AccelStepper stepper = AccelStepper(MotorInterfaceType, motorPin1, motorPin3, motorPin2, motorPin4);
#include <Servo.h>;
Servo myservo;
int pos1 = 0;
int pos2 =160;
const int TRIG_PIN  = 9;     
const int ECHO_PIN  = 10;     
const int DISTANCE_THRESHOLD = 25.4; // centimeters
float duration_us, distance_cm;  

// Variable to signalize that the stepper was stopped 
boolean stepperStopped = false;

void setup() {
  myservo.attach(11);
  stepper.setMaxSpeed(1000);
  stepper.setSpeed(500);
  pinMode(TRIG_PIN, OUTPUT);   
  pinMode(ECHO_PIN, INPUT);    
  pinMode(LED_BUILTIN,OUTPUT); 
 }

void loop() {
  // generate 10-microsecond pulse to TRIG pin
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
 
  duration_us = pulseIn(ECHO_PIN, HIGH);
  distance_cm = 0.017 * duration_us;
  
  
  //print the value to Serial Monitor
  //Serial.print("distance: ");
  //Serial.print(distance_cm /2.54);
  //Serial.print(" in");
  
  if(distance_cm > DISTANCE_THRESHOLD){
    digitalWrite(LED_BUILTIN,HIGH); 
    //If the stepper was stopped before, set speed to 500
    //and stepperStopped to false;
    if (stepperStopped){
      stepper.setSpeed(500);
      stepperStopped = false;
    }
    stepper.runSpeed();
    myservo.write(pos1);
  }
    
  if(distance_cm < DISTANCE_THRESHOLD){
   digitalWrite(LED_BUILTIN,LOW); 
   stepper.stop();
   // If stepper stopped signalize this to set the speed again later
   stepperStopped = true;
   myservo.write (pos2);
  }
}

The main change:

  • A boolean variable is set true when the stepper was stopped.
  • If the stepper is started again, the function stepper.setSpeed(500); is called again once.

See also the comments in the sketch.

If you change the timeout in pulseIn() you may immediately encounter a general problem of your sketch:

see pulseIn() - Arduino Reference

Returns the length of the pulse in microseconds or gives up and returns 0 if no complete pulse was received within the timeout.

So if no pulse returns the computed distance is zero which is less than DISTANCE_THRESHOLD.

Thank you so much for that . I spent the last 3 hours trying to figure this out, without any success. Your solution works perfectly.

Thanks again,
Cheers,
TT

Hello again,

First I would like to say thanks again for the help, it is very much appreciated. Now that I have my program running properly up to this point, I need to incorporate one more process. I'll explain the complete process: It starts with an object approaching the ultrasonic sensor being moved along by a conveyor belt run by a stepper motor. When the object breaks the threshold distance, the conveyor stops, and a servo pushes the object to a new location, and then the servo returns back to it's home position. At this point I need the system to go into a hold function, so that another linear servo can push the object to a different location, and then return back to it's original position to wait for the next object. Once the linear servo returns to it's home position, I then need the process to start all over again,(IE: get the conveyor running, Etc..) and repeat until I shut things down. What would be the best way to code this partial program shutdown? Should I look into Interrupts, millis delay or something else. At the moment I don't know anything about interrupts or millis but I will try to educate myself. I just need to know what would be the best solution.

Thanks for any feedback.
Cheers,
TT

The main problem her is the stop() function. This doesn't make any sense and may influence the setSpeed. stepper.stop() is only used together with positioning and acceleration/deceleration using the run() function.

With stepper.runSpeed() it is sufficent to not call runSpeed() anymore if you want to stop the stepper. Simly delete the stepper.stop() call.

I think like in a lot of projects this looks like a task for a statemachine.

Here an example which I know quite well :wink:

https://forum.arduino.cc/t/help-with-code-if-possible/1201603/9?u=ec2021

Thanks to @MicroBahner's post I made an updated version of the sketch from post #7:

/*
  Forum: https://forum.arduino.cc/t/weird-result-from-a-running-code/1201365
  Wokwi: https://wokwi.com/projects/384626579483410433
*/

#include <AccelStepper.h>
#define motorPin1 2
#define motorPin2 3
#define motorPin3 4
#define motorPin4 5
#define MotorInterfaceType 4
AccelStepper stepper = AccelStepper(MotorInterfaceType, motorPin1, motorPin3, motorPin2, motorPin4);
#include <Servo.h>;
Servo myservo;
int pos1 = 0;
int pos2 =160;
const int TRIG_PIN  = 9;     
const int ECHO_PIN  = 10;     
const int DISTANCE_THRESHOLD = 25.4; // centimeters
float duration_us, distance_cm;  

// Variable to signalize that the stepper was stopped 
boolean stepperStopped = false;

void setup() {
  myservo.attach(11);
  stepper.setMaxSpeed(1000);
  stepper.setSpeed(500);
  pinMode(TRIG_PIN, OUTPUT);   
  pinMode(ECHO_PIN, INPUT);    
  pinMode(LED_BUILTIN,OUTPUT); 
 }

void loop() {
  // generate 10-microsecond pulse to TRIG pin
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
 
  duration_us = pulseIn(ECHO_PIN, HIGH);
  distance_cm = 0.017 * duration_us;
  
  if(distance_cm > DISTANCE_THRESHOLD){
    digitalWrite(LED_BUILTIN,HIGH); 
    stepperStopped = false;
    myservo.write(pos1);
  }
    
  if(distance_cm < DISTANCE_THRESHOLD){
   digitalWrite(LED_BUILTIN,LOW); 
   stepperStopped = true;
   myservo.write (pos2);
  }

  if (!stepperStopped){
      stepper.runSpeed();
  }
}

And this might be a start for planning a statemachine:

START
    Conveyor stop
    All servos move to home position
    Conveyor start
    change state to TRANSPORT

TRANSPORT   
    Conveyor moves
    Object distance measured
    if (object closer than threshold) 
       stop Conveyor
       change state to FIRSTPUSH
 	  
FIRSTPUSH
    Control ServoOne to push object to new location
    if (new location reached)
	    change state to SERVOONEHOME
		
SERVOONEHOME
    Control ServoOne to home position
	if (home position reached)
	    change state to SECONDPUSH
		
SECONDPUSH
    Control ServoTwo to push object to new location
	if (new location reached)
	    change state to SERVOTWOHOME
		
SERVOTWOHOME
    Control ServoTwo to home position
	if (home position reached)
	    change state to TRANSPORT
    		

Thanks for the response. This looks interesting. Time to hit the books.

Hope I can figure this out.

Cheers,
TT

I have coded a commented version for you:

/*
  Forum: https://forum.arduino.cc/t/weird-result-from-a-running-code/1201365
  Commented version
  Wokwi: https://wokwi.com/projects/384655323134971905

*/

// Stepper Motor connected to Arduino that drives a conveyor belt
#include <AccelStepper.h>
constexpr byte motorPin1 {2};
constexpr byte motorPin2 {3};
constexpr byte motorPin3 {4};
constexpr byte motorPin4 {5};
constexpr byte MotorInterfaceType {4};
AccelStepper stepper = AccelStepper(MotorInterfaceType, motorPin1, motorPin3, motorPin2, motorPin4);

// Variable to signalize that the stepper shall run (or not)
boolean stepperRun = false;

// Servos to move the object 
#include <Servo.h>;
// servoOne moves the object from the belt to a  location L1
Servo servoOne;
// servoTwo moves the object from th eL1 to a location L2
Servo servoTwo;
// The pins to control the servos
constexpr byte servoOnePin {11};
constexpr byte servoTwoPin {6};
// servoOne home and push position
constexpr byte servoOneHome {0};
constexpr byte servoOnePush {160};
// servoTwo home and push position
constexpr byte servoTwoHome { 0};
constexpr byte servoTwoPush {160};

// Pins for the Ultrasonic Distance Sensor
const int TRIG_PIN  = 9;
const int ECHO_PIN  = 10;

// "first" is used to identify if a change of state has been performed
// "first" is set in newState() and reset in isFirst()
// "startTime" holds the time in [ms] when a change of state takes place
// It is set in newState() and used in the function expired()
boolean first = true;
unsigned long startTime = 0;

// The enumeration machineStates list all states that the state machine can enter
// "state" is the global variable of type machineStates. It is initialized with "START"
// and changed in newState()
// The function stateMachine() switches accordingly to routines that apply for the appropriate state
enum machineStates {START, TRANSPORT, WAITFORFIRSTPUSH, FIRSTPUSH, SERVOONEHOME, SECONDPUSH, SERVOTWOHOME};
machineStates state = START;


void setup() {
  // Serial is configured for 115200 Bd
  Serial.begin(115200);
  // Both servos are attached to the appropriate pins
  servoOne.attach(servoOnePin);
  servoTwo.attach(servoTwoPin);
  // The stepper parameters are set
  stepper.setMaxSpeed(1000);
  stepper.setSpeed(500);
  // The Ultrasonic Distance Sensor pins are set as Output and Input
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  // The built-in led is used to signalize whether the conveyor is running or has been stopped
  pinMode(LED_BUILTIN, OUTPUT);
  // The servos are commanded to go to their home position 
  servoOne.write(servoOneHome);
  servoTwo.write(servoTwoHome);
  // Give the servos some time to get there .... (adjust to the appropriate delay ...)
  delay(1000); 
}

void loop() {
  // stateMachine controls the whole machine step by step
  stateMachine();
  // This function controls the conveyor belt in loop() and
  // makes sure that it moves if the belt shall run 
  handleConveyor();
}

// It does not do a lot but the function name explains better
// what it is good for than "stepper.runSpeed();"
void handleConveyor() {
  if (stepperRun) {
    stepper.runSpeed();
  }
}

// Everytime we change the state it might be helpful to
// set first and startTime also
// To avoid multiple lines with the same function
// setting of state, first and startTime is bundled 
// in one call
// Even if it would not be used all the time it is less verbose
// and more safe then coding it a several place multiple times.
// 
// In case we encounter further data that had to be set or reset
// with a state change, this would be the right place for it

void newState(machineStates aState) {
  state = aState;
  first = true;
  startTime = millis();
}

//The function isFirst() also makes live easier 
// It does set "first" to false after the first call 
// and avoids to code this several times in the different
// states
boolean isFirst() {
  if (first) {
    first = false;
    return true;
  }
  return false;
}

// Instead of writing the millis() function muliple times in the state machine
// we can use the expired() function to "delay" other functions in the state machine
// for a certain time without blocking the code
//
// This function is used to give some time for the servos to do their job in this sketch.
// If we had sensors that tell us when a servo has reached its home or push position
// we could use the sensor feedback to trigger the next state. That would be better as
// the machine would stop if e.g. a servo is out of order ...
boolean expired(unsigned long interval) {
  return (millis() - startTime >= interval);
}

// Here comes the stateMachine ...
// Depending on the content of "state" the switch/case statement runs through the
// appropriate functions
// The if(isFirst()) parts are only performed once every entry to a certain state
// This enables to print a message and start activities which do not need to be called
// continouesly 

void stateMachine() {
  switch (state) {
    case START:
      if (isFirst()) {
        Serial.println("START");
        // Move the servos to home position (just to be sure ...)
        servoOne.write(servoOneHome);
        servoTwo.write(servoTwoHome);
        // We can of course reset the startTime outside of newState()!
        // This will "delay" the change to the next state
        startTime = millis();
      }
      // Depending on the last time when startTime was set
      // newState will be called to change to TRANSPORT
      if (expired(500)) {
        newState(TRANSPORT);
      }
      break;
    case TRANSPORT:
      if (isFirst()) {
        Serial.println("TRANSPORT");
        // We start the belt now (once everytime we enter this state from a different state)
        startConveyor();
      }
      // The boolean function objectIsClose() checks the distance to objects
      // and returns true if an object is closer than a given threshold
      if (objectIsClose()) {
        // If an object has been detected within the threshold distance
        // We stop the belt
        stopConveyor();
        // And change to the next state
        newState(WAITFORFIRSTPUSH);
      };
      break;
    case WAITFORFIRSTPUSH:
      if (isFirst()) {
        // Just used to print the state name
        Serial.println("WAITFORFIRSTPUSH");
      }
      // We wait for 2s after changing from TRANSPORT to WAITFORFIRSTPUSH
      // just to give the belt (and object) some time to stop stable
      if (expired(2000)) {
        newState(FIRSTPUSH);
      }
      break;
    case FIRSTPUSH:
      if (isFirst()) {
        Serial.println("FIRSTPUSH");
        // Command servo one to the push position
        servoOne.write(servoOnePush);
      }
      // And wait for 3s after changing to FIRSTPUSH
      // before we go to the next state as we do not have a 
      // sensor telling us that the servo and object have reached the 
      // push position L1
      if (expired(3000)) {
        newState(SERVOONEHOME);
      }
      break;
    case SERVOONEHOME:
      if (isFirst()) {
        Serial.println("SERVOONEHOME");
        // Command servo one to home position
        servoOne.write(servoOneHome);
      }
      // And wait for 3s after changing to SERVOONEHOME
      // before we go to the next state as we do not have a 
      // sensor telling us that the servo has reached the 
      // home position again
      if (expired(3000)) {
        newState(SECONDPUSH);
      }
      break;
    case SECONDPUSH:
      if (isFirst()) {
        Serial.println("SECONDPUSH");
        // After servo one (should have reached home position)
        // we command servo two to push the object to location L2
        servoTwo.write(servoTwoPush);
      }
      // And wait for 3s after changing to SECONDPUSH
      // before we go to the next state as we do not have a 
      // sensor telling us that the servo and object have reached the 
      // push position L2
      if (expired(3000)) {
        newState(SERVOTWOHOME);
      }
      break;
    case SERVOTWOHOME:
      if (isFirst()) {
        Serial.println("SERVOTWOHOME");
        // Command servo two to home position
        servoTwo.write(servoTwoHome);
      }
      // And wait for 3s after changing to SERVOTWOHOME
      // As we do not have a sensor telling us that the
      // servo has reached the home position again
      if (expired(3000)) {
        // If that time has expired we check the object distance
        // If there is still an object within the threshold distance
        // we wait until the belt is free again. If it is free
        // we start the cycle all over again from START
        if (!objectIsClose()) {
          newState(START);
        }
      }
      break;
  }

}

// This function is called where we want to know if an object is closer than
// the DISTANCE_THRESHOLD. It returns false if outside and true if closer than
// this given distance
boolean objectIsClose() {
  const int DISTANCE_THRESHOLD = 25.4; // centimeters
  float duration_us, distance_cm;
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
  duration_us = pulseIn(ECHO_PIN, HIGH);
  distance_cm = 0.017 * duration_us;
  return (distance_cm < DISTANCE_THRESHOLD);
}

// startConveyor() just handles the built-in led and the boolean variable
// stepperRun
// We could write both lines in the stateMachine, but using an explicit function
// makes it easier to read (understand) the code and to implement future changes
// e.g. switching a separate external led etc.
void startConveyor() {
  digitalWrite(LED_BUILTIN, HIGH);
  stepperRun = true;
}

// The same applies to stopConveyor()
void stopConveyor() {
  digitalWrite(LED_BUILTIN, LOW);
  stepperRun = false;
}

It runs on Wokwi: https://wokwi.com/projects/384655323134971905

You'll have to adopt it to your needs still, but it might assist you to understand the concept (there are of course several possibilities to code applications, at least one more than developers involved :wink: )

Good luck and have fun!

Holly Mackerel, That is incredible. I think I would have died of old age before I could have created that code. You are a genius. Thanks you sooo much.

I tried the wokwi simulation. How do I trigger the Ultra sensor. When I hit the play button the only thing that happens is the stepper motor turns, but nothing else happens. Am I doing something wrong?
Thanks,
TT

No worries, I am far below the state of a genius :wink:

  • Start the Wokwi simulation
  • Click on the HC-SR04 board and this slider menu will open

image

  • Move the slider to change the distance

That feature is only available during the simulation!

Nice.

// Here comes the stateMachine ...

LOL! Indeed here it does come.

I wonder if the one place where you "manually" tinker with startTime

        // We can of course reset the startTime outside of newState()!
        // This will "delay" the change to the next state
        startTime = millis();

could be eliminated in favor of some kind of initialisation performed elsewhere. I don't think you'd have to add a once and only once startup state, just maybe initialise startTime to millis() at the end of setup(). Looking at it through the tiny window just now.

If expored() and nextState() were class methods, more gory detail could be encapsulated, but I like the way this code just does things. Right in front of your eyes.

a7

1 Like

Agree, it is tempting to write a class ...

I change startTime on purpose to show the TO a possibility to handle timing issues not only from start of a state but also after it is already running ...

When computers came along (I got my first personal computer, a CBM PET 2001, in 1979), I realised that they were "exponential time killers" compared to model railways, but took up much less space.

:wink:

Ok great

Thanks
TT

Hi again, Sorry to be a bother. I ran the sim and moved the slider. The stepper started up, and the sim progressed through its functions. It got as far as the "second servo home position", and then everything stopped. The stepper did not start up again.
Is this because it is only a simulation running, and needs an actual real life model for it to work properly?

Cheers,
PS: I still have a working Radio Shack Tandy TRS-80 computer I bought back in the 70's

After the second servo is back home, the sketch does not start the stepper if there is still an object in close distance... not a bug , it's a safety feature :wink:

In the simulation you have to move the distance slider so that it's outside the distance threshold again (in "real life" the object should have been moved by the servos.)

On Wokwi just move the slider to the left until the sequence starts and then move it to the right outside the threshold value. This way the sequence will cycle through all steps without stopping.

Just give it a try ...

Read the comments in the part stateMachine()! It's written there.