Intermittent skip of Switch case (jumping to limbo?)

Hi. I'm struggling to understand why a Feather M0 controlled Halloween prop I've been making occasionally stops working at the point in a switch() structure where a pneumatic valve should be activated.

When a trigger pulse is detected, the Feather turns on a light in the coffin and triggers an MP3 board to start playing spooky sounds. The Feather then switches a linear actuator (via an electromechanical power and a direction relay) to open the coffin lid. After a limit switch detects that the coffin lid is fully open, the Feather turns off the two linear actuator relays and applies power to the pneumatic valve to make the zombie pop out (... and say some spooky stuff, pop back into his coffin, and the coffin lid closes).

This all typically works fine about 80% of the time, but the rest of the time the program hangs up at (or shortly before) the same point where the pneumatic valve should be activated.

I've tested my limit switches. I'm beginning to wonder if a disruptive inductive fly-back voltage spike is being generated when the linear actuator, or its power and direction relays, are turned off. As a precaution I staggered the relay switching times by 50 ms to avoid concurrent changes. In addition to the routine diode across the relay coils, I also put a simple snubber circuit (a 43 ohm resistor in series with a 0.01 uF capacitor) across the linear actuator motor. As far as I can tell, these precautions have had no effect.

While I continue to suspect a hardware-based problem, I'd appreciate a sanity check as to whether there's something I'm not understanding about my code which might interfere with the sketch getting to the "sc_Extend" case where it normally would turn on the pneumatic valve.

Any advice about my code or suggestions for diagnosing and fixing the root cause would be greatly appreciated!

#define OpeningLightSoundPeriod 5000        // Pre-opening sound period in ms
#define ExtraLidMovementTime 3000           // Extra time (ms) to make sure lid fully opens/closes
#define PostTalkingPauseTime  4000          // Time (ms) for zombie to chill after saying his piece
#define PostLidClosurePauseTime 2000        // Time (ms) from lid closure to light & sound off
#define PostLightOffPauseTime 5000          // Time (ms) from light off to sound off after lid closure

#define RUNstop 0
#define OPENclose 1
#define WaveLight 5
#define EXTENDretract 6
#define LidOpen 9
#define LidClosed 10
#define Trigger 11
#define Extended 12
#define Retracted 17      // Formerly 13
#define Mp3Trigger 14     // FN-BC10 Level Hold Loop Playback (Mode 1). Hold trigger pin low for playback duration 
#define HdMp3Busy 15
#define CofMp3Busy 16

const byte sc_AwaitingTrigger           = 1;  // If triggered, turn on WaveLight & lower CofMp3 trigger (sound begins)
const byte sc_StartOpeningLid           = 2;  // Wait 5 sec before opening coffin lid
const byte sc_FinishOpeningLid          = 3;  // If OPEN limit switch = HIGH, start 3 sec extra movement timer
const byte sc_Extend                    = 4;  // Disable lid actuator. Turn on PValve (zombie begins to rise)
const byte sc_HdMp3Busy                 = 5;  // Wait until HdMp3 is LOW (busy, zombie starts talking)
const byte sc_HdMp3NotBusy              = 6;  // Wait until HdMp3 is HIGH (not busy, zombie stops talking)
const byte sc_StartRetracting           = 7;  // Wait 4 sec then turn off PValve (begin to retract)
const byte sc_StartClosingLid           = 8;  // If RETRACTED limit switch is HIGH, start closing lid
const byte sc_LidClosed                 = 9;  // If CLOSED limit switch is HIGH, start 2 sec post closure timer
const byte sc_LightOff                  = 10; // Turn off WaveLight then start 5 sec post light timer
const byte sc_SoundOff                  = 11; // Raise CofMp3 trigger (coffin sound off). Loop back to await next trigger

byte CoffinStepNo = sc_AwaitingTrigger;

unsigned long MillisMoment;

void setup() {
  pinMode(RUNstop, OUTPUT);
  pinMode(OPENclose, OUTPUT);
  pinMode(WaveLight, OUTPUT);
  pinMode(EXTENDretract, OUTPUT);
  pinMode(LidOpen, INPUT_PULLUP);
  pinMode(LidClosed, INPUT_PULLUP);
  pinMode(Trigger, INPUT_PULLUP);
  pinMode(Extended,INPUT_PULLUP);
  pinMode(Retracted, INPUT_PULLUP);
  pinMode(Mp3Trigger, OUTPUT);
  pinMode(HdMp3Busy, INPUT);          // Active low
  pinMode(CofMp3Busy, INPUT);         // Active low

  digitalWrite(RUNstop, LOW);         // Initialize output levels
  digitalWrite(OPENclose, LOW);
  digitalWrite(WaveLight, LOW);
  digitalWrite(EXTENDretract, LOW);
  digitalWrite(Mp3Trigger, HIGH);
  
  // put your setup code here, to run once:

}

void loop() {
  switch (CoffinStepNo)
  {
    case sc_AwaitingTrigger:
      if(digitalRead(Trigger) == HIGH)
      {
        digitalWrite(WaveLight, HIGH);      // Turn on Wave Light
        digitalWrite(Mp3Trigger, LOW);      // Initiate falling edge of MP3 board trigger
        MillisMoment = millis();            // Capture time to measure pre-opening light/sound period
        CoffinStepNo = sc_StartOpeningLid;      // Point to next switch state
      }
      break;                                // Jump to end of switch

    case sc_StartOpeningLid:
      if(millis() - MillisMoment >= OpeningLightSoundPeriod)
      {
        digitalWrite(OPENclose, HIGH);      // Switch linear actuator power route to open lid
        delay(50);
        digitalWrite(RUNstop, HIGH);        // Apply power to run the linear actuator
        delay(50);
        CoffinStepNo = sc_FinishOpeningLid; // Point to next switch state
      }
      break;                                // Jump to end of switch

    case sc_FinishOpeningLid:
      if(digitalRead(LidOpen) == HIGH)      // If coffin lid limit switch has opened
      {
        MillisMoment = millis();            // Capture time to allow time to fully open
        CoffinStepNo = sc_Extend;           // Point to next switch state
      }
      break;

    case sc_Extend:                         // Jump to end of switch
      if(millis() - MillisMoment >= ExtraLidMovementTime) // If extra lid movement time has passed
      {
        digitalWrite(RUNstop, LOW);         // Switch off power to linear actuator
        delay(50);
        digitalWrite(OPENclose, LOW);       // Power off linear actuator direction relay
        delay(50);
        digitalWrite(EXTENDretract, HIGH);  // Set pneumatic valve to extend the scissor mechanism
        CoffinStepNo = sc_HdMp3Busy;        // Point to next switch state
      }
      break;                                // Jump to end of switch

    case sc_HdMp3Busy:
      if(digitalRead(HdMp3Busy) == LOW)     // Wait for head Mp3 player to get active (zombie's talking)
      {
        CoffinStepNo = sc_HdMp3NotBusy;     // Point to next switch state
      }
      break;                                // Jump to end of switch

    case sc_HdMp3NotBusy:
      if(digitalRead(HdMp3Busy) == HIGH)    // If zombie has finished talking
      {    
        MillisMoment = millis();            // Capture time to allow time for zombie to chill
        CoffinStepNo = sc_StartRetracting;  // Point to next switch state
      }
      break;                                // Jump to end of switch

    case sc_StartRetracting:
      if(millis() - MillisMoment >= PostTalkingPauseTime) // If it'a timw for the zombie to go back home
      {
        digitalWrite(EXTENDretract, LOW);  // Set pneumatic valve to retract the scissor mechanism
        CoffinStepNo = sc_StartClosingLid; // Point to next switch state
      }
      break;                                // Jump to end of switch

    case sc_StartClosingLid:
      if(digitalRead(Retracted) == HIGH)      // If limit switch indicates scissor mechanism has retracted
      {
        digitalWrite(OPENclose, LOW);         // Set linear actuator direction relay to close the lid
        delay(50);
        digitalWrite(RUNstop, HIGH);          // Switch on power to the linear actuator
        delay(50);
        CoffinStepNo = sc_LidClosed;          // Point to next switch state
      }
      break;                                // Jump to end of switch

    case sc_LidClosed:
      if(digitalRead(LidClosed) == HIGH)      // If limit switch indicates lid has closed
      {
        MillisMoment = millis();            // Capture time to measure post-closure delay
        CoffinStepNo = sc_LightOff;         // Point to next switch state
      }
      break;                                // Jump to end of switch

    case sc_LightOff:
      if(millis() - MillisMoment >= PostLidClosurePauseTime)  // If desired time has passed since lid closed
      {
        digitalWrite(RUNstop, LOW);         // Turn off RUNstop relay
        delay(50);                          // Debounce 50 ms
        digitalWrite(WaveLight, LOW);       // Turn off Wave Light
        MillisMoment = millis();            // Capture time to measure post-light delay
        CoffinStepNo = sc_SoundOff;       // Point to next switch state
      }
      break;                                // Jump to end of switch

    case sc_SoundOff:
      if(millis() - MillisMoment >= PostLightOffPauseTime)
        {
          digitalWrite(Mp3Trigger, HIGH);     // Disable Coffin MP3 player
          CoffinStepNo = sc_AwaitingTrigger;  // Loop back to await next trigger
        }
        break;                                // Jump to end of switch
  }
}

is Trigger switch closed while inactive?
i see nothing what should hang the program. but one thing i would change: just skip time instead of waiting for MP3busy signal.
are wires to end switches long?

Too many words - can we have a schematic please?

Have you debounced the switch inputs (if necessary?)

Are your grounds connected properly?

1 Like

Sounds strange. Normally a pair of SPDT relays are used in an H-bridge circuit. 4 flyback diodes for the motor, in addition to the 2 flyback diodes for the 2 relay coils, are needed. (If using SPDT relay modules, these usually have the flyback diodes for the coils built in, but you still need to add the 4 diodes for the motor, in a diode-bridge configuration.)

Yet you posted your code and not your schematic. Easier for you, I guess.

What happens if you disable the command for the valve and replace it with a led for example ? (Trying to assess if a current surge cripples your arduino)

You could add a default: to your switch statement to catch unexpected values of the switch value. I often do this to catch values which should never happen unless there's a bug.

    case sc_SoundOff:
      if(millis() - MillisMoment >= PostLightOffPauseTime)
        {
          digitalWrite(Mp3Trigger, HIGH);     // Disable Coffin MP3 player
          CoffinStepNo = sc_AwaitingTrigger;  // Loop back to await next trigger
        }
        break;                                // Jump to end of switch

    default:
      Serial.println("Invalid switch value = " + String(CoffinStepNo));
  }
}
1 Like

Just go to a bit of troubel to avoid using Strings when you do


  default :
    Serial.print("Invalid switch value = ");
    Serial.println(CoffinStepNo);

a7

I used a stopwatch on my phone to time the linear actuator I used in a similar project. It gave me options like half or quarter open, things like that. It always operates at the exact same speed so it works, if a brutal way of going about it.

I also used multiple Arduinos in my similar build. One Mega for the extra hardware UARTs and to direct traffic while waiting for IR readings and RFID scans, also triggers solid state relays for AC lighting, an Uno for the sound (Adafruit Wave Shield), another Uno (or Nano Every - can't remember) for the linear actuator relay board, another Nano Every for the "breathing" light effect.

Is the Trigger switch closed while inactive?
i see nothing what should hang the program. but one thing i would change: just skip time instead of waiting for MP3busy signal.
are wires to end switches long?aste code here

Yes, the trigger switch is closed (pulling the assigned Feather pin to ground) when inactive. Pressing the trigger switch opens it, causing the Feather's internal pull-up to draw it high.

I need to wait for the zombie to stop talking before initiating the process of returning him to his coffin and closing the lid.

My coffin lid limit switch wires are about 6 feet long.

Thanks for your reply!

The flow seems ok, using Serial chars to navigate around I was able to get through your sketch. Check out what I changed and/or disabled (commented out) to see if it gives you a eureka moment.

#define OpeningLightSoundPeriod 5000  // Pre-opening sound period in ms
#define ExtraLidMovementTime 3000     // Extra time (ms) to make sure lid fully opens/closes
#define PostTalkingPauseTime 4000     // Time (ms) for zombie to chill after saying his piece
#define PostLidClosurePauseTime 2000  // Time (ms) from lid closure to light & sound off
#define PostLightOffPauseTime 5000    // Time (ms) from light off to sound off after lid closure

#define RUNstop 0
#define OPENclose 1
#define WaveLight 5
#define EXTENDretract 6
#define LidOpen 9
#define LidClosed 10
#define Trigger 11
#define Extended 12
#define Retracted 17   // Formerly 13
#define Mp3Trigger 14  // FN-BC10 Level Hold Loop Playback (Mode 1). Hold trigger pin low for playback duration
#define HdMp3Busy 15
#define CofMp3Busy 16

const byte sc_AwaitingTrigger = 1;   // If triggered, turn on WaveLight & lower CofMp3 trigger (sound begins)
const byte sc_StartOpeningLid = 2;   // Wait 5 sec before opening coffin lid
const byte sc_FinishOpeningLid = 3;  // If OPEN limit switch = HIGH, start 3 sec extra movement timer
const byte sc_Extend = 4;            // Disable lid actuator. Turn on PValve (zombie begins to rise)
const byte sc_HdMp3Busy = 5;         // Wait until HdMp3 is LOW (busy, zombie starts talking)
const byte sc_HdMp3NotBusy = 6;      // Wait until HdMp3 is HIGH (not busy, zombie stops talking)
const byte sc_StartRetracting = 7;   // Wait 4 sec then turn off PValve (begin to retract)
const byte sc_StartClosingLid = 8;   // If RETRACTED limit switch is HIGH, start closing lid
const byte sc_LidClosed = 9;         // If CLOSED limit switch is HIGH, start 2 sec post closure timer
const byte sc_LightOff = 10;         // Turn off WaveLight then start 5 sec post light timer
const byte sc_SoundOff = 11;         // Raise CofMp3 trigger (coffin sound off). Loop back to await next trigger

byte CoffinStepNo = sc_AwaitingTrigger;

bool thisTrigger = 0;
bool lidOpened = 0;

unsigned long MillisMoment;

void setup() {
  Serial.begin(115200);
  pinMode(RUNstop, OUTPUT);
  pinMode(OPENclose, OUTPUT);
  pinMode(WaveLight, OUTPUT);
  pinMode(EXTENDretract, OUTPUT);
  pinMode(LidOpen, INPUT_PULLUP);
  pinMode(LidClosed, INPUT_PULLUP);
  pinMode(Trigger, INPUT_PULLUP);
  pinMode(Extended, INPUT_PULLUP);
  pinMode(Retracted, INPUT_PULLUP);
  pinMode(Mp3Trigger, OUTPUT);
  pinMode(HdMp3Busy, INPUT);   // Active low
  pinMode(CofMp3Busy, INPUT);  // Active low

  digitalWrite(RUNstop, LOW);  // Initialize output levels
  digitalWrite(OPENclose, LOW);
  digitalWrite(WaveLight, LOW);
  digitalWrite(EXTENDretract, LOW);
  digitalWrite(Mp3Trigger, HIGH);
  Serial.println("\nCurrent CoffinStepNo: sc_AwaitingTrigger");
  Serial.println("sc_AwaitingTrigger, type in 1 to trigger\n");
}

void loop() {
  if (Serial.available() > 0) {
    char inChar = Serial.read();
    switch (inChar) {
      case 'a':
        CoffinStepNo += 1;
        break;
      case 'd':
        CoffinStepNo -= 1;
        break;
      case '1':
        thisTrigger = 1;
        break;
      case '0':
        thisTrigger = 0;
        break;
      case 'w':
        lidOpened = 1;
        break;
      case 's':
        lidOpened = 0;
        break;
    }
  }
  switch (CoffinStepNo) {
    case sc_AwaitingTrigger:  // 1
      if (thisTrigger == 1) {
        printState(CoffinStepNo);
        digitalWrite(WaveLight, HIGH);      // Turn on Wave Light
        digitalWrite(Mp3Trigger, LOW);      // Initiate falling edge of MP3 board trigger
        MillisMoment = millis();            // Capture time to measure pre-opening light/sound period
        CoffinStepNo = sc_StartOpeningLid;  // Point to next switch state
      }
      break;  // Jump to end of switch

    case sc_StartOpeningLid:  // 2
      if (millis() - MillisMoment >= OpeningLightSoundPeriod) {
        printState(CoffinStepNo);
        digitalWrite(OPENclose, HIGH);  // Switch linear actuator power route to open lid
        delay(50);
        digitalWrite(RUNstop, HIGH);  // Apply power to run the linear actuator
        delay(50);
        CoffinStepNo = sc_FinishOpeningLid;  // Point to next switch state
      }
      break;  // Jump to end of switch

    case sc_FinishOpeningLid:  // 3
      if (lidOpened == 1)      // If coffin lid limit switch has opened
      {
        printState(CoffinStepNo);
        MillisMoment = millis();   // Capture time to allow time to fully open
        CoffinStepNo = sc_Extend;  // Point to next switch state
      }
      break;

    case sc_Extend:                                         // 4
      if (millis() - MillisMoment >= ExtraLidMovementTime)  // If extra lid movement time has passed
      {
        printState(CoffinStepNo);
        digitalWrite(RUNstop, LOW);  // Switch off power to linear actuator
        delay(50);
        digitalWrite(OPENclose, LOW);  // Power off linear actuator direction relay
        delay(50);
        digitalWrite(EXTENDretract, HIGH);  // Set pneumatic valve to extend the scissor mechanism
        CoffinStepNo = sc_HdMp3Busy;        // Point to next switch state
      }
      break;  // Jump to end of switch

    case sc_HdMp3Busy:  // 5
      //  if(digitalRead(HdMp3Busy) == LOW)     // Wait for head Mp3 player to get active (zombie's talking)
      // {
      printState(CoffinStepNo);
      CoffinStepNo = sc_HdMp3NotBusy;  // Point to next switch state
                                       // }
      break;                           // Jump to end of switch

    case sc_HdMp3NotBusy:  // 6
      // if(digitalRead(HdMp3Busy) == HIGH)    // If zombie has finished talking
      // {
      printState(CoffinStepNo);
      MillisMoment = millis();            // Capture time to allow time for zombie to chill
      CoffinStepNo = sc_StartRetracting;  // Point to next switch state
                                          //  }
      break;                              // Jump to end of switch

    case sc_StartRetracting:                                // 7
      if (millis() - MillisMoment >= PostTalkingPauseTime)  // If it'a timw for the zombie to go back home
      {
        printState(CoffinStepNo);
        digitalWrite(EXTENDretract, LOW);   // Set pneumatic valve to retract the scissor mechanism
        CoffinStepNo = sc_StartClosingLid;  // Point to next switch state
      }
      break;  // Jump to end of switch

    case sc_StartClosingLid:  // 8
      // if(digitalRead(Retracted) == HIGH)      // If limit switch indicates scissor mechanism has retracted
      //  {
      printState(CoffinStepNo);
      digitalWrite(OPENclose, LOW);  // Set linear actuator direction relay to close the lid
      delay(50);
      digitalWrite(RUNstop, HIGH);  // Switch on power to the linear actuator
      delay(50);
      CoffinStepNo = sc_LidClosed;  // Point to next switch state
                                    // }
      break;                        // Jump to end of switch

    case sc_LidClosed:     // 9
      if (lidOpened == 0)  // If limit switch indicates lid has closed
      {
        printState(CoffinStepNo);
        MillisMoment = millis();     // Capture time to measure post-closure delay
        CoffinStepNo = sc_LightOff;  // Point to next switch state
      }
      break;  // Jump to end of switch

    case sc_LightOff:                                          // 10
      if (millis() - MillisMoment >= PostLidClosurePauseTime)  // If desired time has passed since lid closed
      {
        printState(CoffinStepNo);
        digitalWrite(RUNstop, LOW);    // Turn off RUNstop relay
        delay(50);                     // Debounce 50 ms
        digitalWrite(WaveLight, LOW);  // Turn off Wave Light
        MillisMoment = millis();       // Capture time to measure post-light delay
        CoffinStepNo = sc_SoundOff;    // Point to next switch state
      }
      break;  // Jump to end of switch

    case sc_SoundOff:  // 11
      if (millis() - MillisMoment >= PostLightOffPauseTime) {
        printState(CoffinStepNo);
        digitalWrite(Mp3Trigger, HIGH);  // Disable Coffin MP3 player
        Serial.println("sc_SoundOff");
        CoffinStepNo = sc_AwaitingTrigger;  // Loop back to await next trigger
        Serial.println("\nCurrent CoffinStepNo: sc_AwaitingTrigger");
        Serial.println("sc_AwaitingTrigger, type in 1 to trigger\n");
      }
      break;  // Jump to end of switch
  }
}
void printState(byte CoffinStepNo) {
  byte thisStepNo = CoffinStepNo;
  Serial.print("\nCurrent CoffinStepNo: ");
  switch (thisStepNo) {
    case 1:
      Serial.println("sc_AwaitingTrigger");
      break;
    case 2:
      Serial.println("sc_StartOpeningLid");
      Serial.println("type w to simulate coffin lid limit switch has opened");
      break;
    case 3:
      Serial.println("sc_FinishOpeningLid");
      break;
    case 4:
      Serial.println("sc_Extend");
      break;
    case 5:
      Serial.println("sc_HdMp3Busy");
      break;
    case 6:
      Serial.println("sc_HdMp3NotBusy");
      break;
    case 7:
      Serial.println("sc_StartRetracting");
      break;
    case 8:
      Serial.println("sc_StartClosingLid");
      Serial.println("type s to simulate coffin lid limit switch has closed");
      break;
    case 9:
      Serial.println("sc_LidClosed");
      break;
    case 10:
      Serial.println("sc_LightOff");
      break;
    case 11:
      thisTrigger = 0;
      break;
  }
  Serial.println();
  delay(500);
}

I'm using one relay to gate power to a second DPDT relay which selects which linear actuator motor terminal is at +12V vs. ground.

Thanks! That sounds like a great idea. Alternatively, I could selectively disconnect the power wires to the linear actuator and then the pneumatic valve and work those mechanical functions artificially to see if the remaining system functions are consistently working. I hadn't thought of a current surge problem (I was only thinking of a voltage spike). I may first try putting a capacitor across the Feather's +5V power input.

Thanks for your reply and for the request. My current schematic is a barely legible sketch. I'll try to come up with a cleaner one to share.

I don't think debouncing is necessary in this case. The leading edge of a switch signal is change is sufficient in my case because the Feather stops listening for subsequent changes until well after any contact bouncing would have subsided.

Good advice about checking the grounds. Yes, they're all connected.

1 Like

Thanks for the helpful debugging technique!

I was reading through your code for fun (!) and I notice these

    break;                                // Jump to end of switch

By now you know what break does, everyone helping or trying to certainly does and I point it out only as an example.

It's just extra ink, cluttering things up, a gratuitous comment like

  a++;    // increment a

I am a fan of not writing comments that aren't necessary. I am a fan of writing, or trying, code that doesn't need much commentary.

Comments have their place, but they can be misleading, wrong or gratuitous, and too often do not get properly updated as code evolves.

Just a thought.

a7

Thanks for taking such a close look. I'll take a closer look at your changes when I have time tonight, but I first wanted to thank you for taking the time to give me so much to think about!

1 Like

It's for the cause; my pleasure.

For Those About to Haunt, I Salute You! :saluting_face:

No AC here, only DC :slight_smile:

2 Likes

Sure, that can work, but it is more usual to use 2x SPDT relays with a motor. SPDT relays are more commonly used/available and maybe a little cheaper also. Almost all the relay modules sold for/used with Arduino are SPDT.

Another option is to use an electronic H-bridge motor driver module. But I would not recommend one that uses L293 or L298. They are almost as old-fashioned and inefficient as using relays! A modern H-bridge based on TB6612 or MX1508 will be much more efficient than those others, and way more efficient than relays.

Yes- but are they connected as a star with high current and low current grounds kept separate - like this?
image