Using a stepper motor between boundaries

Hello guys,

does anyone here have an idea how to improve the following code so that it fulfils the condition. Up to now I got so far and actually hoped to find a remedy with the new limits of the sensor on the gear wheel (A3). Namely, the stepper motor should always move back and forth between these boundaries so that it always remains in the 180 degree range.
To do this, it should take the output voltage of the sensor which is directly connected to it via a 1 to 1 gear. Using the rotation potentiometer on A0, I have already been able to determine exactly these limits (0.42 degrees at the start of the scale and 2.42 degrees at the end of the scale) using a laser on the A3 rotation potentiometer and also calculate the steps in between, which are exactly 1020. It has also shown that the whole mechanism works.

I suspect that I have an error in my thinking, in that the sensor is the actual clock generator for the motor, but at the same time the motor is always one step ahead as it actuates it. And this is also the current result, the motor turns in any direction, sometimes quickly, sometimes slowly. Sometimes it drives out of the limits or stays somewhere in the radius between any two.

If anyone has a new approach for me or could tell me where my error could be, I would be more than happy and grateful.

const int motorPin1 = 8;
const int motorPin2 = 9;
const int motorPin3 = 10;
const int motorPin4 = 11;

const int sensorPin = A0;

const int stepsPerRevolution = 4096;
int stepNumber = 0;

const float minVoltage = 0.42;
const float maxVoltage = 2.41;
const int totalSteps = 1020;

void setup() {
  Serial.begin(9600);

  pinMode(motorPin1, OUTPUT);
  pinMode(motorPin2, OUTPUT);
  pinMode(motorPin3, OUTPUT);
  pinMode(motorPin4, OUTPUT);
}

void loop() {
  int sensorValue = analogRead(sensorPin);
  float voltage = sensorValue * (5.0 / 1023.0);

  float adjustedMinVoltage = 0.99;
  float adjustedMaxVoltage = 1.09;

  int targetStep = map(voltage, adjustedMinVoltage, adjustedMaxVoltage, 0, totalSteps);

  int stepsToMove = targetStep - stepNumber;

  Serial.print("Sensor Value: "); Serial.println(sensorValue);
  Serial.print("Calculated Voltage: "); Serial.println(voltage);
  Serial.print("Target Step: "); Serial.println(targetStep);
  Serial.print("Current Step: "); Serial.println(stepNumber);
  Serial.print("Steps to Move: "); Serial.println(stepsToMove);

  if (stepsToMove != 0) {
    int direction = stepsToMove > 0 ? 1 : -1;
    stepMotor(direction);
    stepNumber += direction;
    delay(10);
  }
}

void stepMotor(int step) {
  switch (stepNumber % 4) {
    case 0:
      digitalWrite(motorPin1, HIGH);
      digitalWrite(motorPin2, LOW);
      digitalWrite(motorPin3, LOW);
      digitalWrite(motorPin4, LOW);
      break;
    case 1:
      digitalWrite(motorPin1, LOW);
      digitalWrite(motorPin2, HIGH);
      digitalWrite(motorPin3, LOW);
      digitalWrite(motorPin4, LOW);
      break;
    case 2:
      digitalWrite(motorPin1, LOW);
      digitalWrite(motorPin2, LOW);
      digitalWrite(motorPin3, HIGH);
      digitalWrite(motorPin4, LOW);
      break;
    case 3:
      digitalWrite(motorPin1, LOW);
      digitalWrite(motorPin2, LOW);
      digitalWrite(motorPin3, LOW);
      digitalWrite(motorPin4, HIGH);
      break;
  }
}



I think it is a resolution problem. If 180° of turning is 4096/2=2048 steps and you are trying to control it based on 1024*(2.41-0.42)/5=407 different analogRead values, youre going to get jumps of 2048/407=5 steps +/-5 times the ADC noise.

If you don't want jumps due to measurement, you need to be able to measure the angle to better than one-step accuracy.

Great thanks, so would that not be possible with the current setup or would it be enough if I change the definition from angle to step using the software definition.
However, theoretically for my desired result it would be quite practical if I had a reasonably high resolution of output signals, so that I could define a proof of deviation by the always constant rotation and positioning.

I'd not use those const float minVoltage = 0.42; values but rather the ADC value that represents this voltage (I assume your sensor is ratiometric as otherwise your readings vary with changes in the 5V supply voltage). That would be 86 and 493 respectively. That will also work nice with map() as it takes integer values only. Saves a lot of unnecessary calculations.

Then you can set a hysteresis or threshold in the code: don't make any movement if the target is less than say 5 or 10 steps away, whatever makes sense. That will filter out a lot of jitter.

1 Like

Hi @00soliwa ,

is there a reason why you need the calculation of voltages and stepsToMove?

How about just using the analog data read from the sensor as they are and use them to evaluate direction and then just step to the left or right until the limit has been reached?

Maybe I did not get the full picture but here just a simple sketch which you can test on Wokwi:

https://wokwi.com/projects/387014223071927297

/*
  Forum: https://forum.arduino.cc/t/using-a-stepper-motor-between-boundaries/1211483
  Wokwi: https://wokwi.com/projects/387014223071927297
*/

const int motorPin1 = 8;  
const int motorPin2 = 9;
const int motorPin3 = 10;
const int motorPin4 = 11;

const int sensorPin = A0;

//const float minVoltage = 0.42;
//const float maxVoltage = 2.41;
const int   minValue   =  86;
const int   maxValue   = 493;
const int totalSteps = 1020;

int position;
boolean moveLeft = true;
int stepNumber = 0;

void setup() {
  Serial.begin(9600);
  pinMode(motorPin1, OUTPUT);
  pinMode(motorPin2, OUTPUT);
  pinMode(motorPin3, OUTPUT);
  pinMode(motorPin4, OUTPUT);
}

void loop() {
  if (moveLeft){
    turnLeft();
  } else {
    turnRight();
  }
 
}

int sensorValue(){
 return analogRead(sensorPin);
}

void turnLeft(){
  position = sensorValue();
  if (position > minValue){
    stepMotor(-1);
    stepNumber--;
  } else {
    moveLeft = false;
  }
}

void turnRight(){
  position = sensorValue();
  if (position < maxValue){
    stepMotor(1);
    stepNumber++;
  } else {
    moveLeft = true;
  }
}

void stepMotor(int dir){
  Serial.println(position);
  if (dir == 1){
    for (int i = 0;i <= 3;i++ ){
      step(i);
    }
  } else {
    for (int i = 3; i >= 0;i-- ){
      step(i);
    }
  }
}


void step(int aStep) {
  switch (aStep) {
    case 0:
      digitalWrite(motorPin1, LOW);
      digitalWrite(motorPin2, LOW);
      digitalWrite(motorPin3, HIGH);
      digitalWrite(motorPin4, LOW);
      break;
    case 1:
      digitalWrite(motorPin1, LOW);
      digitalWrite(motorPin2, HIGH);
      digitalWrite(motorPin3, LOW);
      digitalWrite(motorPin4, LOW);
      break;
    case 2:
      digitalWrite(motorPin1, LOW);
      digitalWrite(motorPin2, LOW);
      digitalWrite(motorPin3, LOW);
      digitalWrite(motorPin4, HIGH);
      break;
    case 3:
      digitalWrite(motorPin1, HIGH);
      digitalWrite(motorPin2, LOW);
      digitalWrite(motorPin3, LOW);
      digitalWrite(motorPin4, LOW);
      break;
  }
  delay(10);
}

Just move the slide potentiometer instead of your sensor ...

Got the same idea :wink: but even without mapping ...

Thank you guys very much and I'll try to adapt your code for my system and implement the tips. I probably had a plank in front of my head and kept trying to get my code to run

Actually there is no explicit reason, it's just based on the simple case that I took another code of mine in which I already did the calculation of the voltage values and thought if it works there it will work here and the StepsToMove topic was inspired by another post because I'm super clueless with the topic

Hi everyone, I was able to solve the problem. I used your advice and made minor adjustments to the value comparison and it actually worked. Thank you very much


const int motorPin1 = 8;  
const int motorPin2 = 9;
const int motorPin3 = 10;
const int motorPin4 = 11;

const int sensorPin = A5;

//const float minVoltage = 0.42;
//const float maxVoltage = 2.41;
const int   minValue   =  60;
const int   maxValue   = 718;
const int totalSteps = 1020;

int position;
boolean moveLeft = true;
int stepNumber = 0;

void setup() {
  Serial.begin(9600);
  pinMode(motorPin1, OUTPUT);
  pinMode(motorPin2, OUTPUT);
  pinMode(motorPin3, OUTPUT);
  pinMode(motorPin4, OUTPUT);
}

void loop() {
  if (moveLeft){
    turnLeft();
  } else {
    turnRight();
  }
 
}

int sensorValue(){
 return analogRead(sensorPin);

}

void turnLeft(){
  Serial.println("Left");
  position = sensorValue();
  if (position < maxValue){
    stepMotor(-1);
    stepNumber--;
  } else {
    moveLeft = false;
  }
}

void turnRight(){
  Serial.println("right");
  position = sensorValue();
  if (position > minValue){
    stepMotor(1);
    stepNumber++;
  } else {
    moveLeft = true;
  }
}

void stepMotor(int dir){
  Serial.println(position);
  if (dir == 1){
    for (int i = 0;i <= 3;i++ ){
      step(i);
    }
  } else {
    for (int i = 3; i >= 0;i-- ){
      step(i);
    }
  }
}


void step(int aStep) {
  switch (aStep) {
    case 0:
      digitalWrite(motorPin1, LOW);
      digitalWrite(motorPin2, LOW);
      digitalWrite(motorPin3, HIGH);
      digitalWrite(motorPin4, LOW);
      break;
    case 1:
      digitalWrite(motorPin1, LOW);
      digitalWrite(motorPin2, HIGH);
      digitalWrite(motorPin3, LOW);
      digitalWrite(motorPin4, LOW);
      break;
    case 2:
      digitalWrite(motorPin1, LOW);
      digitalWrite(motorPin2, LOW);
      digitalWrite(motorPin3, LOW);
      digitalWrite(motorPin4, HIGH);
      break;
    case 3:
      digitalWrite(motorPin1, HIGH);
      digitalWrite(motorPin2, LOW);
      digitalWrite(motorPin3, LOW);
      digitalWrite(motorPin4, LOW);
      break;
  }
  delay(5);
}

That code looks a lot cleaner now! Nicely done.

One point of improvement: get rid of that delay() in your step() function, by using a mllis() (or micros() as it's only 5 ms per step) timer in loop() instead (see "blink without delay" on how this works). Then your Arduino can do other things at the same time.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.