Useless box servo problems

Hi

I'm constructing a useless box with the arduino Uno rev3 as microcontroller.

Everything works besides a very strange servo issue.

The lid opens and the arm turns off the switch and then retracts and closes the lid.
If you stress it enough it will retract the button inside the box.

It works for 4-10 cycles then suddenly the arm stays in the up position and nothing seems to happen
I have narrowed it down to the write method the the servo object.

So what happens is the lid opens the arm goes up and turns the switch off, then the code goes in to
the deactivate method and calls the write_arm method. in the write_arm method everything gets stuck on
arm.write(i);

It does not seem as this is a code issue on my behalf, could anyone have n idea on how to solve this?
I have tried with different servos, and on different output pins.

Code goes here:

#include <Servo.h> 
#include <MsTimer2.h>

/*
// speed 0=fast, speed 20=slow
//
// initial_delay
// lid_speed
// lid_delay
// arm_speed1
// arm_speed2
// arm_delay
 // 
*/
int levels[4][6] = {{1000,10,2000,20,20,200},{200,7,500,2,2,100},{1,1,50,1,1,1},{1,1,50,1,1,1}};

Servo arm;
Servo btn;  
Servo lid;
int switchPin = 8;
int lid_up = 80;
int lid_down = 150;
 int btn_up = 170;
int btn_down = 1;
int arm_up = 180;
int arm_down = 1;
int pmin = 1;
int pmax = 100;
int btn_state=LOW;
int counter = 0;
int level = 1;
unsigned long lastStateHigh = millis();
 unsigned int currentLidValue = pmin;
unsigned int currentArmValue = pmin;
unsigned int currentBtnValue = pmax;
int ignoreInitialDelay = 0;
int stressLevel = 0;
  
void setup() 
{ 
  arm.attach(9, 1000, 2300);
  btn.attach(10, 1000, 2500);
  lid.attach(11, 1000, 2500);
  Serial.begin(38400);

  //set default position
  arm.write(arm_down);
  lid.write(lid_down);
  btn.write(btn_up);
  pinMode(switchPin,INPUT);
   
  //start timer
  MsTimer2::set(50, checkBtn);
  MsTimer2::start();
  
} 

//Check for button state changes, and calculate current level  
void checkBtn() {
  int val1;
  int val2;
  do {
    val1 = digitalRead(switchPin);
    val2 = digitalRead(switchPin);
  }  while(val2!=val1);
  
  unsigned long ms = millis();
   if(val1 == HIGH && btn_state == LOW) {
    btn_state = HIGH;
	//the more you stress the machine, increase level faster
    if (ms-lastStateHigh<1000) {
      counter+=4;
    } else if(ms-lastStateHigh<2500) {
      counter+=2;
    } else if(ms-lastStateHigh<5000) {
       counter+=1;
    }
    lastStateHigh = ms;
  } else if(val1 == LOW && btn_state == HIGH) {
    btn_state = LOW; 
  } else {
    //no state change
  }
  //if no change for 30sek reset everything
  if(ms-lastStateHigh>30000) {
     level = 1;
    counter = 0;
    lastStateHigh = millis();
    currentLidValue = pmin;
    currentArmValue = pmin;
    currentBtnValue = pmax;
    stressLevel = 0;
  }
  if(counter>=16) {
     level+=1;
    if (level==5) {
      level = 4;
    }
    counter = 0;
  }

} 

//main loop, calls activate and deactivate arm
//also check if stresslevel is to high and lower the button   
void loop() 
{ 
  if (btn_state==HIGH) {  
    ignoreInitialDelay = activate_arm();
    if(ignoreInitialDelay==1 && level==4) {
       stressLevel +=1;
    }
    ignoreInitialDelay = deactivate_arm();
    if(ignoreInitialDelay==1 && level==4) {
      stressLevel +=1;
    }
    if(ignoreInitialDelay==0 && level==4) {
       stressLevel =0;
    }
  }
  if(stressLevel>=10) {
    stress();
  }
  
  delay(20);
}

int activate_arm() {
  
  int initial_delay = levels[level-1][0];
  int lid_speed = levels[level-1][1];
   int lid_delay = levels[level-1][2];
  int arm_speed1 = levels[level-1][3];
  int arm_speed2 = levels[level-1][4];
  int arm_delay = levels[level-1][5];
  
  Serial.println("activate: level: "+String(level) + " counter: " + String(counter));

  //delay before open the lid
  if(ignoreInitialDelay==0) {
    for(int i = 1;i<=initial_delay;i++) {
      delay(1);
      if(checkBtn==LOW) {return(1);}
    } 
  }
  
  //open lid
  if(currentLidValue<pmax) {
    for(int i = currentLidValue;i<=pmax;i++) {
       write_lid(i);
      delay(lid_speed);
      if(btn_state==LOW) {return(1);}
    }
  }
  
  //delay before arm is activated
  if(ignoreInitialDelay==0) {
    for(int i = 1;i<=lid_delay;i++) {
      delay(1);
      if(btn_state==LOW) {return(1);}
     }
  }
  
  //raise arm first 50%
  if(currentArmValue<pmax-50) {
    for(int i = currentArmValue;i<=pmax-50;i++) {
      write_arm(i);
      delay(arm_speed1);
      if(btn_state==LOW) {return(1);}
    }
  }

  //raise arm the last 50%
  if(currentArmValue<pmax) {
    for(int i = currentArmValue;i<=pmax;i++) {
      write_arm(i);
      delay(arm_speed2);
      if(btn_state==LOW) {break;}
    }
  }
  
  //delay before arm is lowered
  if(ignoreInitialDelay==0) {
     for(int i = 1;i<=arm_delay;i++) {
      delay(1);
    }
  }
  
  currentLidValue = pmax;
  return 0;
}

int deactivate_arm() {

  int initial_delay = levels[level-1][0];
  int lid_speed = levels[level-1][1];
  int lid_delay = levels[level-1][2];
  int arm_speed1 = levels[level-1][3];
  int arm_speed2 = levels[level-1][4];
  int arm_delay = levels[level-1][5];

  Serial.println("deactivate 1 level: "+String(level) + " counter: " + String(counter));

  //lower the arm first 50%
  if(currentArmValue>pmax-50) {
    for(int i=currentArmValue;i>=pmax-50;i--) {
      write_arm(i);
      delay(arm_speed1);
      if(btn_state==HIGH) {return(1);}
    }
  }

  //lower the arm the rest 50%
  if(currentArmValue>pmin) {
    for(int i=currentArmValue;i>=pmin;i--) {
      write_arm(i);
      delay(arm_speed2);
      if(btn_state==HIGH) {return(1);}
    }
  }
    
  //wait before closing the lid
  if(ignoreInitialDelay==0) {
    for(int i = 1;i<=lid_delay;i++) {
      delay(1);
      if(btn_state==HIGH) {return(1);}
    }
  }
   
  //close the lid  
  if(currentLidValue>pmin) {
     for(int i=currentLidValue;i>=pmin;i--) {
      write_lid(i);
      delay(lid_speed);
      if(btn_state==HIGH) {return(1);}
    }
  }
  
  //if a fully undisturbed deactivate is performed, increase level
  counter +=(8/level);
  currentArmValue = pmin;
  currentLidValue = pmin;
  return 0;
}

//writes the new arm value
void write_arm(int value) {
   int v = map(value,1,100,arm_down,arm_up);
   arm.write(v);
   currentArmValue = value;
}

//writes the new lid value
void write_lid(int value) {
  int v = map(value,100,1,lid_up,lid_down);
   lid.write(v);
  currentLidValue = value;
}

//write the new button value
void write_btn(int value) {
  int v = map(value,1,100,btn_down,btn_up);
  btn.write(v);
  currentBtnValue = value;
}

//to much stress, lower arm and close lid
//retract button and wait for 10sek, then raise the button
//and turn of the button if it is turned on
void stress() {
  Serial.println("stress");
   write_arm(pmin);
  delay(300);
  write_lid(pmin);
  write_btn(pmin);
  delay(10000);
  
  stressLevel = 0;
  for(int i = 1;i<pmax;i++) {
    write_btn(i);
    delay(20);
  }
  if(btn_state==HIGH) {
    level=4;
    activate_arm();
    deactivate_arm();
  }
  delay(300);
  
}

Kind Regards
/Danjel

Are you powering the servo from the Uno's 5V output? That will typically cause enough power fluctuation in the Uno to cause it to reset.

No, that would be insane. :slight_smile:

external power 6V, 1A.

I was thinking if putting some capacitators or diodes somewhere in the wiring could solve the issue.
Strangely it is only the arm servo that behaves this was. The lid servo and the button servo does not behave this way.

More strangely is if i force the button to onm while the arm is resting on it a few times, the mechanism could wake up again
and continue with the arm motion and deactivate it. So the arduino has not hang. It just waits for ??? nothing

Im thinking if it could have somthing with the interupts? or a bug in the servo.h library.

Kind Regards
/Danjel

danjeln:
No, that would be insane. :slight_smile:

.... but common. The tutorial is to blame since it instructs that way.

external power 6V, 1A.

When the fault happens, are all servos in action? Wisdom on the forum is to budget 1A per servo, so could it be that your 1A is exceeded?

The current never goes above 500mah

There is only one servo moving (or should be moved) at the time the error occurs.
Tonight i will try to write my own pwm control to operate the arm servo.

Any other suggestions are most welcome.

Kind Regards
/Danjel

danjeln:
The current never goes above 500mah

That would be mA 8)

I just realized that it could be the use of delay's in my code that causes the problem... Maybe i should read the millis() method instead and go from there?

Kind Regards
/Danjel

danjeln:
I just realized that it could be the use of delay's in my code that causes the problem... Maybe i should read the millis() method instead and go from there?

Kind Regards
/Danjel

There could easily be a timing issue where you expect to be in one state and are in another instead. I would definitely switch to the blink w/o delay setup, as the delay() function is the GOTO of the modern microcontroller (And bad practice).

Hi

I don't think i'm in an unexpected state, i have traced the code with Serial.println'n to the servo's write method in write_arm method to return control to the loop, but it doesn't.

It works as intended 9 out of 10 times, but on the tenth time the output writes "writing position to servo" and then nothing more happens.

i.e.

void write_arm(int value) {
int v = map(value,1,100,arm_up,arm_down);
Serial.println("writing position to servo");
arm.write(v);
Serial.println("Done writing position to servo");
currentArmPosition = value;
}

Strangly, if i force the button to on again, forcing the servo arm away from the button then it could regain control again, and continue.

In most cases i would way this looks and sounds as a programming error,but it doesn't seem so in this case. Please have a look at the code posted first in the thread above.

Kind Regards

Do you mean it fails on exactly the 10th time? And if not exactly the 10th time does it always fail after the same number of repeats? How many?

Search through your code for something that happens after that many repeats - something that accumulates or a value that overflows?

Have you some code that is using up all your RAM?

Is it possible the servo is jamming on something, drawing too much current and causing the Arduino to brown-out?

Can you run the program with the servo removed from its fitting so it moves but does nothing and isn't facing any load?

...R

PS, I've had a look at your code and it seems that you are expecting and generating return values from functions that aren't defined to have return values. I don't know enough about C++ to know what happens but I can imagine the stack getting screwed up. I thought the compiler would object. And, of course I may be misreading your code.

...R

Have you run it with the Serial debugging in place but no servo connected?

If it still fails it isn't a hardware issue, if it then works its hardware...

I've had another look at your code - I'm just putting off doing something useful :slight_smile:

I've also looked at a short youtube video to see what a useless box is. From what I saw when you push a toggle switch the lid opens and an arm pushes the switch off again.

If that's what your code does I can't see why it needs 290 lines of code - 50 or 60 seems more reasonable, and less likely to go wrong?

...R

PS ...
I've just realized that you are using MStimer2 to call your checkBtn() function every 50msecs. That's just crazy - it probably gets called at all sorts of inconvenient times and screws up the stack and such. There is no need for an interrupt approach. Loop() can easily do all you want.

...R

Dear all

I got rid of the mstimer2, it was indeed unneccessary. However that was not the problem.
It was... ...looking and smelling like programmatic error.... and it was.

Once i detected the error everythng workes like a charm.

Thank you for the support and time.

Kind Regards
/Danjel

Do tell ...

...R

Well, when the button was turned off by the arm. There was a delay sensing the button state. This delay caused the program to think the button was still on, so in the deactivation of the arm it returned to the main loop but at the time it got back to the main loop the button state had changed, causing the activation and deactivation of the arm not to be runned anymore.

Easy fix was to store the position or the state of the box in a new variable. And continue with the activation and deactivation as long as the state was working. Regardless of the button state

Again thanks for the support. I will post the working code tomorrow

Kind regards
/danjel

Here is a short clip i made of the useless machine.

And here is the working code:

#include <Servo.h> 

/*
// speed 0=fast, speed 20=slow
//
// initial_delay
// lid_speed
// lid_delay
// arm_speed1
// arm_speed2
// arm_delay
// 
*/
unsigned int levels[4][6] = {{1000,10,2000,20,20,200},{200,7,500,2,2,100},{1,1,50,1,1,1},{1,1,50,1,1,1}};

Servo arm;
Servo btn;  
Servo lid;
int switchPin = 8;
int lid_up = 80;
int lid_down = 160;
int btn_up = 170;
int btn_down = 1;
int arm_up = 180;
int arm_down = 1;
int pmin = 1;
int pmax = 100;
int btn_state=LOW;
int counter = 0;
int level = 1;
unsigned long lastStateHigh = millis();
unsigned int currentLidValue = pmin;
unsigned int currentArmValue = pmin;
unsigned int currentBtnValue = pmax;
int ignoreInitialDelay = 0;
int stressLevel = 0;
int armActivated = 0;
  
void setup() 
{ 
  Serial.begin(38400);
  arm.attach(9, 1000, 2300);
  btn.attach(10, 1000, 2300);
  lid.attach(11, 1000, 2500);
  Serial.begin(38400);

  arm.write(arm_down);
  lid.write(lid_down);
  btn.write(btn_up);
  pinMode(switchPin,INPUT);
} 
  
void checkBtn() {
  int val1;
  int val2;
  val1 = digitalRead(switchPin);
  unsigned long ms = millis();
  if(val1 == HIGH && btn_state == LOW) {
    btn_state = HIGH;
    if (ms-lastStateHigh<1000) {
      counter+=4;
    } else if(ms-lastStateHigh<2500) {
      counter+=2;
    } else if(ms-lastStateHigh<5000) {
      counter+=1;
    }
    lastStateHigh = ms;
  } else if(val1 == LOW && btn_state == HIGH) {
    btn_state = LOW; 
  } else {
    //no state change
  }
  if(ms-lastStateHigh>30000) {
    level = 1;
    counter = 0;
    lastStateHigh = millis();
    currentLidValue = pmin;
    currentArmValue = pmin;
    currentBtnValue = pmax;
    stressLevel = 0;
  }
  if(counter>=16) {
    level+=1;
    if (level==5) {
      level = 4;
    }
    counter = 0;
  }

} 

  
void loop() 
{ 
  if (btn_state==HIGH || armActivated==1) {  
    ignoreInitialDelay = activate_arm();
    if(ignoreInitialDelay==1 && level==4) {
      stressLevel +=1;
    }
    ignoreInitialDelay = deactivate_arm();
    if(ignoreInitialDelay==1 && level==4) {
      stressLevel +=1;
    }
    if(ignoreInitialDelay==0 && level==4) {
      stressLevel =0;
    }
  }
  if(stressLevel>=14) {
    stress();
  }
  
  sleep(20);
}

int activate_arm() {
  Serial.println("reading level values 1");
  int initial_delay = levels[level-1][0];
  int lid_speed = levels[level-1][1];
  int lid_delay = levels[level-1][2];
  int arm_speed1 = levels[level-1][3];
  int arm_speed2 = levels[level-1][4];
  int arm_delay = levels[level-1][5];
  
  Serial.println("activate: level: "+String(level) + " counter: " + String(counter));
  
  if(ignoreInitialDelay==0) {
    for(int i = 1;i<=initial_delay;i++) {
      sleep(1);
      if(checkBtn==LOW) {return(1);}
    } 
  }
  
  if(currentLidValue<pmax) {
    for(int i = currentLidValue;i<=pmax;i++) {
      write_lid(i);
      sleep(lid_speed);
      if(btn_state==LOW) {return(1);}
    }
  }
  
  if(ignoreInitialDelay==0) {
    for(int i = 1;i<=lid_delay;i++) {
      sleep(1);
      if(btn_state==LOW) {return(1);}
    }
  }
  
  if(currentArmValue<pmax-50) {
    for(int i = currentArmValue;i<=pmax-50;i++) {
      write_arm(i);
      sleep(arm_speed1);
      if(btn_state==LOW) {return(1);}
    }
  }
  
  if(currentArmValue<pmax) {
    for(int i = currentArmValue;i<=pmax;i++) {
      write_arm(i);
      sleep(arm_speed2);
      if(btn_state==LOW) {break;}
    }
  }
  
  if(ignoreInitialDelay==0) {
    for(int i = 1;i<=arm_delay;i++) {
      sleep(1);
    }
  }
  
  armActivated = 1;
  currentLidValue = pmax;
  return 0;
}

int deactivate_arm() {

    Serial.println("reading level values 2");
  int initial_delay = levels[level-1][0];
  int lid_speed = levels[level-1][1];
  int lid_delay = levels[level-1][2];
  int arm_speed1 = levels[level-1][3];
  int arm_speed2 = levels[level-1][4];
  int arm_delay = levels[level-1][5];

  Serial.println("deactivate 1 level: "+String(level) + " counter: " + String(counter));

  if(currentArmValue>pmin+50) {
    for(int i=currentArmValue;i>=pmin+50;i--) {
      write_arm(i);
      sleep(arm_speed1);
      if(btn_state==HIGH) {return(1);}
    }
  }

  Serial.println("deactivate 2");

  if(currentArmValue>pmin) {
    for(int i=currentArmValue;i>=pmin;i--) {
      write_arm(i);
      sleep(arm_speed2);
      if(btn_state==HIGH) {return(1);}
    }
  }
    Serial.println("deactivate 3");

  if(ignoreInitialDelay==0) {
    for(int i = 1;i<=lid_delay;i++) {
      sleep(1);
      if(btn_state==HIGH) {return(1);}
    }
  }
   Serial.println("deactivate 4");
  
  if(currentLidValue>pmin) {
    for(int i=currentLidValue;i>=pmin;i--) {
      write_lid(i);
      sleep(lid_speed);
      if(btn_state==HIGH) {return(1);}
    }
  }
  
  counter +=(10-level);
  currentArmValue = pmin;
  currentLidValue = pmin;
  armActivated = 0;
  return 0;
}

void write_arm(int value) {
   int v = map(value,1,100,arm_down,arm_up);
   arm.write(v);
   currentArmValue = value;
}

void write_lid(int value) {
  int v = map(value,100,1,lid_up,lid_down);
  lid.write(v);
  currentLidValue = value;
}

void write_btn(int value) {
  int v = map(value,1,100,btn_down,btn_up);
  btn.write(v);
  currentBtnValue = value;
}

void stress() {
  Serial.println("stress");
  write_arm(pmin);
  sleep(300);
  write_lid(pmin);
  write_btn(pmin);
  sleep(10000);
  
  stressLevel = 0;
  for(int i = 1;i<pmax;i++) {
    write_btn(i);
    sleep(20);
  }
  if(btn_state==HIGH) {
    level=4;
    activate_arm();
    deactivate_arm();
  }
  sleep(300);
}

void sleep(unsigned long milliseconds) {
  unsigned long ms = millis();
  unsigned long l = 0;
  while(millis()<milliseconds+ms) {
    l++;
    if((l % 100)==1) {
      checkBtn();
    }
    //waiting
  }
}

Kind Regards
/Danjel

I love that, totally brilliant.

You are better mannered than I am, clearly. My annoyed machine would not simply retract the switch, it would spray you with pepper spray or something. Maybe crack a stink bomb.....

Excellent work!

mirith:
the delay() function is the GOTO of the modern microcontroller (And bad practice).

I like that.... must remember it

I have taken the liberty of tweeting a link to that clip from my Twitter handle JimDuino and @-ed arduino

I love your woodwork and the fact that the switch retracts.

...R