Need help with toggle switching a servo in "for" loop

Hi, i know it may sound easy but not for me as I'm a noob. I couldnt find a solution to my project.

Basicly i want to turn a servo on, which is in "for" loop (servo sweep) by single clicking a push button and turn it off with another click. one click on, one click off. I want it to be working until the second press.

This was what i'm currently working on; but it make the servo sweeps for once only;

#include <Servo.h>


Servo myservo;  // create Servo object to control a servo
// twelve Servo objects can be created on most boards

int pos = 0;  // variable to store the servo position
int minAngle = 0;
int maxAngle = 130;

int servoState = 0;
int buttonPin = 12;  // the number of the pushbutton pin
int servoPin = 8;    // the number of the Servo pin

int buttonNew;
int buttonOld = 1;
int dt = 100;



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

  Serial.print("ButtonPin = ");
  Serial.println(digitalRead(buttonPin));
  Serial.print("ButtonOld = ");
  Serial.println(digitalRead(buttonOld));
  Serial.print("ButtonNew = ");
  Serial.println(digitalRead(buttonNew));
  delay(dt);



  Serial.print("ServoState = ");
  Serial.println(digitalRead(servoState));
  Serial.print("ServoPin = ");
  Serial.println(digitalRead(servoPin));

  myservo.attach(servoPin);
  pinMode(servoPin, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
}


void loop() {

  buttonNew = digitalRead(buttonPin);
  if (buttonOld == 0 && buttonNew == 1) {
    if (servoState == 0) {
      digitalWrite(servoPin, HIGH);
      servoState = 1;
    } else {
      digitalWrite(servoPin, LOW);
      servoState = 0;
    }
    do {
    for (pos = minAngle; pos <= maxAngle; pos += 1) {  // goes from 0 degrees to 180 degrees
      // in steps of 1 degree
      myservo.write(pos);  // tell servo to go to position in variable 'pos'
      delay(3);            // waits 15 ms for the servo to reach the position
    }
    for (pos = maxAngle; pos >= minAngle; pos -= 1) {  // goes from 180 degrees to 0 degrees
      myservo.write(pos);                              // tell servo to go to position in variable 'pos'
      delay(3);
    }
    
    }
    while (digitalRead(servoPin) == HIGH);
  }
  buttonOld = buttonNew;
  delay(dt);
}

servo is always on, it's position changes. it draws more current to maintain the position depending on the force required to maintain that position

perhaps you want detach() to disable the servo and attach() to re-enable it

By "on" do you mean that the servo is moving? So you want to push a button, the servo starts moving and when you push the button again, the servo stops moving?

If that is the case, you don't need your do/while loop wrapped around your for() loop which is all inside your loop() function. Just let loop() do what it was born to do... loop.

Every time through loop():

  1. check your button state and if it has changed, you are either off/on
  2. if on, take one servo step in the current direction (up or down)
  3. if you are at the limit (min/max based on current direction) then change direction
  4. repeat forever (or keep track if you want to stop after 2 sweeps)

Thanks for your answer, yes ı want to move it 0 to 180 and 180 to 0 until i press the button again.. and yes i tried that too. i deleted "do" and while section and nothings changed. same.. what should i do ?

that sound good and might help.. i didnt tried it before..so i will try it now and inform you. thanks for your answer

I had more trouble with this than I care to admit.

The key is to not change direction, rather to make the direction go towards the opposite extreme. To ensure you only change direction once.

Is it a subtle difference? If you just change direction at a limit, you have to make sure that limit doesn't happen again immediately on the next step.

For step sizes that exactly fit, like stepping by one, there may be no problem. But using an odd step size may mean the test succeeds again, and you change direction too soon. At least that's what I think was happening to me. I was in a hurry, my ride to the beach was rolling towards me, and it was just so annoying I gave up and made the step size fit exactly, problem temporarily solved.

When you calculate a new position and it is over 180, set the position to 180 before you write it to the servo, and change the direction to -1. Use a step size times the direction when you add it to the servo position.

At the other end, the same thing, only with 0 and changing the direction to 1.

It is a kind of hysteresis, which I mention whenever I can as it is one of my favorite fundamental concepts in control systems.

This seemed so obvious after I relaxed and thought about it calmly. I don't mean to say y'all are subject to the same kind of lapses of reason.

HTH

a7

unfortunatelly its not working.

When you try a sketch that isn't working, you could say just how, but you should def post the code so we can see if you were able to follow any suggestions that informed it.

a7

1 Like
#include <Servo.h>


Servo myservo;  // create Servo object to control a servo
// twelve Servo objects can be created on most boards

int pos = 0;  // variable to store the servo position
int minAngle = 0;
int maxAngle = 130;

int servoState = 0;
int ButtonPin = 12;  // the number of the pushbutton pin
int ServoPin = 8;    // the number of the Servo pin

int buttonNew;
int buttonOld = 1;
int dt = 100;



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

  /*Serial.print("ButtonPin = ");
  Serial.println(digitalRead(ButtonPin));
  Serial.print("ButtonOld = ");
  Serial.println(digitalRead(buttonOld));
  Serial.print("ButtonNew = ");
  Serial.println(digitalRead(buttonNew));
  delay(dt);



  Serial.print("ServoState = ");
  Serial.println(digitalRead(servoState));
  Serial.print("ServoPin = ");
  Serial.println(digitalRead(ServoPin));
*/
  myservo.attach(ServoPin);
  pinMode(ServoPin, OUTPUT);
  pinMode(ButtonPin, INPUT_PULLUP);
}


void loop() {

  buttonNew = digitalRead(ButtonPin);
  if (buttonOld == 0 && buttonNew == 1) {
    if (servoState == 0) {
      digitalWrite(ServoPin, HIGH);
      servoState = 1;
    } else {
      digitalWrite(ServoPin, LOW);
      servoState = 0;
    }
    for (pos = minAngle; pos <= maxAngle; pos += 1) {  // goes from 0 degrees to 180 degrees
      // in steps of 1 degree
      myservo.write(pos);  // tell servo to go to position in variable 'pos'
      delay(3);            // waits 15 ms for the servo to reach the position
    }
    for (pos = maxAngle; pos >= minAngle; pos -= 1) {  // goes from 180 degrees to 0 degrees
      myservo.write(pos);                              // tell servo to go to position in variable 'pos'
      delay(3);
    }
  }
  buttonOld = buttonNew;
  delay(dt);
}

final code is here.. any suggestions?

with this code, servo moves from 0 to 130 and 130 to 0 once each time I press the button.... I want it to keep doing this action until i press the button again.

Yes. Move your for loops out of the button handling code, and run them only when servoState is 1.

Yu write the code - this is pseudocode, a way for me to be lazy but show you what I mean:

loop

   establish servo state (toggles with each button press)



   if servo state is one
      do those for loops

HTH I'm off to the beach, so.

a7

Thank You very Much. I'm so happy. I did what you told me and...It Worked!!! I was trying hard to do it for a week and about to give up. That means a lot to me thank you. :pray:

I'm glad you are happy with the code. If you want to be even more responsive to your buttons, eliminate the for() loops as well so you only take 1 step each time through loop as well as check for a button press
Note: you have to move your delay() to only when a button change is detected or else your servo will move painfully slow.

#include <Servo.h>


Servo myservo;  // create Servo object to control a servo
// twelve Servo objects can be created on most boards

int pos = 0;  // variable to store the servo position
int direction = 1;  // 1 = up, -1 = down
const int step = 1; // amount of movement each time through loop

const int minAngle = 0;
const int maxAngle = 130;

int servoState = 0;
int ButtonPin = 12;  // the number of the pushbutton pin
int ServoPin = 8;    // the number of the Servo pin

int buttonNew;
int buttonOld = 1;
int dt = 200;

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

  myservo.attach(ServoPin);
  pinMode(ServoPin, OUTPUT);
  pinMode(ButtonPin, INPUT_PULLUP);
}


void loop() {

  buttonNew = digitalRead(ButtonPin);
  if (buttonOld == 0 && buttonNew == 1) {
    if (servoState == 0) {
      digitalWrite(ServoPin, HIGH);
      servoState = 1;
      Serial.println("on");
    } else {
      digitalWrite(ServoPin, LOW);
      servoState = 0;
      Serial.println("off");
    }
    delay(dt);

  }

  if ( servoState == 1 ) {
    // servo is active so take 1 step
    if ( direction > 0 ) {
      // going up
      pos += step;
      if ( pos >= maxAngle ) {
        // maximum, so reverse direction
        pos = maxAngle;
        direction = -direction;
      }
    }
    else {
      // going down
      pos -= step;
      if ( pos <= minAngle ) {
        // minimum, so reverse direction
        pos = minAngle;
        direction = -direction;
      }
    }
    myservo.write(pos);  // tell servo to go to position in variable 'pos'
    delay(3);            // waits 15 ms for the servo to reach the position
  }
  buttonOld = buttonNew;
}

Doesn't. Part of the reason I ignore comments!

And here

        // maximum, so reverse direction
        pos = maxAngle;
        direction = -direction;

You are on solid ground due to the encompassing logic, but it would be easier to believe if you wrote

        // maximum
        pos = maxAngle;
        direction = -1; 

and similarly the other case. Call it belt and suspenders as well.

And I think (but can't be sure without building and running the sketch) that this

if (buttonOld == 0 && buttonNew == 1) {

means your debouncing delay state change code will allow changes to servoState because of bouncing on contacts opening. Which they do.

The common pattern is to detect a change, act on the change if it is the edge you are interested in, and in either case, delay for the debounce time. BTW 200 milliseconds are some truly crappy switches if that's what they need!

a7

Those are all the OP's comments and delay, except for the pos/direction. I don't usually clean up other's [inaccurate] comments

1 Like

I just delete them all before starting in on the code. :expressionless:

a7

You've always had 130, is that a typo?

I don't usually post solutions. Here I have modifed the changes that @blh64 offered which allow for turning on and off the sweeping whilst it is in progress.

The button handling in the origianl is misleading and complicated. It actually depends on the 200 millisecond delay - if you leave your fat finger on the button any longer, it can catch a bounce and toggle the state.

It is only as good as this

  if (digitalRead(ButtonPin) == 0) {
    servoState = !servoState;

    if (servoState == 1) {
      digitalWrite(ServoPin, HIGH);
      Serial.println("on");
    } else {
      digitalWrite(ServoPin, LOW);
      Serial.println("off");
    }

    delay(dt);
  }

which tracks no button state - it just sees the button reading LOW (being pressed) and toggles the servo state, the delay is again essential for the logic to function correctly for all pushbuttons, and is the period one has to get off the button to avoid undesired outcomes.

Here is the code with a button handler that tracks button state, and uses a short delay on both transitions as a means of debouncing.

# include <Servo.h>

Servo myservo;

int pos = 0;  // variable to store the servo position
int direction = 1;  // 1 = up, -1 = down
const int step = 5; // amount of movement each time through loop

const int minAngle = 0;
const int maxAngle = 180;

int servoState = 0;
int ButtonPin = 12;  // the number of the pushbutton pin
int ServoPin = 8;    // the number of the Servo pin

int buttonNew;
int buttonOld = 1;
int dt = 20;

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

  myservo.attach(ServoPin);
  pinMode(ServoPin, OUTPUT);
  pinMode(ButtonPin, INPUT_PULLUP);

  pinMode(7, OUTPUT);
}

void loop() {

  buttonNew = digitalRead(ButtonPin);
  if (buttonOld != buttonNew) {
    if (!buttonNew) {
      if (servoState == 0) {
        digitalWrite(ServoPin, HIGH);
        servoState = 1;
        Serial.println("on");
      } else {
        digitalWrite(ServoPin, LOW);
        servoState = 0;
        Serial.println("off");
      }
    }
    delay(dt);
  }
  buttonOld = buttonNew;

  digitalWrite(7, servoState ? HIGH : LOW);

  if (servoState == 1) {
    pos += step * direction;

    if (pos >= maxAngle) {
      pos = maxAngle;
      direction = -1;
    }
    
    if (pos <= minAngle) {
      pos = minAngle;
      direction = 1;
    }

    myservo.write(pos);
    Serial.println(pos);
    delay(20);
  }
}

I added the LED at pin 7 to show the servo state. I changed the delay between steps to 20 milliseconds. There is a subtle interaction between the delay per step and the step size - it only takes 0.6 milliseconds to step one degree, but if you use a delay saying it is "to allow the servo to move" and call it very frequently, the servo only hears about it every 20 milliseconds, so sweeping becomes unsatisfactory as the servo gets way behind, or out of sync at least, kinda goes nuts.

I redid the code that handled the times when the servo is active.

a7

This is more responsive and fluent. thanks for your time. besides with this code servo stops its motion at whereever it is when the button pressed. now i am going to try to change its position to stop always at 0.

Good next step.

I thought about it, you may have. Here are my specifications, it may be harder than it seems, so please take small steps.

  • when turned off, the servo should park at 90 degrees
  • when started, move in the opposite direction thy it went to park

The challenge is to have the servoState be only part of why the servo moves.

So we are in the territory of FSMs, or finite state machines. Don't worry to much about it if you feel you haven't gotten some basic coding skills developed, but if you take a minute and google

arduino finite state machine

you will see literally dozens of tutorials be they videos or texts or part of a larger organized online course of study; exposure to more than one is useful as learning styles differ as much as teaching styles. Something will catch you at the right level.

Here's one that you might like

For this servo thing, the states might be like MovingUp, MovingDown, Parking and Parked.

I've never been any good at naming things, so.

Another very useful concept that will help is to organize your control flow using the IPO model.

Read about it here in the abstract

The IPO Model

but don't get bogged down - it just means that every time you loop, you look at your inputs, figure out what the inputs mean for what is currently happening and decide what should happen next then write all your outputs.

The two things combined make a buncha things quite easier, but it will all seem hard until it gets easy.

a7

Thank you for your assistance. I better get to work on this. I'll let you know about the process and maybe ask for help again :smiley: