Problem with timing and stepper motors blocking sensor readings

Hi all,
this is my first time posting, but I've searched the forum extensively and can't fix my problem. I have already examined Robin2's "Demonstration code for several things at the same time" and still cannot apply it to my code.

  • I am running 3 stepper motors using ST-M5045 drivers and an external 12V power supply.

  • I am also running 2 loadcells, one for measuring downward force and one for measuring tension, that take separate readings. They are using hx711 loadcell amplifiers.

  • There is a switch and a button. The switch changes between a tension check and the motion of the stepper motors. The button begins the motion, and has no function when the switch is LOW.

I have been able to get all of the parts to function as intended independently, but when I run the stepper motors, it blocks the sensor data from being printed in the serial monitor. It is crucial to my project that I am able to measure downward force off one of the loadcells while the stepper motors are still moving.

My main questions are:

  1. How can I make the stepper motors run without blocking the sensor data? or do I need to use 2 arduinos?

  2. Is the data still being read, and just the print function is being blocked? If my end goal is to export the sensor readings to excel automatically, will that be blocked by the stepper motion as well?

I cannot fit my code in this post because it is too long. I will put (a shortened version of) it in the first reply. I appreciate any feedback in advance. Thanks.

Here is the code (i left out pin definitions and some unimportant code that shouldn't affect timing):

void setup(){
//+++++++++++++++++++LOADCELLS SETUP++++++++++++++++++++++++++++  
  Serial.begin(9600);  
  scale1.begin(LOADCELL_DOUT_PIN1, LOADCELL_SCK_PIN1);
  scale2.begin(LOADCELL_DOUT_PIN2, LOADCELL_SCK_PIN2);
  scale1.set_scale(calibration_factor1); 
  scale1.tare(); 
  scale2.set_scale(calibration_factor2); 
  scale2.tare(); 
  Serial.println("Readings:");
//+++++++++++++++++++LOADCELLS SETUP++++++++++++++++++++++++++++ 

//=============================STEPPER SETUP======================  
  pinMode(horizontal_dir, OUTPUT);
  pinMode(horizontal_step, OUTPUT);
  pinMode(vertical_dir, OUTPUT);
  pinMode(vertical_step, OUTPUT);
  pinMode(zdir, OUTPUT);
  pinMode(zstep, OUTPUT);
  pinMode(buttonPin, INPUT);
  pinMode(switchPin, INPUT);
  
  Serial.begin(9600);  
//=============================STEPPER SETUP======================    
  }
 
void loop(){
digitalWrite(horizontal_dir, HIGH); 
digitalWrite(vertical_dir,HIGH);
digitalWrite(zdir,HIGH);
switchState = digitalRead(switchPin); //get current state of button and switch
newButtonState = digitalRead(buttonPin);

//++++++++++++++++++++++++++LOADCELL++++++++++++++++++++++
scale1.set_scale(calibration_factor1);
scale2.set_scale(calibration_factor2);
scale1.read();
scale2.read();

 hx = scale1.get_units(), 1;
  Serial.print("Force: ");
  Serial.print(scale2.get_units(), 2);
  Serial.print('\t');
  Serial.print("Tension: ");
  Serial.print(scale1.get_units(), 2); //scale.get_units() returns a float
  Serial.print(" g"); 
  Serial.println();
+++++++++++++++++++++++LOADCELL++++++++++++++++++++++++++

//------------------------------------------------------MOTION------------------------------------------------------------------------------

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~if the switch is HIGH~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 if (switchState == HIGH )
 
  {
    digitalWrite(horizontal_dir, HIGH); //horizontal goes <-
    digitalWrite(vertical_dir, HIGH); //vertical goes V
  //~~~~~~~~~~~~~~~~~~~~~~if the switch is HIGH and the button is HIGH~~~~~~~~~~~~~~~~~~~~~      
     if (newButtonState == HIGH && oldButtonState == LOW) //check if button has gone high since it was last read 
 {
    if (x == 0) {
      // Toggle on 
  
//==============================BEGIN HORIZONTAL MOTION=======================================

            for (int i = 0; i < 3200; i++){
          digitalWrite(horizontal_step, HIGH);
          delayMicroseconds(mtrSpeed );
          digitalWrite(horizontal_step, LOW);
          delayMicroseconds(mtrSpeed);
          mtrSpeed = 100;
            }  
            for (int i = 0; i < 32000; i++){
          digitalWrite(horizontal_step, HIGH);
          delayMicroseconds(mtrSpeed );
          digitalWrite(horizontal_step, LOW);
          delayMicroseconds(mtrSpeed);
          mtrSpeed = 50;
            }
            for (int i = 0; i < 32000; i++){
          digitalWrite(horizontal_step, HIGH);
          delayMicroseconds(mtrSpeed );
          digitalWrite(horizontal_step, LOW);
          delayMicroseconds(mtrSpeed);
          mtrSpeed = 50;
            }
            for (int i = 0; i < 32000; i++){
          digitalWrite(horizontal_step, HIGH);
          delayMicroseconds(mtrSpeed );
          digitalWrite(horizontal_step, LOW);
          delayMicroseconds(mtrSpeed);
          mtrSpeed = 50;
            }
            for (int i = 0; i < 4800; i++){
          digitalWrite(horizontal_step, HIGH);
          delayMicroseconds(mtrSpeed );
          digitalWrite(horizontal_step, LOW);
          delayMicroseconds(mtrSpeed);
          mtrSpeed = 100;
            }
            
//=============================END HORIZONTAL MOTION=====================================

//////////////////////////////BEGIN VERTICAL MOTION/////////////////////////////////////
            for (int i = 0; i < 3200; i++){
          digitalWrite(vertical_step, HIGH);
          delayMicroseconds(mtrSpeed );
          digitalWrite(vertical_step, LOW);
          delayMicroseconds(mtrSpeed);
          mtrSpeed = 200;
            }  
             for (int i = 0; i < 8000; i++){
          digitalWrite(vertical_step, HIGH);
          delayMicroseconds(mtrSpeed );
          digitalWrite(vertical_step, LOW);
          delayMicroseconds(mtrSpeed);
          mtrSpeed = 70;
            } 
//////////////////////////////////END VERTICAL MOTION/////////////////////////////////////
  
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%CUTTING MOTION%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%       
   digitalWrite(horizontal_dir, LOW);
            for (int i = 0; i < 26000; i++){
          digitalWrite(vertical_step, HIGH);
          digitalWrite(horizontal_step, HIGH);
          delayMicroseconds(mtrSpeed );
          digitalWrite(vertical_step, LOW);
          digitalWrite(horizontal_step, LOW);
          delayMicroseconds(mtrSpeed);
          mtrSpeed = 50;
            }        
    digitalWrite(horizontal_dir, HIGH);
//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%CUTTING MOTION%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

//&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&END OF MOTION - BEGIN REVERSE&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&

    digitalWrite(horizontal_dir, LOW); //horizontal goes ->
    digitalWrite(vertical_dir, LOW); //vertical goes ^
    
//normally would repeat same motion in reverse but no room for code here

  } //end of switchstate HIGH
  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~if the switch is LOW~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
  else  //zstep pulls until tension reads 100g 
  {
    digitalWrite(zdir, LOW);
    if (hx <= 100) {
        for (int i = 0; i < 200; i++){
      digitalWrite(zstep, HIGH);
      delayMicroseconds(mtrSpeed);
      digitalWrite(zstep, LOW);
      delayMicroseconds(mtrSpeed);
      mtrSpeed = 400;
        }
    }
  }
//------------------------------------------------------END MOTION--------------------------------------------

}//end of loop

Delta_G:
Got external resistors? Which way? Pull-up or pull-down?

Yes there are resistors, they are pull down.

Thanks for the reply. I most understood this part:

Delta_G:
The concept of using millis isn't hard. You've got the loop function repeating over and over. Instead of holding it up while you take a step and wait, take your step and record the time. Then on each pass of loop you can check to see if it is time to take another step or not and if it is, take it and record the time.

So to make sure I'm understanding, when I begin a stepping interval, it is pausing the rest of the loop, which is why I cannot read data? Does that mean that I should not use this type of interval for stepping motion?

for (int i = 0; i < 3200; i++){
          digitalWrite(horizontal_step, HIGH);
          delayMicroseconds(mtrSpeed );
          digitalWrite(horizontal_step, LOW);
          delayMicroseconds(mtrSpeed);
          mtrSpeed = 100;
            }

Or is there a way to leave these intervals and apply Robin's method? That is why I had trouble using the millis approach.

Also, I have almost no knowledge of microcontroller timers. I understand that your analogy is trying to show how simple the computation is for me to know that I am meeting you at 2:20, but microcontrollers run on a timing system I don't fully understand. Is the timer reset at the start of the loop, or only when the code is uploaded?

Again, thank you for the helpful reply and bearing with my inexperience.

Hi,
Welcome to the forum.

Can you please post a copy of your circuit, in CAD or a picture of a hand drawn circuit in jpg, png?

You post your pin definitions please?

Thanks.. Tom... :slight_smile:

I agree with the analysis by @Delta_G in Reply #4 - study it carefully.

For non-blocking control of a stepper motor using millis() and micros() have a look at the second example in this Simple Stepper Code

If you need to do a set number of steps then have a variable to record the number of steps and increment it once per step. Then you can check (in loop() ) whether another step is due with code like this pseudo code which eliminates the need for a FOR loop

if (stepCount < maxSteps) {
   makeOneStep();
}


void makeOneStep() {
   if (millis() - lastStepMillis >= stepIntervalMillis) {
      lastStepMillis = millis();
      // code to do a step
      stepCount ++;
    }

...R
Stepper Motor Basics

Thank you all for the responses. Tom, I will put the pin definitions in the bottom of this post, but I can't show the wiring because of an NDA.

Delta, I have studied the code and think I understand how millis works. It's just comparing the current time minus an arbitrary variable to an interval, and once it reaches the value of that interval it completes an action and adds the time passed since last check (also equal to the interval) to the arbitrary variable.
So it's basically comparing time since last check to the amount of time you want to pass before an action happens.

I made a flow chart for my machine and listed the actions that need to happen:

  1. read the loadcells and print to serial monitor.
  2. check tension of scale1 and adjust zstep until it reaches a specified tension
  3. on button push, begin motion with horizontal and vertical steppers
  4. reverse and complete same motion in opposite direction to return to home position

What's confusing me now is that I want the first step to always be reading. So for the millis approach, only one step of the motor would be in the loop, but every time it comes through it would be told to take another step? So are the sensors being read once per loop as the motor steps once per loop? This is what I meant by timing confusion.

Here are the pin definitions:

#include "HX711.h"

#define LOADCELL_DOUT_PIN1  A4
#define LOADCELL_SCK_PIN1  A5
HX711 scale1;

#define LOADCELL_DOUT_PIN2  A2
#define LOADCELL_SCK_PIN2  A3
HX711 scale2;

#define horizontal_dir 8 //Direction pin
#define horizontal_step 9 //Stepper pin
#define vertical_dir 4
#define vertical_step 5
#define zdir 2 //base
#define zstep 3 //base
#define zeroPin A0

float calibration_factor1 = 65.7;
float calibration_factor2 = 78;

//========smoothing and zeroing=======
const int numReadings = 5;
float hxAvg;
long hxSum;
float hx;
float newHxAvg = 0;
float hxRead;
//========smoothing and zeroing=======

int mtrSpeed = 0;
const int buttonPin = 12;
const int switchPin = 13;
int oldButtonState = LOW;
int newButtonState = 0;
int switchState = 0;   
int x;

lorenzcity:
Here are the pin definitions:

Please post the complete code as one piece so we can compile it. The code in your Original Post is missing something.

but I can't show the wiring because of an NDA.

Does that mean you are hoping to get free help for a commercial project - that would not be fair.

...R

PS ... @Delta_G said it better.

Delta_G:
This is a hobbyist site. If this is for some commercial product then it is absolutely inappropriate for you to be abusing this community to get it done for free. If you need an engineer for your company then hire one. We aren’t here to put good engineers out of work.

What you are doing is like running a restaurant and then deciding to cut costs by going to the local soup kitchen to get your soup for free. It’s just despicable.

Will you share the profits with us? Or will you just use us to build it and then keep the money for yourself?

Despicable. You should be ashamed. If it’s top secret then hire someone you can put under an NDA. But it isn’t fair to ask us to do it for free without the needed information in that case.

I am a 20 year old programming student and my summer job is to create VERY early arduino prototypes of ideas that are not mine, purely for proof of concept. My boss will be paying engineers to actually create the product using literally 0 of the code I use, since in the end it won't even be in C.

Would you help a college student finish a project so they get a good grade? I'm not designing this so I can sell it. I just have a project I need help with.

Robin2:
Please post the complete code as one piece so we can compile it. The code in your Original Post is missing something.
Does that mean you are hoping to get free help for a commercial project - that would not be fair.

...R

PS ... @Delta_G said it better.

My full code is over the character limit. Here is a pastebin link to it: #include "HX711.h"#define LOADCELL_DOUT_PIN1 A4#define LOADCELL_SCK_PIN1 - Pastebin.com

Also I apologize, I wasn't trying to mislead anybody. It really is just a summer project to help me learn programming more than anything else. Nothing I design will be sold, and the idea already exists. I'm just trying to get a prototype built that we can show to people around the office, mostly just for interests sake. The commerical project will be entirely built by engineers who are paid, and who will probably never even see what I have made.

Everything I have said is actually the truth, I have no way of proving that. Again I'm truly sorry I did not mean to take advantage of anyone. The NDA exists around the concept, which yes I am using. And that same concept will be given to the engineers, but I doubt they will see what I have made.

I'm not sure what doesn't make sense about my story. But I guess the best way to prove it is for me to say I'm sorry if I mislead you or took advantage of your knowledge. If you don't want to help that is entirely up to you and fine.

I "outed myself" on purpose because I was never trying to be anything other than honest.

Delta_G:
What’s more the concept you’re working on has already been proven. Lots of people have done this sort of thing. So no, I don’t believe that this is a proof of concept.

What you need to do is admit to your boss that you’re totally clueless about how to program an Arduino. I think this thread has established that. You’re not qualified for the job he has you doing. You need your boss to hire you some help.

But we are NOT here to serve as a free engineering department for profit driven companies to use so they don’t have to pay for engineers. Too many of our fellow engineers are currently out of work.

My boss knows that I am not qualified. It is a summer job and he hired me because he is a friend of mine's dad and is giving me the opportunity to learn. That is why engineers will never see what I make, because what I make will probably be so bad/rough that it isn't of any use to them. Again, I'm really sorry. I wasn't trying to put engineers out of work.

Delta_G:
You say it’s for college credit then you say “my boss” which sounds like a job. Bullshit. You’re lying. You can’t even keep your own story straight. Is it a professor or a boss?

You’re not a college kid trying to get a grade. But even if you were, then this is STILL despicable cheating.

You’ve outed yourself. You’re going to have to hire someone now. Don’t bullshit me.

Re reading this, I realized you misunderstood what I meant. I was just using the analogy of a college student because I have seen many posts on this forum asking for help with school projects.

I did not mean to say that this was for a school project. Yes, this is for my summer job, which is basically just getting paid minimum wage to learn arduino.

Delta_G:
If that were true then there wouldn’t be an NDA. Maybe for the concept at large but certainly not for the wiring diagram that “nobody is going to use anyway”. Your story doesn’t make sense.

Sorry bud but your story just doesn’t add up.

You don’t need a boss and a top secret project to learn to code. You could work out something on your own and then there won’t be any NDA.

Honestly, looking back, you're right. I probably could have posted the wiring diagram or even taken a picture of the machine and not violated the NDA. I was just nervous because I don't really know a lot about NDA's in the first place. I'm just trying to say that I was never lying to you or trying to bullshit you.

If you want I can still post that, but I'm sure no one wants to help at this point. Lesson learned.

Delta_G:
Go back and read what you wrote. It was misleading. And it seemed intentional. Now the story changes again.

See that’s how I know for certain that you’re lying. The story keeps changing. That doesn’t happen when people are telling the truth.

You can just stop here. Changing the story again is not going to make you look like anything more than a bigger liar.

I didn't change my story, I was clarifying something you misunderstood.
I would apologize again, but I think I've apologized enough. If you don't want to help, don't. But don't call me a liar when I'm telling the truth.

lorenzcity:
My full code is over the character limit.

Then add your .ino file as an attachment. See How to use the Forum

When people tell some untruths it becomes very difficult to know what is true and what isn't.

I reckon it would be a good idea to write the proper version of your request in your next Reply - so we can have it all in one place along with your code.

And I confess to being very much of the same opinion as @Delta_G if there is a Non-Disclosure-Agreement associated with the project. If you are looking for advice for your benefit only then you should be able to tell us everything about your project including a full schematic.

...R

Thanks Robin. I understand where both you and DeltaG were coming from.

If you're still willing to help, it's a machine that measures downward force while holding a stretched medium at a certain tension. two steppers then move an object to push down on the medium, measuring the downward force, and then the motors would return the object to a home position.

What I need help with at this point is making the motors move without blocking any sensor reading code. I am in the middle of a program that uses the millis method also. I'll attach both inos, but the millis one is still a major WIP.

The big problem I'm running into is that when I try to use the stepper motors without the for loop or delays, i just get a buzzing sound with clicks on the stepper motor. The ino that produces this result is "millis_approach."

You will see in the code that I started to use a maxSteps variable to eliminate the need for a for loop, but I cannot make the stepper actually move.

Again, thanks for taking the time to reply. I attached pictures of what the wiring looks like and where it is contained, but you definitely won't be able to see the exact hookups. If you still want to help I can try to make a drawing or recreate it in fritzing.

millis_approach.ino (4.72 KB)

IMG_7510.jpg

IMG_7511.jpg

digitalWrite(zdir, LOW);
    if (hx <= 100) {
      if (ZstepCount < ZmaxSteps){
   oneZStep();      
        }   
    }

I have also tried this, but no luck. I can only make the steppers move when I use a for loop and delays.

ok so I somehow renamed the wrong file which led me to upload the wrong file. The ino I meant to upload is attached, and is the main project with lots of blocking. The millis approach file is still correct.

loadcell_motor_project.ino (9.96 KB)

lorenzcity:
The big problem I'm running into is that when I try to use the stepper motors without the for loop or delays, i just get a buzzing sound with clicks on the stepper motor. The ino that produces this result is "millis_approach."

Have you studied the code in the link I gave you in Reply #6

...R

I have studied both the codes you linked pretty extensively and also read the stepper motor basics posts.
I think that in my millis approach file I am doing exactly what your second example in the code does.

Upon further examination, I noticed that the stepper motor is not just clicking, but it is stepping very slowly. by changing ZmaxSteps, I was able to figure out that each click corresponds to a step, so if I set the max steps to only 10, I will hear 10 clicks before it stops and the rod it is turning will spin in tiny almost unnoticeable increments.

So I thought this had to do with timing since my step interval is 1 (millisecond), however when I changed millis() to micros(), nothing about the timing in my code changed at all.

I figured the issue might be with the readScales() function, since it might be only allowing a step of the motor as fast as it can read, however removing the function did not change anything.

I then decided to remove the all of the code in the loop that wasn't controlling the stepper. What's interesting is that this caused the 10 clicks to happen much faster. but changing ZstepInterval did not change the timing still, neither did changing millis() to micros().

Removing the if (hx <= 100) parameter around the steps had an interesting effect of making the clicks stop. Now my stepper motor hums but does nothing, and if I turn the max steps up to an amount I would visibly be able to see, I get a loud, short beeping sound. I am certain it is wired correctly because I can run it just fine with for loops.

I will attach the code that I am using to get these results in the state it was after trying all of this. What I am most confused about now is why changing micros() to millis() didn't affect my timing. I would think that micros() would speed things up 1000x, very noticeable. I also think ZstepInterval should have changed timing now too.

The program is attached. I will let you know if there's any progress

millis_approach.ino (4.82 KB)

Ok so with more tests, I've come to the bigger issue.

when the step code looked like this:

if (scale1.get_units(), 1 <= 100) {
if (ZstepCount < ZmaxSteps){
   oneZStep();      
        }  
  }

it clicked, and the number of clicks corresponded to the number of steps I told it to do.
Removing the scale read, however, like this:

if (ZstepCount < ZmaxSteps){
   oneZStep();      
        }

allowed me to move the motors smoothly. I just had to play around with the timing but I got it to work.

What I think is happening now is that the sensor readings are actually blocking the stepper motors from moving as fast as I need them to. So the stepper motor makes one step, gets a reading. One step, take a reading. etc.

keeping the loadcell reading requirement removed, I then added back the readScales() function (which constantly serial prints the loadcell data) and that brought me back to a slow clicking.

This got me curious if I could add more steps into the onestep function, so I created fivestep:

void fiveZStep() { 
   if (currentMillis - previousZstepMillis >= ZstepInterval) {
      previousZstepMillis += ZstepInterval;
      digitalWrite(zstep, HIGH);
      digitalWrite(zstep, LOW);
      ZstepCount ++;
      digitalWrite(zstep, HIGH);
      digitalWrite(zstep, LOW);
      ZstepCount ++;
      digitalWrite(zstep, HIGH);
      digitalWrite(zstep, LOW);
      ZstepCount ++;
      digitalWrite(zstep, HIGH);
      digitalWrite(zstep, LOW);
      ZstepCount ++;
      digitalWrite(zstep, HIGH);
      digitalWrite(zstep, LOW);
      ZstepCount ++;
    }

This turned the soft clicking of the stepper motor into loud knocks, but also forced the stepper motor to turn a little faster. This did nothing to move the stepper when I removed any loadcell reading code.

So now my problem is flipped. Taking sensor readings can only happen at a certain speed as far as I'm aware, and the unpleasant sound from using fivestep makes me think it isn't a good long term approach. Is there some way I can collect sensor readings constantly in the background of my program but still get smooth motion on steppers?