How to limit Stepper Motor Movement while using PID?

I am trying to make a control valve using a stepper motor, using NEMA23 with TB6600 driver. i know the amount of steps to fully open or fully close the valve which was 7000 steps. but the thing was, when i was simulating my codes and everything, my motor would not stop moving open or closing depends on the PV towards the SV.

for example, if my set point was set at 1.7 bar, and the process variable is currently at 1.2 bar, my arduino would command the stepper to close the valve, which is sensible. but the problem is, my motor would not stop to close the valve even the valve has fully closed. this makes the stepper skip all the coils which was supposed to move the rotor.

how can i limit the amount of steps that the motor can move, which is from 0 (valve fully close) to 7000 (valve fully open).

unfortunately, i cant install a physical barier or a limit switch to stop the movement of the motor. since everything else was already welded and glued permanently.

here's the code for my PID


//LCD config
#include <Smoothed.h>   // Include the library
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <AccelStepper.h>
LiquidCrystal_I2C lcd(0x3f,16,2);  //sometimes the adress is not 0x3f. Change to 0x3f if it dosn't work.
AccelStepper myStepper (1,8,9);

/*    i2c LCD Module  ==>   Arduino
 *    SCL             ==>     A5
 *    SDA             ==>     A4
 *    Vcc             ==>     Vcc (5v)
 *    Gnd             ==>     Gnd      */

//Inputs and outpu`ts
int firing_pin = 3;
int myStepper_pul = 6;
int myStepper_dir = 7;
const int sensorPin = A0;
float analogRaw = 0.0;
float voltageRaw = 0.0;
float realPressure = 0.0;



//Variables
int last_CH1_state = 0;
bool newData, runallowed = false; 
int firing_delay = 7400;
int directionMultiplier = 1; // = 1: positive direction, = -1: negative direction
long receivedSteps = 0; //Number of steps
long receivedSpeed = 0; //Steps / second
long receivedAcceleration = 0; //Steps / second^2
float real_pressure_smoothed = 0;

float readPressure(){
  analogRaw = analogRead(sensorPin);
  voltageRaw = (analogRaw * 5.0 / 1024.0) - 0.92;
//  Serial.println("analog");
//  Serial.println(analogRaw);
//  Serial.println("voltage");
//  Serial.println(voltageRaw);
//  Serial.println("pressure");
  Serial.println((voltageRaw * 16.0 / 4.62) + 0.20);
  return (voltageRaw * 16.0 / 4.62) + 0.20;
} 

char receivedCommand;
int curPos = 0;
int nexPos = 0;

//////////////////////////////////////////////////////
int maximum_firing_delay = 7400;
/*Later in the code you will se that the maximum delay after the zero detection
 * is 7400. Why? Well, we know that the 220V AC voltage has a frequency of around 50-60HZ so
 * the period is between 20ms and 16ms, depending on the country. We control the firing
 * delay each half period so each 10ms or 8 ms. To amke sure we wont pass thsoe 10ms, I've made tests
 * and the 7400us or 7.4ms was a good value. Measure your frequency and chande that value later */
//////////////////////////////////////////////////////

unsigned long previousMillis = 0; 
unsigned long currentMillis = 0;
int temp_read_Delay = 500;
float real_pressure = 0.0;
float setpoint = 1.75;
bool pressed_1 = false;
bool pressed_2 = false;
int maxRotate = 6000;

//const int minSample = 5;
//int sampleCount = 0;
//const float trigger = 7.0;
//const float rangeTrigger = 0.5;
//bool startTrigger = false;

//PID variables
float PID_error = 0;
float previous_error = 0;
float elapsedTime, Time, timePrev;
float  PID_value = 0;
//PID constants
float kp = 200 ; float ki= 1;  float kd = 1;
float PID_p = 0;    float PID_i = 0;    float PID_d = 0;
int scale = 1000;
unsigned long prevmillis = 0;

Smoothed <float> mySensor;

void setup() {
  //Define the pins
  Serial.begin(9600);
  pinMode (firing_pin,OUTPUT); 
  pinMode (myStepper_pul,INPUT); 
  pinMode (myStepper_dir,INPUT);      
  lcd.init();       //Start the LC communication
  lcd.backlight();  //Turn on backlight for LCD
      myStepper.setMaxSpeed(1000); //SPEED = Steps / second
      myStepper.setAcceleration(1000); //ACCELERATION = Steps /(second)^2
      myStepper.disableOutputs(); //disable outputs
  mySensor.begin(SMOOTHED_AVERAGE, 18); 
    myStepper.setCurrentPosition (0);
  myStepper.moveTo (0);
}

void loop() {    

//  Serial.println("loop");
  
//   if (real_pressure_smoothed > trigger - rangeTrigger && real_pressure_smoothed < trigger + rangeTrigger){
//      sampleCount += 1;
//    }
//
//    if (sampleCount > minSample){
//      startTrigger = true;
//    }
//
//    if (startTrigger == false){
//      lcd.setCursor(0,0);
//      lcd.print("belom trigger ");
//      return 0;
//    }

    
    RunTheMotor(); //function to handle the motor  
    analogRaw = analogRead(0);
    voltageRaw = (analogRaw * 5.0 / 1024.0) - 0.92;
    real_pressure = (voltageRaw * 16.0 / 4.62) + 0.54;
    mySensor.add(real_pressure);
  
  currentMillis = millis();           //Save the value of time before the loop
   /*  We create this if so we will read the temperature and change values each "temp_read_Delay"
    *  value. Change that value above iv you want. The MAX6675 read is slow. Tha will affect the
    *  PID control. I've tried reading the temp each 100ms but it didn't work. With 500ms worked ok.*/
  if(currentMillis - previousMillis >= temp_read_Delay){
    previousMillis += temp_read_Delay;              //Increase the previous time for next loop
    real_pressure_smoothed = mySensor.get();
    PID_error = setpoint - real_pressure_smoothed;        //Calculate the pid ERROR
    
//    if(PID_error > 30)                              //integral constant will only affect errors below 30ºC             
//    {PID_i = 0;}
    
    PID_p = kp * PID_error;                         //Calculate the P value
    PID_i = PID_i + (ki * PID_error);               //Calculate the I value
    timePrev = Time;                    // the previous time is stored before the actual time read
    Time = millis();                    // actual time read
    elapsedTime = (Time - timePrev) / 1000;   
    PID_d = kd*((PID_error - previous_error)/elapsedTime);  //Calculate the D value
    PID_value = (PID_p + PID_i + PID_d);                      //Calculate total PID value

    //Printe the values on the LCD
       if (prevmillis >= millis){
      prevmillis = 250;
    }
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Set: ");
    lcd.setCursor(5,0);
    lcd.print(setpoint);
    lcd.setCursor(0,1);
    lcd.print("Pressure: ");
    lcd.setCursor(11,1);
    lcd.print(real_pressure_smoothed);
    
    Serial.println (real_pressure_smoothed);
    previous_error = PID_error; //Remember to store the previous error.
    newData= true;
  }
//
//  Serial.println ("pid error");
//  Serial.println (PID_error);
//
//  Serial.println ("D");
//  Serial.println (PID_d);

  

  //If the zero cross interruption was detected we create the 100us firing pulse  
  if (newData)     
//    Serial.println("PID_value");
//    Serial.println(PID_value);
    {
        if (PID_value > maxRotate){
          nexPos = maxRotate;
        }else if (PID_value <(-1 * maxRotate)){
          nexPos = -1 * maxRotate;
        }else{
          nexPos= PID_value;
        }
         //value for the steps
         //Run the function

    } 
   RotateRelative();
//     Serial.println(readPressure());
//     delay(1000);
}


void RotateRelative(){
    //We move X steps from the current position of the stepper motor in a given direction.
    //The direction is determined by the multiplier (+1 or -1)

//    Serial.println("curPos");
//    Serial.println(curPos);

    if (nexPos > curPos){
      receivedSteps = nexPos - curPos;
      curPos = nexPos;
    }else if (nexPos < curPos){
      receivedSteps = -nexPos - curPos;
      curPos = nexPos;
    }else{
      return 0;
    }

//    Serial.println("received Steps");
//    Serial.println(receivedSteps);
//    Serial.println("nexPos");
//    Serial.println(nexPos);

    runallowed = true; //allow running - this allows entering the RunTheMotor() function.
    myStepper.setMaxSpeed(1000); //set speed
    myStepper.move(receivedSteps); //set relative distance and direction

//    Serial.println("STEPS");
//    Serial.println(receivedSteps);
    
}
    
void RunTheMotor() //function for the motor
{
    if (runallowed == true)
    {
        myStepper.enableOutputs(); //enable pins
        myStepper.run(); //step the motor (this will step the motor by 1 step at each loop)  
    }
    else //program enters this part if the runallowed is FALSE, we do not do anything
    {
         myStepper.disableOutputs(); //disable outputs
        return 0;
    }
}

thanks for the help, i sorry if the code llokk

What I would do is constrain the PID_value to the range 0 to 7000 and use myStepper.moveTo() (absolute move) instead of myStepper.move() (relative move). That way the stepper will never move out of the 0 to 7000 range.

If there are no physical stops (the valve can turn forever in either direction) you will need a 'home' switch so you can synchronize the steppe range to the valve range.

A stepper motor is not damaged or overloaded if it runs into an end stop, so a simple approach is to always send more steps than ever required, to fully open or close the valve.

Since the motor stops, you can also reset the current position to be at one end or the other of the travel.

Since the motor stops, you can also reset the current position to be at one end or the other of the travel.

i have tried it here, but it just wont't work, or am i doing something wrong here ? the coding was written by me and my friend, but i couldn't really understand this part, since im not that good in coding

if i limit the PID_value to 7000, isn't that also limiting the amount of error that could be handled ?

i have never thought of using the moveTo() function before, might give it a try tomorow thanks..

how can you suggest me to put a physical switch onto the stem of the valve ? i need some inspiration too. but take a note that right now, there is only a little room to put the switch, since i have welded the motor sittings to the valve

A mechanical switch detects a notch or protrusion on the moving part.

An optical sensor detects a reflective, light, or dark spot on the moving part.

A Hall-Effect switch detects a magnet glued to the moving part.

You could also add the missing physical stops at full open and/or full closed.

Sorry, I have no idea what you mean by "won't work". Describe what you expect to happen, and what happens instead.

sorry, what i meant by won't work is that my arduino still commands the motor to close, even if i had stated that it cannot move more than maxrotate amount. but i dont really know whether the codes are correct or not.

thanks,

ah i see, so its still counts the amount of rotation before commanding it to stop..
thank you sir, but i think iwill try the coding approaches for now, but apreciate the help

For questions about code, post ALL the code. The snippet in post #4 does not help us understand your problem.

the snippet code, comes from the original post.. i was quoting it