linearizing the output flow of a needle valve using a stepper motor

I posted this topic here before. At that time I didn't even know the right questions to ask. I hope I am now better prepared to receive your help.

I have a needle valve which has a non linear output as a function of stem rotation.
I want to connect the valve stem to the stepper motor and control the motor with an Arduino Uno, 10 turn (10K) potentiometer/A4988 I'm using a "27:1 geared stepper motor. The valve stem rotation corresponds to a linear output for the first 6 rotations. That is, if the 1st rotation induces a flow of x, then the next 5 rotations produce a flow of 2x,3x, 4x, 5x, 6x. The 7th rotation increases the flow by 2x and, the 8th rotation increases the flow by 3x.

I can linearize the valve output in time by modifying the speed of the motor. So the first 6 rotations will be at time t/rotation, the 7th is at 1/2t and the 8th is at 1/3t.
I can do this by changing the stepdelay map such that the 1st 6 rotations are at map(val, 0, 1023, 1, 500) then the 7th is at map(val, 0, 1023, 1, 1000) and the 8th at map(val, 0, 1023, 1, 1500). .

Ultimately, I want to have it go through this protocol then wait 20 sec and reset to the origin. (8 revolutions back). Im hoping to avoid using an encoder but that decision will be determined by performance.

Currently, the code I cobbled together allows me to set a speed via the potentiometer rotate the motor 6 rotations in one direction then reverse direction for 6 rotations. I have the analogread occurring every 3000 steps (about 5370 steps per rev) using a countdown function.(Thanks Cattledog)

Any attempt ive made at cut, paste, modify the existing code hasn't worked.
If you could help me figure out how to get the motor to do the 6 rotations at a speed set by the potentiometer followed by the 1 rotation at half speed I would greatly appreciate it. Hopefully I can figure out the last (1/3 speed) with that info.
Here is the code to date:

#include <Stepper.h>

int steppin = 4;
int dirpin = 3;
int stepdelay;
int Distance = 0;  //How far we've traveled

int AnalogReadCountDown = 3000;

int NumSteps = 5370.2479;  //how many steps does the stepper take to make a rotation  (the actual motor, not factoring in "micro" steps)

int FullRotation = NumSteps ;


void setup()
{
  pinMode (steppin, OUTPUT);
  pinMode (dirpin, OUTPUT);
  digitalWrite(4, LOW);
  digitalWrite(3, LOW);
  Serial.begin(9600);
  //int val = analogRead(A0);
  //stepdelay = map(val, 0,1023,1,500);
  

}

void loop ()
{
  if(AnalogReadCountDown == 0){
    int val= analogRead(A0);
    stepdelay = map(val, 0,1023,1,500);
    Serial.println(stepdelay);
    AnalogReadCountDown = 3000;}

  

  digitalWrite(steppin, HIGH);
  delayMicroseconds(stepdelay);          
  digitalWrite(steppin, LOW); 
  delayMicroseconds(stepdelay);
  Distance = Distance + 1;   // record this step
  AnalogReadCountDown = AnalogReadCountDown - 1;
  if (Distance == FullRotation * 6)  {  //as soon as distance = "FullRotation", that means we have made a full revolution!

      if (digitalRead(3) == LOW){
      digitalWrite(3, HIGH);  //this is a "toggle" to reverse whichever way its going to the other direction!
    }
    else{
      digitalWrite(3, LOW); 
    }

   Distance = 0;  //reset our Distance counter!
    }
}

Im obviously brand new to coding. If someone could give me a clue as to how to get the motor to do a 7th rotation at 1/2 the original speed set by the pot (corresponding to the stepdelay = map(val, 0, 1023, 1, 1000); I would be eternally grateful.

int NumSteps - is an integer.
The fractional part is ignored.

Is the pressure at the valve inlet fixed (regulated), or does it fall with increasing flow? Remember, delta flow varies (roughly) with the square root of delta pressure. If the pressure falls to 1/2, flow falls to 1/4 (roughly) with a fixed orifice.

Make a copy of that last if section, but change the 6 to a 7.

7*NumSteps will overflow a signed integer. Make Distance an unsigned int.

Delta_G- I tried your suggestion. Motor kept rotating indefinitely without changing direction. May have been a syntax error on my part or maybe overflow issue. Code is a lot more stringent than English so a narrative description of a code change can have several slightly different iterations which can have drastically different outcomes.

I used your Pro Tip-thanks!

I can live with the rounding error. lol

Cattledog- How exactly do I make distance an unsigned integer? How then to get that 7th rev at half speed?

JCA34F- Yes, it is a fairly well pressure regulated system. The spray reservoir volume is about a gallon pressurized to 45psi and the valve orifice is 0.25". Two valves will be operated simultaneously and inversely so the sum of the flow volume remains constant. The total cycle time is about 30 seconds.

I tried your suggestion. Motor kept rotating indefinitely without changing direction. May have been a syntax error on my part or maybe overflow issue.

If you don't post the code you tried, how can you expect us to find the error?

How exactly do I make distance an unsigned integer?

//int Distance = 0;  //How far we've traveled
unsigned int Distance = 0;//How far we've traveled

Delta_G- Good point.

Make a copy of that last if section, but change the 6 to a 7. That way the reset you've already figured out happens when it gets to 7 now.

Next, change the one that still happens at 6 to have a line to double the step delay. That way the last rotation happens at half speed. So something like just stepDelay *= 2

#include <Stepper.h>

int steppin = 4;
int dirpin = 3;
int stepDelay;
unsigned int Distance = 0;  //How far we've traveled
int AnalogReadCountDown = 3000;
int NumSteps = 5370;
int FullRotation = NumSteps ;

void setup()
{
  pinMode (steppin, OUTPUT);
  pinMode (dirpin, OUTPUT);
  digitalWrite(4, LOW);
  digitalWrite(3, LOW);
  Serial.begin(9600);
  //int val = analogRead(A0);
  //stepDelay = map(val, 0,1023,1,500);


}

void loop ()
{



  if (AnalogReadCountDown == 0) {
    int val = analogRead(A0);
    stepDelay = map(val, 0, 1023, 1, 500);
    Serial.println(stepDelay);
    AnalogReadCountDown = 3000;
  }


  digitalWrite(steppin, HIGH) ;
  delayMicroseconds(stepDelay);
  digitalWrite(steppin, LOW) ;
  delayMicroseconds(stepDelay);
  Distance = Distance + 1;   // record this step
  AnalogReadCountDown = AnalogReadCountDown - 1;
  if (Distance == FullRotation * 6)  {  //as soon as distance = "FullRotation", that means we have made a full revolution!

    (stepDelay = 2);

    if (Distance == FullRotation * 7)

      if (digitalRead(3) == LOW) {
        digitalWrite(3, HIGH);  //this is a "toggle" to reverse whichever way its going to the other direction!
      }
      else {
        digitalWrite(3, LOW);
      }

    Distance = 0;  //reset our Distance counter!
  }

}

This iteration keeps the motor rotating CW indefinitely at the initial speed.

This piece of code looks like it is all mixed up

  if (Distance == FullRotation * 6)  {  //as soon as distance = "FullRotation", that means we have made a full revolution!

    (stepDelay = 2);

    if (Distance == FullRotation * 7)

      if (digitalRead(3) == LOW) {
        digitalWrite(3, HIGH);  //this is a "toggle" to reverse whichever way its going to the other direction!
      }
      else {
        digitalWrite(3, LOW);
      }

    Distance = 0;  //reset our Distance counter!
  }

I suspect it should be something like this

  if (Distance == FullRotation * 6)  {  //as soon as distance = "FullRotation", that means we have made a full revolution!

    (stepDelay = 2);
  }

  if (Distance == FullRotation * 7) {

      if (digitalRead(3) == LOW) {
        digitalWrite(3, HIGH);  //this is a "toggle" to reverse whichever way its going to the other direction!
      }
      else {
        digitalWrite(3, LOW);
      }

    Distance = 0;  //reset our Distance counter!
  }

...R

//stepDelay = 2;
stepDelay *=2;//stepDelay = stepDelay*2; another way of writing the same thing
if (Distance == FullRotation * 6)  {  //as soon as distance = "FullRotation", that means we have made a full revolution!

    stepDelay = 2;
  }
  if (Distance == FullRotation * 7) {

I tried it both with and without parentheses around stepDelay = 2;

Both rotates 7 times then reverses direction for 7 rotations. No change in speed on the 7th rotation. I also tried stepDelay = 3; to further verify no speed change.

Could you please give the corrected code so I can compare side by side? I see your point but its difficult to understand how to correct this.
Meanwhile, it appears that the barrel jack connection to the motor power may have shorted my A4988.
Ill repair and get back to it tomorrow.

Thank you all for your assistance. I have learned something here today.

Curtis

Delta_G:
This get's it part way there.

Looks a little bit like what I suggested in Reply #10 - not that I'm complaining.

...R

Ok, back up and running.
Delta_G - So in other words, change "stepDelay = 2" to "stepDelay = stepDelay * 2" GREAT! It works!
Only problem now is that it only makes a partial (maybe a 120deg) revolution at the slow speed then reverts back to original speed for remainder of the 7th rotation. This has been a major breakthrough. Thanks so much.

Strangely, when 7th rotation begins in CCW direction, the slow speed occurs for about 20 deg of arc then the remaining 340 deg speed is at the original speed yet in the CW direction the slow speed occurs for about 100 deg arc and the remaining 260 deg is at the original speed.

//int Distance = 0;  //How far we've traveled
unsigned int Distance = 0;

You continue to type Distance as a signed int and you get rollover.

I suspected something like that so I reduced the number of rotations. Same problem.
I tried to add "unsigned int " to any distance term including the reset at the end. I kept getting the following error

Arduino: 1.8.9 (Windows 7), Board: "Arduino/Genuino Uno"

C:\Users\Owner\Documents\Arduino\sketch_sep29a\sketch_sep29a.ino: In function 'void loop()':

sketch_sep29a:43:28: error: expected primary-expression before 'unsigned'

unsigned int Distance = unsigned int Distance + 1; // record this step

^

sketch_sep29a:45:8: error: expected primary-expression before 'unsigned'

if ( unsigned int Distance == FullRotation * 6) { //as soon as distance = "FullRotation", that means we have made a full revolution!

^

sketch_sep29a:45:8: error: expected ')' before 'unsigned'

sketch_sep29a:49:7: error: expected primary-expression before 'unsigned'

if (unsigned int Distance == FullRotation * 7) {

^

sketch_sep29a:49:7: error: expected ')' before 'unsigned'

exit status 1
expected primary-expression before 'unsigned'

This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.

Sorry here is the code im currently using:

#include <Stepper.h>

int steppin = 4;
int dirpin = 3;
int stepDelay;
unsigned int Distance = 0;  //How far we've traveled

int AnalogReadCountDown = 3000;

int NumSteps = 5370;  //how many steps does the stepper take to make a rotation  (the actual motor, not factoring in "micro" steps)

int FullRotation = NumSteps ;


void setup()
{
  pinMode (steppin, OUTPUT);
  pinMode (dirpin, OUTPUT);
  digitalWrite(4, LOW);
  digitalWrite(3, LOW);
  Serial.begin(9600);
  //int val = analogRead(A0);
  //stepDelay = map(val, 0,1023,1,500);


}

void loop ()
{
  if (AnalogReadCountDown == 0) {
    int val = analogRead(A0);
    stepDelay = map(val, 0, 1023, 1, 500);
    Serial.println(stepDelay);
    AnalogReadCountDown =3000;
  }



  digitalWrite(steppin, HIGH);
  delayMicroseconds(stepDelay);
  digitalWrite(steppin, LOW);
  delayMicroseconds(stepDelay);
   Distance =   Distance + 1;   // record this step
  AnalogReadCountDown = AnalogReadCountDown - 1;
  if (   Distance == FullRotation * 6)  {  //as soon as distance = "FullRotation", that means we have made a full revolution!

    stepDelay = stepDelay * 2;
  }
  if (  Distance == FullRotation * 7)  {

    if (digitalRead(3) == LOW) {
      digitalWrite(3, HIGH);  //this is a "toggle" to reverse whichever way its going to the other direction!
    }
    else {
      digitalWrite(3, LOW);
    }

     Distance = 0;  //reset our Distance counter!
  }
}

It seemed like an overflow problem since the speed change went into effect but terminated prematurely reverting back to the original speed for the remainder of the 7th rotation. I was hoping to circumvent this although when I reduced the number of rotations to 6 total with the speed change at 5, which should have eliminated the overflow the problem still existed.

I thought the code would allow the 7th complete rotation at the slow speed followed by toggling the direction.
Why isn't it making a complete 7th rotation at stepDelay * 2?

Probably because you are counting loops to determine when you read your pot again instead of doing it at a time that makes sense to your process. It's probably going back to read the pot and that has a line to set the speed back to whatever.

Good catch.

Back in an original thread of yours, the issue was raised as to why you did not place the potentiometer speed adjustment in setup. It appears you wanted to adjust the speed while the cycle was running.

This program design has come back to bite you. If indeed you want speed adjust during the 7 rotation cycle, you need to pay attention to the actual step delay and what you want at the time of the adjustment.

So I made the changes as per your suggestions, including the move of the analogread and stepdelay command line to the set up. I then added the remaining 8th revolution at 1/3 speed. All worked well.
As was pointed out, after the 8th rotation the direction change and subsequent speed is at (stepdelay*3).

Here is where I think a hardware solution may be appropriate. I was thinking of putting a switch both at the "home" position of the motor and at the fully open position (8th rotation). Both switches would close a circuit sending the "enable" pin on the A4988 driver high. This should stop the motor. A pushbutton would open the circuit to the enable pin allowing the motor to advance. Then a command to reverse direction. If direction = CW make CCW at stepDelay, if CCW, make CW at stepDelay.

Does this seem feasable? An additional advantage is that, by not powering down/resetting the driver I wont have to deal with the driver's internal command to "home". As it is, when I power up the motor driver and the arduino simultaneously, the motors attempt to "home" causes it to move spasmodically.
With a hard connection to the valve this would create excessive torque in one direction of this movement.