[Animatronic] Trying to establish servo cycle code using millis()

Hello. I am a beginner with Arduino and I am working on an animatronic project. I have no prior experience with Arduino or C++ (or any coding language for that matter), and I have just learned the basics of C++ by watching a full beginner's course. I also did some additional research concerning initialization of variables so I know what variable types to use in what situation.

My project is a gorilla animatronic with three points of movement - a mouth, a right arm, and a left arm. The mouth (as you would guess) opens and closes, and the right arm waves. The left arm is of least priority right now since it is a continuous rotation "servo." I am currently only concerned with the mouth and right arm servos moving properly. There is a prerecorded speech that the animatronic is going to follow, so I am trying to get the mouth servo to match it approximately.

The physical setup consists of a "skull" that holds the mouth servo and an extension that holds the arm servos. Here is a video of basic movement of the assembly:
Arduino Animatronic

I have come from a previous post in which I admittedly foolishly attempted to get a code to work without understanding the language: I need a code please - Programming Questions - Arduino Forum Sorry to all those involved in that heck show.

Based on what I've learned, I've regrouped and put together my best attempt at a code aimed at completing my objective. My objective is as follows:

As soon as the code starts:

RightArmServo performs one up/down cycle (wave) <-- at the same time
mouthServo performs one up/down cycle (saying "Hi") <-- at the same time

500 millisecond pause (resembles pause in speech after saying "Hi")

mouthServo loops up/down movement for 3 seconds (next sentence in speech)

500 millisecond pause (pause)

mouthServo loops up/down movement for 3 seconds (next sentence)

(this will repeat another 4 times or so)

I have commented throughout the code to draw attention to what I am trying to do within the code in order to reach my objective.

#include <Servo.h>

Servo mouthServo;
Servo rightArmServo;
Servo leftArmServo;
    
unsigned long currentTime; //to keep track of the time
const unsigned long mouthServoPeriod = 3000; //marks the period that the mouthServo will start operating on loopMoveMouth()
const unsigned long mouthServoPeriodTwo = 500; //pause period that mouthServo will start using in loopMoveMouth()

const byte mouthMinAngle = 70; //mouth is closed
const byte mouthMaxAngle = 130; //mouth is open
boolean mouthMoving = true;
int mouthIncrement = 1;
unsigned long mouthStepStart;
unsigned long mouthStepPeriod = 1;
byte mouthCurrentAngle = mouthMinAngle;

const byte rightArmMinAngle = 0; //right arm is down
const byte rightArmMaxAngle = 90; //right arm is up
boolean rightArmMoving = true; 
int rightArmIncrement = 1;  
unsigned long rightArmStepStart;
unsigned long rightArmStepPeriod = 1;
byte rightArmCurrentAngle = rightArmMinAngle;

int currentPosition = 0;
int mouthCount; //number of times mouthServo reaches min or max angle; will switch to loopMoveMouth once mouthCount = 2
int rightArmCount; //number of times rightArmServo reaches min or max angle; rightArmMoving = false once mouthCount = 2

void setup()
{
  Serial.begin(100000);
  leftArmServo.attach(3);
  mouthServo.attach(4);
  rightArmServo.attach(5);
  mouthServo.write(mouthMinAngle); //start with mouth closed
  rightArmServo.write(rightArmMinAngle); //start with right arm down
}

void moveMouth() //controls mouth up/down cycle; will switch to loopMoveMouth() once mouthCount = 2
{
  currentTime = millis();

  if (currentTime -  mouthStepStart >=  mouthStepPeriod)
  {
   mouthCurrentAngle +=  mouthIncrement;

  if ( mouthCurrentAngle <=  mouthMinAngle ||  mouthCurrentAngle >=  mouthMaxAngle)  //still in range?
  {
   mouthCount++;
   mouthIncrement *= -1; //change the increment direction if out of range
  }
   mouthServo.write(mouthCurrentAngle);
   mouthStepStart = currentTime;
  }
}

void moveRightArm() //controls right arm up/down cycle, rightArmMoving = false once mouthCount = 2
{
  currentTime = millis();

  if (currentTime -  rightArmStepStart >=  rightArmStepPeriod)
  {
   rightArmCurrentAngle +=  rightArmIncrement;

  if ( rightArmCurrentAngle <=  rightArmMinAngle ||  rightArmCurrentAngle >=  rightArmMaxAngle)  //still in range?
  {
   rightArmCount++;
   rightArmIncrement *= -1; //change the increment direction if out of range
  }
   rightArmServo.write(rightArmCurrentAngle);
   rightArmStepStart = currentTime;
  }
}

void loopMoveMouth()
{
  currentTime = millis();

  if (currentTime >= mouthServoPeriod) //mouth will begin to cycle up/down on loop after 3 seconds have passed
  {
    moveMouth();
  }
}

void loop()
{
  currentTime = millis();

  if(mouthCount <= 2) //mouth will perform one up/down cycle
  {
    boolean mouthMoving = true;
  }
  else //mouth movement will be controlled by loopMoveMouth if mouthCount > 2 so that its up/down cycle loops
  {
    loopMoveMouth();
  }

  if(rightArmCount <= 2) //right arm will perform one up/down cycle
  {
    boolean rightArmMoving = true;
  }
  else //right arm movement will cease once mouthCount > 2
  {
    boolean rightArmMoving = false;
  }
}

When I run the code as is, the mouth and right arm servos seem to lock in place at their initialized min positions. I assume this means there are conflicting statements within the code. Other than not knowing how to incorporate mouthServoPeriodTwo, being the 500 ms pause intervals that come into play after the initial single up/down cycle of mouthServo and rightArmServo, I was able to account for everything I needed in the code. I have written this code to the best of my understanding of the language and I sincerely hope you guys can help me get it working. Consider this code a proof of concept, as I am not fluent in the inner workings of the language and hence seek guidance. Thanks in advance.

I'm a co-OP of this thread because this is my partner, in case anyone gets confused if I post inside info or ask questions specific to this project.

Start at the top of loop(). Imagine this is the very first loop and everything is at its starting value from setup().

Is the mouthCount less than or equal to 2? Yes. So mouthMoving is set to true and we do NOT run the code in else { }.

Now ignoring the right arm, what happens next? How can mouthMoving change?

@Morgan S

Thank you for your reply.

Start at the top of loop(). Imagine this is the very first loop and everything is at its starting value from setup().

Is the mouthCount less than or equal to 2? Yes. So mouthMoving is set to true and we do NOT run the code in else { }.

Now ignoring the right arm, what happens next? How can mouthMoving change?

Looking at loop, once mouthCount is greater than 2, else{} comes into play, causing loopMoveMouth() to run. Or at least this is what I am trying to make happen in my code, and I am not entirely sure if I've done it correctly.

Yes but there is nothing that changes the value of mouthCount.

Yes but there is nothing that changes the value of mouthCount.

Ah. I was under the impression that including mouthCount++ would increase its value by 1 each time mouthServo reaches its min or max angle. Here is the part of the code where I attempt to dictate this:

if ( mouthCurrentAngle <=  mouthMinAngle ||  mouthCurrentAngle >=  mouthMaxAngle)  //still in range?
  {
   mouthCount++;
   mouthIncrement *= -1; //change the increment direction if out of range
  }

Is there something specific I need to include for mouthCount to increase?

Yes, but you never call that function.

I realized I did not assign mouthCount a value, so I initialized mouthCount = 0.

I was also looking in loop() and realized that I never called to the mouthMove function, but rather just used boolean mouthMoving = true. I changed this to call moveMouth.

if(mouthCount <= 2) //mouth will perform one up/down cycle
  {
    moveMouth();
  }
  else //mouth movement will be controlled by loopMoveMouth if mouthCount > 2 so that its up/down cycle loops
  {
    loopMoveMouth();
  }

eguirguis2005:
I realized I did not assign mouthCount a value, so I initialized mouthCount = 0.

But because mouthCount is a global, and you didn't give it an explicit value, the C runtime helpfully set it to zero anyway.

MorganS:
Yes, but you never call that function.

What do you mean by that?

eguirguis2005:
I was also looking in loop() and realized that I never called to the mouthMove function,

That's what I meant. See reply #2.

MorganS:
That's what I meant. See reply #2.

We need a code fast

@Morgan S

So are you saying I need to put mouthCount in loop()? I currently have it set as the if() statement condition, but I assume you mean I need to include mouthCount++ in order to change the value of mouthCount. I currently have mouthCount++ within the individual servo functions. If mouthCount++ needs to be included in loop(), where would I put it? Here is loop() currently:

void loop()
{
  currentTime = millis();

  if(mouthCount <= 2) //mouth will perform one up/down cycle
  {
    moveMouth();
  }
  else //mouth movement will be controlled by loopMoveMouth if mouthCount > 2 so that its up/down cycle loops
  {
    loopMoveMouth();
  }

  if(rightArmCount <= 2) //right arm will perform one up/down cycle
  {
    moveRightArm();
  }
  else //right arm movement will cease once mouthCount > 2
  {
    boolean rightArmMoving = false;
  }
}

That version of loop() looks like it will do the trick. What happened when you tried it? What do you want it to do different?

  else //right arm movement will cease once mouthCount > 2

Your comment is wrong.

That version of loop() looks like it will do the trick. What happened when you tried it?

Upon uploading the code, it caused mouthServo and rightArmServo to start out of range. RightArmServo started past its max angle and went down and up again, then ceased motion. Then, mouthServo started moving back and forth very short distances extremely fast.

What do you want it to do different?

I am trying to get mouthServo and rightArmServo to begin at their min angle positions, which I established in setup(). I then want mouthServo and rightArmServo to move to their max positions and back down to their min positions. At this point, since mouthCount = 2, mouthServo will begin moving back and forth from its min to max positions. Meanwhile, since rightArmCount = 2, rightArmServo motion will cease.

Your comment is wrong.

You're right. That's supposed to say "right arm movement will cease once rightArmCount > 2"

So are mouthCount and rightArmCount being correctly tracked? Are mouthCount++ and rightArmCount++ increasing their values by being included within their respective individual functions, being moveMouth() and moveRightArm()? Or is there a certain place they need to be within loop()?

Please post your entire current code.

Here is the entire code

#include <Servo.h>

Servo mouthServo;
Servo rightArmServo;
Servo leftArmServo;
    
unsigned long currentTime; //to keep track of the time
const unsigned long mouthServoPeriod = 3000; //marks the period that the mouthServo will start operating on loopMoveMouth()

const byte mouthMinAngle = 70; //mouth is closed
const byte mouthMaxAngle = 130; //mouth is open
boolean mouthMoving = true;
int mouthIncrement = 1;
unsigned long mouthStepStart;
unsigned long mouthStepPeriod = 1;
byte mouthCurrentAngle = mouthMinAngle;

const byte rightArmMinAngle = 0; //right arm is down
const byte rightArmMaxAngle = 90; //right arm is up
boolean rightArmMoving = true; 
int rightArmIncrement = 1;  
unsigned long rightArmStepStart;
unsigned long rightArmStepPeriod = 1;
byte rightArmCurrentAngle = rightArmMinAngle;

int currentPosition = 0;
int mouthCount = 0; //number of times mouthServo reaches min or max angle; will switch to loopMoveMouth once mouthCount = 2
int rightArmCount = 0; //number of times rightArmServo reaches min or max angle; rightArmMoving = false once mouthCount = 2

void setup()
{
  Serial.begin(100000);
  leftArmServo.attach(3);
  mouthServo.attach(4);
  rightArmServo.attach(5);
  mouthServo.write(mouthMinAngle); //start with mouth closed
  rightArmServo.write(rightArmMinAngle); //start with right arm down
}

void moveMouth() //controls mouth up/down cycle; will switch to loopMoveMouth() once mouthCount = 2
{
  currentTime = millis();

  if (currentTime -  mouthStepStart >=  mouthStepPeriod)
  {
   mouthCurrentAngle +=  mouthIncrement;

  if ( mouthCurrentAngle <=  mouthMinAngle ||  mouthCurrentAngle >=  mouthMaxAngle)  //still in range?
  {
   mouthCount++;
   mouthIncrement *= -1; //change the increment direction if out of range
  }
   mouthServo.write(mouthCurrentAngle);
   mouthStepStart = currentTime;
  }
}

void moveRightArm() //controls right arm up/down cycle, rightArmMoving = false once mouthCount = 2
{
  currentTime = millis();

  if (currentTime -  rightArmStepStart >=  rightArmStepPeriod)
  {
   rightArmCurrentAngle +=  rightArmIncrement;

  if ( rightArmCurrentAngle <=  rightArmMinAngle ||  rightArmCurrentAngle >=  rightArmMaxAngle)  //still in range?
  {
   rightArmCount++;
   rightArmIncrement *= -1; //change the increment direction if out of range
  }
   rightArmServo.write(rightArmCurrentAngle);
   rightArmStepStart = currentTime;
  }
}

void loopMoveMouth()
{
  currentTime = millis();

  if (currentTime >= mouthServoPeriod) //mouth will begin to cycle up/down on loop after 3 seconds have passed
  {
    moveMouth();
  }
}

void loop()
{
  currentTime = millis();

  if(mouthCount <= 2) //mouth will perform one up/down cycle
  {
    moveMouth();
  }
  else //mouth movement will be controlled by loopMoveMouth if mouthCount > 2 so that its up/down cycle loops
  {
    loopMoveMouth();
  }

  if(rightArmCount <= 2) //right arm will perform one up/down cycle
  {
    moveRightArm();
  }
  else //right arm movement will cease once rightArmCount > 2
  {
    boolean rightArmMoving = false;
  }
}
 else //right arm movement will cease once rightArmCount > 2
  {
    boolean rightArmMoving = false;

What's the point of a variable that's going out of scope in a few hundred nanoseconds?

What's the point of a variable that's going out of scope in a few hundred nanoseconds?

I'm not sure what you mean. Are you saying the else{} clause is unneeded because rightArmServo will stop automatically after rightArmCount > 2?

Or if you mean the boolean, it was to my understanding that setting it to false is what stops the servo from moving. Did I get it wrong?

No, I'm saying you already have a global called "rightArmMoving", but you decided to pointlessly create a new one.