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.
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.
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.
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
/*
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;
}
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 )
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
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.
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.
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
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.