While loop not executing the second time

So I am doing a project which is to get a servo motor moving by doing serial communication with python. However, the while/if (mode == 2) {...} statements do not work after the variable mode is changed to 2 for the second time (and so on). Note that the while/if (mode == 1) statements works fine.

I have tried everything I have think of, for example using an LED and a serial monitor to check the values of the variable.

These are the arduino and python codes (I omitted everything that works fine):
Arduino code:

void loop() {
  // get input for mode
  if (mode == 0) {
    digitalWrite(LED_BUILTIN, LOW);
    // Serial.println("mode?");
    while (Serial.available() > 0) {
      String sel = Serial.readString();
      mode = sel.toInt();
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print(mode == 2); // it showed "1" when I typed 2 in python
      if (mode == 1 || mode == 2) {
        break;
      }
    }
  }

  // Codes for manual control
  // run once when mode = 1
  if (mode == 1) {
    ...
  }

  // run as long as button isnt pressed
  while (mode == 1) {
    ...
  }

  if (mode == 2) {
    // this works the first time, but when mode is switched back to 0 then switched to 2 this does not work
    digitalWrite(LED_BUILTIN, HIGH); 
  }

  // run repeatedly when mode = 2
  while (mode == 2) {
    // get linear position from serial monitor
    while (Serial.available() > 0) {
      String selpos = Serial.readString();
      pos = selpos.toInt();
      if (pos == 0 || (pos >= 33 && pos <= 56)) {
        break;
      }
    }

    // exit auto mode if input is 0
    if (pos == 0) {
      myservo.write(0);
      mode = 0;
    }

    // calculate angular position of the servo, and turn it into the specified linear position
    if (pos >= 33 && pos <= 56.5) {
      pos = (pos - 33) * 7.6433;
      myservo.write(pos);
      // delay(10000);

      while (1) {
        // Read the button state
        int btnState = digitalRead(SW);
        // If we detect LOW signal, button is pressed and exit manual control mode
        if (btnState == LOW) {
          // Remember last button press
          lastButtonPress = millis();
          delay(100);
          // if 50ms have passed since last LOW pulse, it means that the
          // button has been pressed, released and pressed again
          if (millis() - lastButtonPress > 50) {
            // Serial.println("Retracted");
            Serial.println("8");
            myservo.write(0);
            pos = 1;
            break;
          }
        }
      }
    }
  }  
}

And the python code is:

while 1:

    # get user input for mode
    while mode == "0":
        print("Input mode. (1: Manual, 2: Auto)")
        selection = input()
        while selection != "1" and selection != "2":
            print("Invalid input, please try again. (1: Manual, 2: Auto)")
            selection = input()
        mode = selection
        ser.write(mode.encode())
        break


    # code for manual control
    if mode == "1":
        ...

    while mode == "1":
        ...

    # code for automatic control
    while mode == "2":
        print("Input distance (33-56mm). Input 0 to exit auto mode.")
        pos = input()
        while (int(pos) < 33 or int(pos) > 56) and int(pos) != 0:
            print("Invalid input, please try again (33-56mm). Input 0 to exit control mode")
            pos = input()
        # write selection to serial
        ser.write(pos.encode())
        if pos == "0":
            mode = "0"
            pos = "1"
            break
        if int(pos) >= 33 and int(pos) <= 56:
            print("Extended to required distance. Press to retract.")
        while int(pos) >= 33 and int(pos) <= 56:
            exit_2 = int.from_bytes(ser.readline(), "little")
            # print(exit_2)
            if exit_2 == 658744:
                pos = "1"
                break

Any helps and questions are appreciated, thanks!

Hello michael_yeung
Welcome to the worldbest forum.
Post the complete Arduino code to see how we can help.
Have a nice day and enjoy coding in C++.

Welcome to the forum

Please post your full sketch rather than a portion of it or a smaller but complete sketch that illustrates the problem. It can be important to know how and where variables are declared, for instance

may as well edit some of the comments in the code, because it looks very messy right now
FYI, the lcd monitor and the led is there just to see if the codes works.

#include <Servo.h>
#include <LiquidCrystal_I2C.h>

#define CLK 2
#define DT 3
#define SW 4

int currentStateCLK;
int lastStateCLK;

Servo myservo;  // create servo object to control a servo
LiquidCrystal_I2C lcd(0x27, 16, 2);

float pos = 1;       // variable to store the servo position (In mm)
int manual_pos = 0;  // init manual position
int mode = 0;        // init mode
unsigned long lastButtonPress = 0;

void setup() {
  // Set encoder pins as inputs
  pinMode(CLK, INPUT);
  pinMode(DT, INPUT);
  pinMode(SW, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);

  lcd.init();
  lcd.backlight();

  // myservo.write() range from 0-180
  // the value controls the angular position of the servo
  Serial.begin(9600);
  myservo.attach(9, 544, 2500);

  // initialise state of servo
  myservo.write(0);
}

void loop() {
  // get input for mode
  if (mode == 0) {
    digitalWrite(LED_BUILTIN, LOW);
    // Serial.println("mode?");
    while (Serial.available() > 0) {
      String sel = Serial.readString();
      mode = sel.toInt();
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print(mode == 2);  
      if (mode == 1 || mode == 2) {
        break;
      }
    }
  }

  // Codes for manual control
  // run once when mode = 1
  if (mode == 1) {
    manual_pos = 0;
    lastStateCLK = digitalRead(CLK);
    digitalWrite(LED_BUILTIN, HIGH);
  }

  // run as long as button isnt pressed
  while (mode == 1) {
    // get the current state of CLK
    currentStateCLK = digitalRead(CLK);
    // If last and current state of CLK are different, then pulse occurred
    // React to only 1 state change to avoid double count
    if (currentStateCLK != lastStateCLK && currentStateCLK == 1) {
      // If the DT state is different than the CLK state then
      // the encoder is rotating CW so increment
      if (digitalRead(DT) != currentStateCLK) {
        manual_pos++;
        if (manual_pos > 180)
          manual_pos = 180;
      } else {
        // Encoder is rotating CCW so decrement
        manual_pos--;
        if (manual_pos < 0)
          manual_pos = 0;
      }
    }

    // move the servo
    myservo.write(manual_pos);

    // Remember last CLK state
    lastStateCLK = currentStateCLK;

    // Read the button state
    int btnState = digitalRead(SW);
    // If we detect LOW signal, button is pressed and exit manual control mode
    if (btnState == LOW) {
      // if 50ms have passed since last LOW pulse, it means that the
      // button has been pressed, released and pressed again
      if (millis() - lastButtonPress > 50) {
        Serial.println("9");
        myservo.write(0);
        mode = 0;
      }
      // Remember last button press event
      lastButtonPress = millis();
    }
  }

  if (mode == 2) {
    digitalWrite(LED_BUILTIN, HIGH);
  }

  // run repeatedly when mode = 2
  while (mode == 2) {
    // get linear position from serial monitor
    
    while (Serial.available() > 0) {
      String selpos = Serial.readString();
      pos = selpos.toInt();
      if (pos == 0 || (pos >= 33 && pos <= 56)) {
        break;
      }
    }

    // exit auto mode if input is 0
    if (pos == 0) {
      myservo.write(0);
      mode = 0;
    }

    // calculate angular position of the servo, and turn it into the specified linear position
    if (pos >= 33 && pos <= 56.5) {
      pos = (pos - 33) * 7.6433;
      myservo.write(pos);
      // delay(10000);

      while (1) {
        // Read the button state
        int btnState = digitalRead(SW);
        // If we detect LOW signal, button is pressed and exit manual control mode
        if (btnState == LOW) {
          // Remember last button press event
          lastButtonPress = millis();
          delay(100);
          // if 50ms have passed since last LOW pulse, it means that the
          // button has been pressed, released and pressed again
          if (millis() - lastButtonPress > 50) {
            Serial.println("8");
            myservo.write(0);
            pos = 1;
            break;
          }
        }
      }
    }
  }  
}

Printing the 1 when mode = 2 is exactly what you wrote in the code.
The code
mode == 2
is a logical expression, it returns true if mode has value =2. The true is 1 in numerical context in the arduino.

What did you try to achieve in that line?
If you want to print the mode value, do something like

lcd.print(mode);

Yes, it does, then it should break the while loop there
But then the while (mode == 2) {...} statement does not execute when I input "2" in python for the second time (and so on)

your pos is integer, what the point to compare it with float value?

Try to add a debug print of the all strings, received from Serial

Also,

What is your encoding in python?

I think it is the default one, utf-8

Hello michael_yeung

I did a little code review that seems to have grown dynamically. I did not want to use the word spaghetti code.

And now?

Take a piece of paper, a pencil and design, based on the IPO model, a program structure plan before start coding.
Identify functions and their depencies. Now start with coding and testing, function by function.
At the end merge all tested function together to get the final sketch.
The basics for coding the project are to be found in the IDE as examples.

Have a nice day and enjoy coding in C++.

Hi @michael_yeung,

at a first glance I found this in your sketch:

lastButtonPress = millis();
          delay(100);
          // if 50ms have passed since last LOW pulse, it means that the
          // button has been pressed, released and pressed again
          if (millis() - lastButtonPress > 50) {
            // Serial.println("Retracted");
            Serial.println("8");
            myservo.write(0);
            pos = 1;
            break;
          }

The delay(100) after storing the time in msec makes sure that the following if condition is always true...

The detection of the buttonState has to be timely independent from checking the if condition.

Coding in Python and coding in C++ is usually quite different regarding the code structure. I would suggest not to use while(true) with break. I would recommend to use a break condition inside the while() brackets instead.

Hi @michael_yeung ,
Welcome to the forum..
Them while loops are beating you up..

Give this a try..

#include <Servo.h>
#include <LiquidCrystal_I2C.h>

#define CLK 2
#define DT 3
#define SW 4

int currentStateCLK;
int lastStateCLK;

Servo myservo;  // create servo object to control a servo
LiquidCrystal_I2C lcd(0x27, 16, 2);

int pos = 1;       // variable to store the servo position (In mm)
int manual_pos = 0;  // init manual position
int mode = 0;        // init mode
unsigned long lastButtonPress = 0;


void setup() {
  // Set encoder pins as inputs
  pinMode(CLK, INPUT);
  pinMode(DT, INPUT);
  pinMode(SW, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);

  lcd.init();
  lcd.backlight();

  // myservo.write() range from 0-180
  // the value controls the angular position of the servo
  Serial.begin(9600);
  Serial.println("Ready..");
  myservo.attach(9, 544, 2500);

  // initialise state of servo
  myservo.write(0);
}

void loop() {
  // get input for mode and pos
  digitalWrite(LED_BUILTIN, LOW);
  // Serial.println("mode?");
  if (Serial.available() > 0) {
    String sel = Serial.readString();
    int val = sel.toInt();
    if (val < 3) {
      mode = val;
    } else if (val >= 33 && val <= 56) {
      pos = val;
    } else {
      Serial.print("invalid value recieved:");
      Serial.println(val);
    }
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("mode:"); lcd.print(mode);
    lcd.setCursor(0, 1);
    lcd.print("pos:"); lcd.print(pos);
  }

  // Codes for manual control
  // run once when mode = 1
  if (mode == 1) {

    //lastStateCLK = digitalRead(CLK);
    digitalWrite(LED_BUILTIN, HIGH);

    // get the current state of CLK
    currentStateCLK = digitalRead(CLK);
    // If last and current state of CLK are different, then pulse occurred
    // React to only 1 state change to avoid double count
    if (currentStateCLK != lastStateCLK) {
      lastStateCLK = currentStateCLK; //remember..
      // If the DT state is different than the CLK state then
      // the encoder is rotating CW so increment
      byte data = digitalRead(DT);

      if (!data && currentStateCLK == LOW) {
        manual_pos++;
        if (manual_pos > 180)
          manual_pos = 180;
      } else if (data && currentStateCLK == LOW) {
        // Encoder is rotating CCW so decrement
        manual_pos--;
        if (manual_pos < 0)
          manual_pos = 0;
      }
    }

    // move the servo
    myservo.write(manual_pos);

    //check encoder button and exit to mode 0
    if (millis() - lastButtonPress > 50) {
      // Read the button state
      int btnState = digitalRead(SW);
      // If we detect LOW signal, button is pressed and exit manual control mode
      if (btnState == LOW) {
        // if 50ms have passed since last LOW pulse, it means that the
        // button has been pressed, released and pressed again
        Serial.println("9");
        myservo.write(0);
        mode = 0;
      }
      // Remember last button press event
      lastButtonPress = millis();
    }
  }




  if (mode == 2) {
    digitalWrite(LED_BUILTIN, HIGH);



    // exit auto mode if input is 0
    if (pos == 0) {
      myservo.write(0);
      mode = 0;
    }

    // calculate angular position of the servo, and turn it into the specified linear position
    if (pos >= 33 && pos <= 56) {
      pos = (pos - 33) * 7.6433;//what's this??
      myservo.write(pos);
    }

    // Read the button state
    if (millis() - lastButtonPress >= 50)
    {
      lastButtonPress = millis();
      int btnState = digitalRead(SW);
      // If we detect LOW signal, button is pressed and exit manual control mode
      if (btnState == LOW) {
        Serial.println("8");
        myservo.write(0);
        pos = 1;
        mode = 0;//added.. ~q
      }
    }
  }
}

simmed here..

have fun.. ~q

@qubits-us was quicker :wink:

Here is a version that runs on Wokwi and should do most of what you seem to achieve:

/*
  Forum: https://forum.arduino.cc/t/while-loop-not-executing-the-second-time/1159017 
  Wokwi: https://wokwi.com/projects/373323514455873537

*/

#include <Servo.h>
#include <LiquidCrystal_I2C.h>

#define CLK 2
#define DT 3
#define SW 4

int currentStateCLK;
int lastStateCLK;

Servo myservo;  // create servo object to control a servo
LiquidCrystal_I2C lcd(0x27, 16, 2);

float pos = 1;       // variable to store the servo position (In mm)
int manual_pos = 0;  // init manual position
int mode = 0;        // init mode

void setup() {
  // Set encoder pins as inputs
  pinMode(CLK, INPUT);
  pinMode(DT, INPUT);
  pinMode(SW, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);

  lcd.init();
  lcd.backlight();
  lcd.print("Start");
  // myservo.write() range from 0-180
  // the value controls the angular position of the servo
  Serial.begin(9600);
  Serial.println("Start");
  myservo.attach(9, 544, 2500);

  // initialise state of servo
  myservo.write(0);
  set_mode(0);
}

void loop() {
  stateMachine();
}



void stateMachine() {
  switch (mode) {
    case 0: // Get Input Mode
      getInputMode();
      break;
    case 1: // 1: Manual
      handleRotaryEncoder();
      if (buttonReleased()) {
        Serial.println("From Manual to Select Mode");
        myservo.write(0);
        set_mode(0);
      }
      break;
    case 2 :
      getPosData();
      break;
    case 3: // 2: Auto
      if (buttonReleased()) {
        Serial.println("From Auto to Select Mode");
        myservo.write(0);
        set_mode(0);
      }
      break;
    default:
      mode = 0;
      break;
  }
}

void getInputMode() {
  static char buf[4];
  static int count = 0;
  char c;
  digitalWrite(LED_BUILTIN, LOW);
  // Serial.println("mode?");
  if (Serial.available()) {
    c = Serial.read();
    if (c >= '0' && c <= '9') {
      buf[count] = c;
      count++;
      buf[count] = '\0'; // End of string array
    }
    if (c == '\n') {
      if (count > 0) {
        mode = buf[0] - 48;
        set_mode(mode);
      }
      count = 0;
    }
  }
  switch (mode) {
    case 1 :  // Prepare mode 1
      Serial.println("Switch to Manual Mode");
      manual_pos = 0;
      digitalWrite(LED_BUILTIN, HIGH);
      break;
    case 2 :  // Prepare mode 3
      Serial.println("Get Data for Auto Mode");
      lcd_print(0,1,"Input 33 .. 56",false);
      break;
    default: // Do Nothing
      break;

  }
}

void lcd_print(int x, int y, char * txt, bool clr) {
  if (clr) {
    lcd.clear();
  }
  lcd.setCursor(x, y);
  lcd.print(txt);
}

void lcd_print(int x, int y,  int pos) {
  char buff[8];
  sprintf(buff, "%3d", pos);
  lcd.setCursor(x, y);
  lcd.print(buff);
}

void set_mode(int newMode){
   mode = newMode;
   lcd_print(0, 0, "Mode: ", true);
   lcd_print(8, 0, mode);
}



void handleRotaryEncoder() {
  static int lastClk = HIGH;
  int newClk = digitalRead(CLK);
  int old_pos = manual_pos;
  if (newClk != lastClk) {
    // There was a change on the CLK pin
    lastClk = newClk;
    int dtValue = digitalRead(DT);
    if (newClk == LOW && dtValue == HIGH) {
      manual_pos++;
      if (manual_pos > 180) {
        manual_pos = 180;
      }
    }
    if (newClk == LOW && dtValue == LOW) {
      manual_pos--;
      if (manual_pos < 0) {
        manual_pos = 0;
      }
    }
  }
  if (manual_pos != old_pos) {
    myservo.write(manual_pos);
    old_pos = manual_pos;
    lcd_print(0, 1, "pos = ", false);
    lcd_print(6, 1, manual_pos);
  }
}


boolean buttonReleased() {
  static unsigned long lastButtonPress = 0;
  static boolean buttonPressed = true;
  static byte lastState   = HIGH;
  byte recentState = digitalRead(SW);
  if (recentState != lastState) {
    // Remember last button press event
    lastButtonPress = millis();
    lastState = recentState;
  }
  if (millis() - lastButtonPress > 30 && lastState != buttonPressed) {
    buttonPressed = lastState;
    if (buttonPressed) {
      return true;
    }
  } else {
    return false;
  }
}

void getPosData() {
  static char buf[4];
  static int count = 0;
  char c;
  if (Serial.available()) {
    c = Serial.read();
    if (c >= '0' && c <= '9') {
      buf[count] = c;
      count++;
      buf[count] = '\0'; // End of string array
    }
    if (c == '\n' || count > 2) {
      int v = atoi(buf);
      if (v >= 33 && v <= 56) {
        set_mode(3);
        lcd_print(0, 1, "NewPos =        ", false);
        lcd_print(9, 1, buf, false);
        pos = v;
       //pos = (pos - 33) * 7.6433;  ???
       myservo.write(pos);
      }
      if (v == 0) {
        myservo.write(0);
        set_mode(0);
      }; // Return to Input Mode
      count = 0;
    }
  }
}

  

See here for testing: https://wokwi.com/projects/373323514455873537

It is not using the calculation pos = (pos - 33) * 7.6433; but you can easily add it.

You can leave the modes by pressing and releasing the encoder switch. I like this way because it avoids to run into unwanted further keypress detections. I added also a further mode so that the position data input is a separate one. The "old" mode 2 is now mode 3.

I don't know if it is exactly what you wanted but maybe a good start ...

1 Like

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