Weird Issue playing sound in a very specific situation

Hello everyone, my first post here. A group of friends have been building our own Halloween Props for some time, and in the last three years, we've been using Arduinos to build better stuff.

We built a prop we call the "Screaming Skull". We have a two button remote control powering two separate relays to active the Arduino to control the sequence of events. (We connected these relays to Pin 2 and Pin 3 (Example, if I press button A, the arduino sends a command to my sound board to play a door-creaking sound while opening the doors using servos. If I press the A button again, the arduino sends a command to my sound board to play a different door creaking sound and closes the doors. If I press the B button, it checks to see if the doors are opened, if they are not, it opens them and then it launches the skull out via a long air cylinder. Then, when we press the B button again, it retracts the skull, and closes the doors.) So all of that is actually working.

The issue is this: When we use the B button to launch the skull, if we wait for more than a second to press the B button again to bring the skull back, no sound will play when closing the doors. This is not a problem if we just use the A button to open and close the doors no matter how long we wait to press it again to close the doors.
If we launch the skull and then immediately press the B button again to retract the skull, the closing door sound will play.

I don't know what could potentially be causing the closing door sound to not play. I hope I am not confusing anyone and perhaps someone can tell me if my code needs to be changed in some way to prevent that from happening. I am really an amateur when it comes to Arduino programming so maybe I need to restructure my code in some way.

I included a picture I took of all the components except the servos for the doors I took before installing it in our prop.

/*  Screaming Skull Code version 3.0  by Luis Melo
This code works with a remote control with two buttons.
Button 1 is used to open and close the doors independently
Button 2 is used to lauch the skull
 v1.0 - Initial code
 v2.0 - Changed the code to pulse voltage to actuators since low voltage made them weak and did not work
 v3.0 - Linear actuators are not really going to work so we have changed them with Servo Motors and the code was
        changed to accomodate that
        Also, the eyes will now light up immediately upon opening the doors instead of waiting for the second button to be pressed.
 */

#include <SoftwareSerial.h>
#include <Adafruit_Soundboard.h>
#include <Servo.h>

#define SFX_TX 12
#define SFX_RX 4
#define SFX_RST 13

SoftwareSerial ss = SoftwareSerial(SFX_TX, SFX_RX);
Adafruit_Soundboard sfx = Adafruit_Soundboard(&ss, NULL, SFX_RST);

Servo leftdoor;  // create servo object to control left door
Servo rightdoor;  // create servo object to control right door

int ACTIVATE_DOORS = 2; // This pin will be an input
int ACTIVATE_SKULL = 3; // This pin will be an input
int LIGHTEYES = 7;
int LAUNCHSKULL = 8;
int DOORSPEED = 25; // The lower the number, the faster doors close
int door_action = LOW;
int doors_state = 0;
int skull_action = 0;
int skull_state = 0;
int check_doors = 0;
int leftdoorpos = 180; // variable to store servo 1 position
int rightdoorpos = 0; // variable to store servo 2 position
int skulleyestimer;
int millisnow;
int i;
int ii;

void setup(){
  Serial.begin(9600); // open the serial port at 9600 baud for debugging
  ss.begin(9600);  // used to communicate with the sound board
  pinMode(ACTIVATE_DOORS, INPUT_PULLUP); // sets the pin to input using the built-in resistor so it is HIGH by default
  pinMode(ACTIVATE_SKULL, INPUT_PULLUP);  // sets the pin to input using the built-in resistor so it is HIGH by default
  pinMode(LIGHTEYES, OUTPUT);
  pinMode(LAUNCHSKULL, OUTPUT);
  leftdoor.attach(5);  // attaches the left door servo on pin 5 to the servo object
  rightdoor.attach(6);  // attaches the right door servo on pin 6 to the servo object
  leftdoor.write(leftdoorpos);  // Close the left door if it is opened
  rightdoor.write(rightdoorpos);  // Close the right door if it is opened
}

// the loop function runs over and over again forever
void loop(){
  door_action = digitalRead(ACTIVATE_DOORS); // See if the doors need to be activated by checking if the button is pushed
  if (door_action == LOW){ // If the button is pushed, activate doors
    if (skull_state == 0){ // Check that the skull is not out before proceeding. If it is out, do not activate the doors
      ActivateDoors(); // Activate Doors
    }
  }
  skull_action = digitalRead(ACTIVATE_SKULL); // Check if the skull needs to be launched
  if (skull_action  == LOW){
    if (skull_state == 1){ //if skull is out, return it
      digitalWrite(LAUNCHSKULL, LOW);
      delay(4000);
      ActivateDoors();
      skull_state = 0;
    }else{
      if (doors_state == 0){
        ActivateDoors();
        digitalWrite(LIGHTEYES, HIGH);
        delay(2000);
      }
      sfx.playTrack("SCREAM  OGG");
      digitalWrite(LAUNCHSKULL, HIGH);
      digitalWrite(LIGHTEYES, LOW);
      delay(2000);
      skull_state = 1;
    }
  }
  millisnow = millis();
  if (millisnow > skulleyestimer){
    digitalWrite(LIGHTEYES, LOW);
  }
}

void ActivateDoors(){
  if (doors_state == 0){
    sfx.playTrack("DOOROPENOGG");
    ii = 0;
    for (int i = leftdoorpos; i >= rightdoorpos; i -= 1){
      ii = ii + 1;
      leftdoor.write(i);
      rightdoor.write(ii);
      Serial.print("Left Door: ");
      Serial.print(i);
      Serial.print(" - Right Door: ");
      Serial.print(ii);
      Serial.println();
      delay(DOORSPEED);
    }
    doors_state = 1;
    skulleyestimer = millis() + 10000;
    digitalWrite(LIGHTEYES, HIGH);
  }else{
    sfx.playTrack("DOORCLSEOGG");
    digitalWrite(LIGHTEYES, LOW);
    for (int i = rightdoorpos; i <= leftdoorpos; i += 1){
      ii = leftdoorpos - i;
      leftdoor.write(i);
      rightdoor.write(ii);
      Serial.print("Left Door: ");
      Serial.print(i);
      Serial.print(" - Right Door: ");
      Serial.print(ii);
      Serial.println();
      delay(DOORSPEED);
    }
    doors_state = 0;
  }
}

Welcome to the forum

Please do us and yourself a favour by breaking up your description into paragraphs rather than a slab of text. This will make the description easier to read and understand

Thank you for your suggestion. I have changed my post so that I hope it is a little more readable now.

Forum members need to see a wiring diagram. Hand drawn is fine, with pins and connections clearly labeled.

Also please post links to the product pages or data sheets all the modules used in the circuit.

I understand that, but in this case, since the only problem is that the command "sfx.playTrack("DOORCLSEOGG");" in the ActivateDoors function does not seem to be sent to my sound board if we wait too long to press the B button, it seems that this is a coding issue since other than the sound not playing, everything else is working perfectly fine. I don't have a diagram but I'll upload a picture of the finished product.

I don't see a button library so your button code will be subject to bounce errors.
The code would make more sense as a state machine as well.

The buttons are remote controlled relays that feed signal to pins 2 and 3 respectively.

Real quick, variables that compare lapsed milliseconds to the millis() function should of data type unsigned long

The reason for that is int will overflow quickly on AVR based boards, such as the Arduino Nano. The size of int on AVR based boards is only -32,768 to 32,767.

and you are using it incorrectly. You need to note the time when the eyes are turned on and then compare the elapsed time by using millis() - startTime >= duration

unsigned long millisnow, skulleyetimer;
...

  millisnow = millis();
  if ( millisnow - skulleyetimer >= 10000 ) {
    digitalWrite(LIGHTEYES, LOW);
  }
...

void ActiveDoors() {

  skulleyetimer = millis(); ' start of eyes on

Oh, I had not thought about that. What is weird, is that this function is actually working as intended and the system will turn the eyes off after the ten seconds have passed, BUT, if this is overflowing, that may explain the weird behavior with the sound not playing on that specific situation. Maybe this is overflowing it and that's why the command is not properly sent to my sound board when we wait too long. I'll try fixing that this evening and run a test.

See @blh64 's input in post #9 for the more complete solution to correctly using millis().

Also, in general, if you are going to use delay(x);, such as here:

understand that it is blocking code, which is suitable if blocking out everything else for that duration is what you want. Just understand that can plague you down the road if you make changes or additions to your code.

It is, in general, preferable to use many instances of millis() comparisons to variables you create to avoid blocking code, and, as @sonofcy suggested, to structure the whole code as a state machine or any number of consecutive or concurrent state machines if you hope to make more complex projects in the future.

What I mean is, it may not be needed for this project, and with 25 days and counting to the big event, I'm sure you have enough left to do in your haunt; however, as an exercise for the future, maybe come November 2nd, try rewriting your code as a state machine because to do so will unlock new powers to create increasingly more complex projects.

My boilerplate advice in ^ regard is to study the Arduino IDE example sketches "StateChangeDetection" and "switchCase" (as opposed to if/else).

The best way to think about state machines is, in my opinion (especially as a fellow Hallowe'en haunter, where Arduino props such as ours really are just a type of parlor/arcade game), is to consider how state machines are used in Nintendo games.

Once again (I post this link a lot in the forum), here is a great explanation of what state machines can do for projects like these.

For those about to Haunt, I salute you! :saluting_face:

It is doubtful this is the cause of your problem. milis() rolls every ~49 days and I doubt you have been running it that long. It is really just a problem with long running sketches, but it is always best to do things properly.

@blh64 Isn't an int on AVR (-32768 to 32,767) only good for about 30 seconds?

I would also note that you never actually use Serial for debugging in your sketch. Why not add

Serial.println(F("play track x"));

right before calling the function to play a track and then

Serial.println(F("track x finished"));

as soon as it has supposedly stopped, just to try to isolate the issue to a specific part of code.

In fact, it's not a bad idea to use many such Serial.println(""); (I like the F macro to save on RAM) throughout your sketch to pinpoint where the issues arise.

Additionally, I would recommend another print statement in void setup() just so you always know what version of the code is loaded on your board, such as:

void setup(){
  Serial.begin(9600);
  Serial.println(F("Screaming Skull Code version 3.0"));
  delay(1000); // yes, blocking, a quick sec to read ^ up there
// all your other stuff in setup here
}

Yes, at int on AVR is 16 bits, but is was referring to millis() which returns an unsigned long

I do understand that using millis() is better for a lot of things, but like you said, in this case, the Delays are there on purpose to give time for the physical parts of the prop to reach their destination or to build suspense. Closing the doors before I make sure the skull is back could break the doors so yes, those are there on purpose for this application.

Ok, but it's not the millis() overflowing that is the problem is it.

You can do the same thing with a millis loop. The advantage is you can also do other stuff while 'waiting'. You really need to look at the tutorials on doing many things at once etc and state. Good luck.

Currently I have been having to remove the nano to re-program it and then bring it back to the prop. I'll see if I can get a laptop so that I can run the debugging while it is actually inside it and actually running the hardware to see if anything stands out on the debugging side of things. Thanks for your suggestion.

Thank you.

Blank lines between the paragraphs would have made it even better (:grinning:) but it is already much easier to read than it was originally