Program unstable

I have made a can crusher. It is design with a chute to crush the cans that are entered into the chute. The can is droped throught two gates and entre a chamber where a motor is driven forward to crush the can and then reverses to load the next can. But i keeping having the same issue of the code gets unstable. It works really well when only one can is placed in the chute and sometimes when two cans are loaded but then gets unstable and keeps driving forward or reverse to far. I have tried every thing i can but can not work out where i am going wrong. I was hoping somebody here may be able to help.

#define MRELAY1 13  //relay for top gate
#define MRELAY2 12  //relay for bottom gate
#define MRELAY3 11  //earth relay
#define MRELAY4 10  //forward and reverse relay(de-energised forward)
#define MRELAY5 9   //forward and reverse relay(as above)
#define SWITCH1 8   //top ir sensor 
#define SWITCH2 7   //forward cutoff switch
#define SWITCH3 6   //reverse cutoff switch
#define SWITCH4 5   //bottom ir sensor
int val1 = 1; //top ir switch   
int val2 = 1; //forward cutoff switch
int val3 = 0; //reverse cutoff switch
int val4 = 1; //bottom ir switch

void setup() {
pinMode(MRELAY1, OUTPUT);
pinMode(MRELAY2, OUTPUT);
pinMode(MRELAY3, OUTPUT);
pinMode(MRELAY4, OUTPUT);
pinMode(MRELAY5, OUTPUT);
pinMode(SWITCH1, INPUT);
pinMode(SWITCH2, INPUT);
pinMode(SWITCH3, INPUT);
pinMode(SWITCH4, INPUT);


}


void loop() {
val2 = digitalRead(SWITCH2);  //forward cutoff switch
val3 = digitalRead(SWITCH3);  //reverse cutoff switch
val4 = digitalRead(SWITCH4);  //bottom ir sensor

if (val4 == LOW && val2 == HIGH && val3 == LOW) {
  delay(1000);    
  digitalWrite(MRELAY2, HIGH);  //open bottom gate
  delay(2000);
  digitalWrite(MRELAY2, LOW);   //close bottom gate
  delay(2000);
  digitalWrite(MRELAY3,HIGH);   //energise earth relay drive forward
  delay(4000);
  
} 
  val2 = digitalRead(SWITCH2);
  val3 = digitalRead(SWITCH3); 
  
if(val2 == LOW && val3 == HIGH){
  delay(100);
  digitalWrite(MRELAY3,LOW);  //de-energise earth relay stop driving forward
  delay(5000);
  digitalWrite(MRELAY5,HIGH);  //energise relay to drive reverse
  digitalWrite(MRELAY4,HIGH);  //energise relay to drive reverse
  digitalWrite(MRELAY3,HIGH);  //energise earth relay to drive reverse
  delay(2000);

}
  val2 = digitalRead(SWITCH2);
  val3 = digitalRead(SWITCH3);
  
if(val2 == HIGH && val3 == LOW){
  delay(100);
  digitalWrite(MRELAY5,LOW);  //de-energise earth relay off
  digitalWrite(MRELAY4,LOW);  //de-energise reverse relay off
  digitalWrite(MRELAY3,LOW);  //de-energise reverse relay off
  delay(2000);

}
  val1 = digitalRead(SWITCH1);
  val2 = digitalRead(SWITCH2);
  val3 = digitalRead(SWITCH3);
  val4 = digitalRead(SWITCH4);
  
if(val3 == LOW && val1 == LOW && val2 == HIGH && val4 == HIGH){
  delay(1000);  
  digitalWrite(MRELAY1, HIGH);  //energise top gate relay open
  delay(2000);
  digitalWrite(MRELAY1, LOW);   //de-energise top gate relay close
  delay(2000);
      

 
}else{
digitalWrite(MRELAY1, LOW);
}

}

Moderator edit:
</mark> <mark>[code]</mark> <mark>

</mark> <mark>[/code]</mark> <mark>
tags added.

I have been programming for 40 years and have never, even once, seen code that was "unstable". It always does just what it was written to do.

How about a bit more description than "unstable"? What have you done to debug the program? What results?

Why all the "delay" throughout the program?

Paul

Well Paul
Thanks for the reply yes i am new to programming.
I mean that after one can it does not drop the second can and the motor drives forward or reverse not swithing the motor off when the switch is is avtivated. I does not seem to read the switches. The switches are used to stop the piston driving forward or backward.I put the delays there thinking it would help with the control. When i done that it did appear to settle things down that it would crush one can easy and the when it finished would load another can and it worked well. I tried counting the switches and made no difference to with way it reachted.Plus the delays are there to allow everything to stablelize before before doing the next action. It is a 12 volt setup and the motor draws a bit of current so did not want to overheat anything.

First of all, I would use decent names for pins and states. It will make it a million times easier to follow what is happening in the program.
E.g.

#define RELAY_TOPGATE 13
#define RELAY_BOTTOMGATE 12
#define RELAY_EARTH 11
#define MRELAY4 10  //forward and reverse relay(de-energised forward)
#define MRELAY5 9   //forward and reverse relay(as above)
#define SENSOR_IR_TOP 8
#define SW_FW_CUTOFF 7
#define SW_REV_CUTOFF 6
#define SENSOR_IR_BOTTOM 5
int topIRState = 1;
int fwCutoffState = 1;
int revCuroffState = 0;
int bottomIRState = 1;

I have no idea how to name relays 4 and 5; your comment states that they do the same.

Next you can help us by commenting the if statements

  if (bottomIRState == LOW && fwCutoffState == HIGH && revCuroffState == LOW)
  {

What does it mean?
bottomIRState == LOW; does this mean that a can is detected or that no can is detected. Same question for the switches. Does LOW mean activated or does HIGH mean activated.
E.g. (I don't know if this is correct)

  // if no can in chamber and ...
  if (bottomIRState == LOW && fwCutoffState == HIGH && revCuroffState == LOW)
  {

It's just an idea to make life easier for us (now) and for yourself (in the future).

Next a proper description would help as well :wink: E.g.

Can detected above chamber? If yes, go to (2), else stay in (1)
2)
Close bottom gate
Open topgate
go to (3)
3)
Close topgate
go to (4)
...
...

Add all conditions and all actions.

Next question is if you use pull-up or pull-down resistors on your switches; the might also be required on your IR sensors (post a link so we can check them).

Grant1962:
I have made a can crusher. It is design with a chute to crush the cans that are entered into the chute. The can is droped throught two gates and entre a chamber where a motor is driven forward to crush the can and then reverses to load the next can. But i keeping having the same issue of the code gets unstable. It works really well when only one can is placed in the chute and sometimes when two cans are loaded but then gets unstable and keeps driving forward or reverse to far. I have tried every thing i can but can not work out where i am going wrong. I was hoping somebody here may be able to help.

It sounds like your problem is that the motor(s) are loading down your power supply and causing the Arduino to crash, either because of electrical noise or because the voltage drops too low.

Try putting a voltmeter on the Arduino 5 volt and ground pins and measure it. You should get almost 5 volts (anywhere between 4.95 and 5.05 is to be expected).

Then run the motors. If the voltmeter reading drops or even flickers, then you've found your problem.

Thankyou Sterretje and Krupski
I will rewrite the code as you has sugguested Sterretje. Relay 4 and 5 are the change over relay to drive forward or reverse a bit like a H bridge.Then i will coleetc the reast of the information you have ask for and post. I will also check the volatge drop at the same time.
Thanks Guys i realy apperciate your help. Have been trying to work this out for a long time and was getting now where. Thankyou again.

#define SWITCH2 7   //forward cutoff switch
#define SWITCH3 6   //reverse cutoff switch

NO! Bad programmer!

#define FWD_CUTOFF_SW_PIN 7
#define REV_CUTOFF_SW_PIN 6

Or better still:

const byte FWD_CUTOFF_SW_PIN = 7;
const byte REV_CUTOFF_SW_PIN = 6;

Hi All
Have redone the code and tried to explain it all as best as i can. Here it is with all the info i can give. Hope this explains more about what i am try to do.
// Can crusher is driven from a 12 volt lead acid battery.Voltage reducer fitted to control the Arduino board.
// Can loading chamber has two gates both connected to solenoids reason for gates are to control the cans being feed into the crushing chamber.
// There are two ir sensors in the loading chamber to sensing when the can are in the loading chamber.
// Crushing chamber has two switches and a motor. The swithes are used for controling the motor forward and reverse travel.The motor drives a piston to crush the can.
// 1)So the basic idea is to load the loading chamber with cans.The loading chamber can hold 5 cans at once.
// 2)The top ir sensor detects the can and opens the top chamber gate allowing the can to drop down to the next gate.
// 3)The lower ir sensor detects the can and opens the gate allowing the can to drop into the crushing chamber.
// 4)The motor drives the piston forward crushing the can. When the forward switch is opened the motor stops driving forward.
// 5)After a delay the motor drives the piston in reverse until it closes to reverse switch stopping the motor.
// 6)Then it should detect the next can and starts over again until no cans detected(This is where i am having issue's with the progarm).

#define SOLENOID_TOPGATE 13 //This is to open and close top gate. De-energised the solenoid is closed and opens when energized.
#define SOLENOID_BOTTOMGATE 12 //This is to open and close bottom gate. De-energised the solenoid is closed and opens when energized.
#define RELAY_EARTH 11 //De-energised the relay disconnects the ground wiring.This is the sarting state of the relay.
#define RELAY1 10 //Forward and Reverse relay.This is a change over relay.De-energised the motor while drive forward.Energized the relay changes over to drive motor in reverse.
#define RELAY2 9 //Forward and Reverse relay.This is a change over relay.De-energised the motor while drive forward.Energized the relay changes over to drive motor in reverse.
#define SENSOR_IR_TOP 8 //This detects a can on the top gate of the loading chamber.
#define SW_FW_CUTOFF 7 // This detects when the piston has reached is travel and stops it driving forward.
#define SW_REV_CUTOFF 6 // This detects when the piston has reached is travel and stops it driving in reverse.
#define SENSOR_IR_BOTTOM 5 // This detects a can on the bottom gate of the loading chamber.
int topIRState = 1; // No can detected
int fwCutoffState = 1; // The switch is closed power is going through the switch
int revCutoffState = 0; // The switch is open no power going through the switch
int bottomIRState = 1; // No can detected

void setup() {
pinMode(SOLENOID_TOPGATE, OUTPUT);
pinMode(SOLENOID_BOTTOMGATE, OUTPUT);
pinMode(RELAY_EARTH, OUTPUT);
pinMode(RELAY1, OUTPUT);
pinMode(RELAY2, OUTPUT);
pinMode(SENSOR_IR_TOP, INPUT);
pinMode(SW_FW_CUTOFF, INPUT);
pinMode(SW_REV_CUTOFF, INPUT);
pinMode(SENSOR_IR_BOTTOM, INPUT);

}

void loop() {
  // This is to check the piston is in the right position to allow the can to drop into crushing chamber
 fwCutoffState = digitalRead(fwCutoffState); // Check switch is in the open position so the piston is not at that end 
 revCutoffState = digitalRead(revCutoffState); // Check switch is in the closed position so the piston is in the right position ready to drive forward
 bottomIRState = digitalRead(bottomIRState); // Cheeking if there is a can there ready to load into crushing chamber
 
 // If the condition below are meet then load can and start driving piston forward
 if (bottomIRState == LOW && fwCutoffState == HIGH && revCutoffState == LOW) {
   digitalWrite(SOLENOID_BOTTOMGATE, HIGH);  // Energize solenoid to open the bottom gate allowing can to enter crushing chamber
   delay(2000); // Delay added to allow can to drop through properly other wise gate closes to quick not allowing can to drop through
   digitalWrite(SOLENOID_BOTTOMGATE, LOW);  // Soleniod de-energized to close bottom gate
   delay(2000); // Delay added so there are not have to many function running at the same time(Also did not want the motor starting until gate fully closed). 
   digitalWrite(RELAY_EARTH, HIGH);  // Power sent to the relay to switch the relay connecting earth wiring again. Driving the piston forward
   delay(4000);  // Delay added because the piston would not drive forward without it. Think its because it was reading the revCutoff switch to quick maybe 
   
   
 }
 // Read switches to detect when fwCutoff switch is reached and has left the revCutoff switch 
 fwCutoffState = digitalRead(fwCutoffState);
 revCutoffState = digitalRead(revCutoffState);
 // fwCutoff switch has been reached stop driving piston forward and change direction to reverse piston back to the revCutoff switch
 if(fwCutoffState == LOW && revCutoffState == HIGH) {
   delay(100); // Delay added to read swtch with out switch bounce
   digitalWrite(RELAY_EARTH, LOW); // fwCutoff switch has been reached de-energize relay disconnecting earth wiring stopping piston being driven forward
   delay(5000); // Delay added again to allow everything settle before driving in reverse
   digitalWrite(RELAY2, HIGH); // Energize relay to change over to drive motor in reverse
   digitalWrite(RELAY1, HIGH); // Energize relay to change over to drive motor in reverse
   digitalWrite(RELAY_EARTH, HIGH); // Energize relay to connect earth wiring and diving piston in reverse
   delay(2000); // Delay added so it does not read the fwCutoff switch until it starts driving in reverse
   
}
 // Read fwCutoff switch to make sure piston has driven in reverse and read revCutoff switch to turn the piston off driving in reverse
 fwCutoffState = digitalRead(fwCutoffState);
 revCutoffState = digitalRead(revCutoffState);
 
 // Once revCutoff switch is reached stop driving in reverse
 if(fwCutoffState == HIGH && revCutoffState == LOW) {
   delay(100); // Delay added to read switch with out switch bounce
   digitalWrite(RELAY_EARTH, LOW); // De-energize relay to disconnect earth wiring
   digitalWrite(RELAY2, LOW); // De-energize relay to return it to drive forward state but will not drive forward due to the earth relay
   digitalWrite(RELAY1, LOW); // De-energize relay to return it to drive forward state but will not drive forward due to the earth relay
   delay(2000); // Delay added to let things settle again
   
 }

 // Check every thing is ready to reload
 topIRState = digitalRead(topIRState); // Check if there is a can ready at the top gate
 fwCutoffState = digitalRead(fwCutoffState); // Check the piston is not sitting at the wrong end off crushing chamber
 revCutoffState = digitalRead(revCutoffState); // Check the piston has fully returned so next can be loaded
 bottomIRState = digitalRead(bottomIRState); // Check if there is a can sitting against the second gate
 
 // Check the piston is in the right place and the bottom gate does not have a can there and laod if there is a can at the top Gate ready to load and load it into the second gate.
 if(revCutoffState == LOW && fwCutoffState == HIGH && topIRState == LOW && bottomIRState == HIGH) {
   delay(200);
   digitalWrite(SOLENOID_TOPGATE, HIGH); // Energize top solenoid to open the gate
   delay(2000); // Delay added to allow can to drop though 
   digitalWrite(SOLENOID_TOPGATE, LOW); // De-energize solenoid to close the gate
   delay(2000); // Delay added to let things settle
   
   
 }else{
   // If there is no can at the top gate leave gate closed
   digitalWrite(SOLENOID_TOPGATE, LOW);
   
 }
 
}

PaulMurrayCbr:

#define SWITCH2 7   //forward cutoff switch

#define SWITCH3 6   //reverse cutoff switch




**NO!** Bad programmer!



#define FWD_CUTOFF_SW_PIN 7
#define REV_CUTOFF_SW_PIN 6




Or better still:



const byte FWD_CUTOFF_SW_PIN = 7;
const byte REV_CUTOFF_SW_PIN = 6;

Hi i tried to add this but keep on getting a fault not sure how to add this in the coding.

Now you have too many comments (or they make it difficult to read for me) :wink: I think that this (aligned comments) e.g. is easier to read

  // If the condition below are meet then load can and start driving piston forward
  if (bottomIRState == LOW && fwCutoffState == HIGH && revCutoffState == LOW)
  {
    digitalWrite(SOLENOID_BOTTOMGATE, HIGH);  // Energize solenoid to open the bottom gate allowing can to enter crushing chamber
    delay(2000);                              // Delay added to allow can to drop through properly other wise gate closes to quick not allowing can to drop through
    digitalWrite(SOLENOID_BOTTOMGATE, LOW);   // Soleniod de-energized to close bottom gate
    delay(2000);                              // Delay added so there are not have to many function running at the same time(Also did not want the motor starting until gate fully closed).
    digitalWrite(RELAY_EARTH, HIGH);          // Power sent to the relay to switch the relay connecting earth wiring again. Driving the piston forward
    delay(4000);                              // Delay added because the piston would not drive forward without it. Think its because it was reading the revCutoff switch to quick maybe
  }

You don't have to comment the obvious, but at least we now know why there is a delay(100) in your code.

  // If the condition below are meet then load can and start driving piston forward
  if (bottomIRState == LOW && fwCutoffState == HIGH && revCutoffState == LOW)
  {

"if the conditions below are met" is obvious; "if can is in loading chamber and piston not in compression chamber" (or something like that) would make more sense.

Grant1962:
Hi i tried to add this but keep on getting a fault not sure how to add this in the coding.

Then you have to provide the updated code that causes the error as well as the complete error message (you can copy the orange part of the information in the output window and past it here).

Please use code tags in both cases

type
** **[code]** **

paste your code or error message after that
type
** **[/code]** **
after that

PS
digging through your code now trying to find the issue.

Question: does your problem occur when you have multiple cans in the loading chamber?

I might still be missing something. Assume that you have two (or more) cans in the loading chamber and start the process.

The first can will fall into the crushing chamber and the second can will fall down as well; how do you prevent the second can from also falling into the crushing chamber as well?

Next the crushing of the first can is finished and you end at the last if statement in your code that checks the bottomIRState (amongst others). That state will not be HIGH because the second can is now LOW.

Can you elaborate?

Note: this is how I visualize your setup

    X            X      <---------- top IR sensor
      ==========        <---------- top gate
+-------------------+
|  loading chanber  |
|                   |
|                   |
|       can 2       |
|   X   can 1    X  |   <---------- bottom IR sensor (with can)
+-------------------+
      ==========        <---------- bottom gate
+-------------------+
| crushing chamber  |
|                   |
+-------------------+

Hi Sterretje
Yes happens when more then one can is loaded into the loading chamber.
The cans sit against the first chamber. Then the top gate opens to allow one can only into the second chamber.
Done it this way with the two gates so i do not end up with two cans in the crushing chamber.

		+                  +
		 | Loading	    |								
		 | Chamber   |	 	 								
		 |		    |	 	 								
		 |		    |	 	 								
		 |		    |	
		 |		    |								
		 X		    X	<---- Top IR							
		 |========|	<---- Top gate			_________						
		 |		    |			                        |             |
	         |		    |						|	      |		
		 |		    |						|	      |	
		 X		    X	<---- Bottom IR		|	      |			
		 |========|	<---- Bottom gate		|	      |						
		 |		    |						|	      |	Drive motor
	--------		    --------------------------------	      |								
	                             |--------------------|                          |
               Crushing         |                            |                          |
               Chamber         |       Piston            |                          |
                                     |                            |                          |
                                     |--------------------|                          |
        --------------------------------------------------------------

You should not use in this type of drawings, that's why it looks all skew :wink: Only spaces.

You did not answer the question how you prevent two cans falling through the top gate; just my curiosity but I can image that the second can in the loading chamber also tries to fall through the top gate into the second chamber. Maybe closing the top gate 'clamps' the second can.

Another question is how you will remove a can once it's crushed? Or how it is removed? For safety you might actually have to implement two buttons that need to be pressed before the piston can work so your fingers can't be in the crushing chamber when the piston is activated; this might also apply to the solenoids.

I just picked up a bug in the code

  // This is to check the piston is in the right position to allow the can to drop into crushing chamber
  fwCutoffState = digitalRead(fwCutoffState); // Check switch is in the open position so the piston is not at that end
  revCutoffState = digitalRead(revCutoffState); // Check switch is in the closed position so the piston is in the right position ready to drive forward
  bottomIRState = digitalRead(bottomIRState); // Cheeking if there is a can there ready to load into crushing chamber

digitalRead does not read the state; it reads the switch or sensor. So that shoudl change to

  // read switches and sensors
  fwCutoffState = digitalRead(SW_FW_CUTOFF);
  revCutoffState = digitalRead(SW_REV_CUTOFF);
  bottomIRState = digitalRead(SENSOR_IR_BOTTOM);

You will need to fix this for all your digitalRead statements.

Anyway

You start the process with an empty crushing chamber (revCutoffState active, fwCutoffState not active), an empty secondary chamber (bottom IR not blocked) and one or more cans in the loading chamber. Next you start the machine (by applying power).

1)
if top IR blocked, go to (2)
else go to / stay in (1)
2)
open top gate, go to (3)
3)
if bottom IR blocked, close top gate and go to (4)
else go to / stay in (3)
4)
open bottom gate and wait a little, go to (5)
5)
I don't know if you can close the bottom gate at this point or if that can only happen once the crushing chamber is empty (height of chamber vs height of can). Let's assume that you can close the bottom gate, close it and go to (6)
6)
activate piston forward, go to (7)
7)
if fwCutoffSwitch activated, go to (8)
else go to / stay in  (7)
8)
activate piston reverse, go to (9)
9)
if revCuttoffSwitch activated, go to (10)
else go to / stay in (9)
10)
go to (1)

Is this a correct description of what should happen? You might need a step before (10) to empty the crushing chamber. If you need to implement safety switches, you basically need to check them in every step where there is movement (solenoids, piston) so you don't loose your fingers :wink:

Hi Sterretje
All the cans in the top of the loading chamber sit against the top gate. It is the position of both the gates and the power of the solenoid for the top gate with the delay that only allows one can to go to the second chamber. With the top gate holding the rest of the cans agaisnt the top gate. As for removing the cans there is a hole in the bottom of the crushing chamber. When the can is crushed down to size and the piston startes to reverse the can just drops out of the bottom of the crushing chamber. That is the only place you may have a pinch point. Have not worried about that yet as it is only me using it so i know to keep my fingers away from there. At a later date the idea will be to add a gate there to cover the opening for safety which will open to allow the can to drop out.
The steps you put are correct and that is the idea.
I was playing with it yesturday and what happens is you load one can only into the loading chamber it all works well.
But when you place two cans into the loading chamber the first can goes through the program but after it is crushed and the piston drives in reverse it keeps driving and does not stop when the reverse switch is reached. This is where my problem is. For some reason it does not read the reverse switch and stops the piston. It may be busy looking at the loading chamber top IR sensor and not looking at the reverse cut off switch.

The first step for debugging would be to add some Serial.println() statements after reading the switches. That way you can see what the states of the sensors and switches are.

e.g.

  // Check every thing is ready to reload
  topIRState = digitalRead(SENSOR_IR_TOP);
  fwCutoffState = digitalRead(SW_FW_CUTOFF);
  revCutoffState = digitalRead(SW_REV_CUTOFF);
  bottomIRState = digitalRead(SENSOR_IR_BOTTOM);

  Serial.println("last step");
  Serial.print("topIR: "); Serial.println(topIRState);
  Serial.print("bottomIR: "); Serial.println(bottomIRState);
  Serial.print("fwCutoff: "); Serial.println(fwCutoffState);
  Serial.print("revCutoff: "); Serial.println(revCutoffState);

Add step numbers (I used "last step" in the example because I did not feel like counting :wink: ) so you know where in the code you are when data is displayed.

You can also add some simple Serial.println statements in the if bodies. E.g. Serial.println("crushing started") and Serial.println("crushing finished"). For the debugging, add an 'else' to every 'if' and also add a Serial.println() statement to the else so you can follow the flow more easily.

Don't forget a Serial.begin() statement in the setup() :wink:

I will see if I can give you a basic framework based for the steps in my previous reply. If I can, it will look totally different from what you currently have.

This is a quick idea. It basically loops through the steps described earlier.

Add this to the beginning of your code for starters; it defines some sensible names for when a can is detected and a cutoff switch is activated. Based on your code, I think that both will be HIGH; if not, you can modify it.

#define CAN_DETECTED HIGH
#define SW_ACTIVATED HIGH

Next we will implement the steps. We first give them some sensible names; the numbers match the earlier mentioned steps. Add this at the beginning of your code as well.

#define ST1_WAITCANATTOP 1
#define ST1_OPENTOPGATE 2
#define ST1_WAITLOADED 3
#define ST1_OPENBOTTOMGATE 4
#define ST1_STARTCRUSH 6
#define ST1_WAITCRUSHCOMPLETE 6
#define ST1_REVERSE 8
#define ST1_WAITREVERSECOMPLETED 9

Next we need a variable to remember which step our program is doing. We call this step a state and we're implementing a simple statemachine. Place the below near the top of your code as well.

// current state for statemachine
byte currentState = ST1_WAITCANATTOP;

Next you can create a basic framework for the steps; I've implemented this using a switch / case instead of using if / else if / else if ....

void loop()
{

  topIRState = digitalRead(SENSOR_IR_TOP);
  bottomIRState = digitalRead(SENSOR_IR_BOTTOM);
  fwCutoffState = digitalRead(SW_FW_CUTOFF);
  revCutoffState = digitalRead(SW_REV_CUTOFF);

  switch (currentState)
  {
    // wait for one or more cans at the top gate
    case ST1_WAITCANATTOP:
      break;
    // drop a can in the loading chamber
    case ST1_OPENTOPGATE:
      break;
    // wait for can to be loaded in loading chamber
    case ST1_WAITLOADED:
      break;
    // open bottom gate
    case ST1_OPENBOTTOMGATE:
      break;
    // start crushing
    case ST1_STARTCRUSH:
      break;
    // wait for crushing to be completed
    case ST1_WAITCRUSHCOMPLETE:
      break;
    // reverse piston after crushing
    case ST1_REVERSE:
      break;
    // wait for piston to be retracted
    case ST1_WAITREVERSECOMPLETED:
      break;
  }
}

It's now the intention that every time that a step is completed, we will change the currentState. Because loop() is called repeatedly, the new state will take effect when loop is called again.

I've first a few helper function for the controlling of the piston and the gates. The serve a few purposes:

  1. re-usabilty
  2. readibility; it keeps loop() smaller and the names immediately make clear what is happening
  3. in future a possible different approach to the state machine
/******************************************
  motor actions
******************************************/

void motorForward()
{
  // set motor forward direction
  digitalWrite(RELAY2, LOW);
  digitalWrite(RELAY1, LOW);
  // switch motor on
  digitalWrite(RELAY_EARTH, HIGH);
}

void motorReverse()
{
  // set motor reverse direction
  digitalWrite(RELAY2, HIGH);
  digitalWrite(RELAY1, HIGH);
  // switch motor on
  digitalWrite(RELAY_EARTH, HIGH);
}

void motorStop()
{
  // switch motor off
  digitalWrite(RELAY_EARTH, LOW);
}

/******************************************
  gate actions
******************************************/

/*
  open specified gate
  in:
    gate to open
*/
void openGate(int gate)
{
  digitalWrite(gate, HIGH);
}

/*
  close specified gate
  in:
    gate to close
*/
void closeGate(int gate)
{
  digitalWrite(gate, LOW);
}

And lastly the full implementation of loop()

void loop()
{

  topIRState = digitalRead(SENSOR_IR_TOP);
  bottomIRState = digitalRead(SENSOR_IR_BOTTOM);
  fwCutoffState = digitalRead(SW_FW_CUTOFF);
  revCutoffState = digitalRead(SW_REV_CUTOFF);

  switch (currentState)
  {
    // wait for one or more cans at the top gate
    case ST1_WAITCANATTOP:
      if (topIRState == CAN_DETECTED)
      {
        currentState = ST1_OPENTOPGATE;
      }
      break;
    // drop a can in the loading chamber
    case ST1_OPENTOPGATE:
      // make sure bottom gate is closed
      closeGate(SOLENOID_BOTTOMGATE);
      // wait a little to give gate time to close
      delay(1000);
      // open top gate
      openGate(SOLENOID_TOPGATE);

      currentState = ST1_WAITLOADED;
      break;
    // wait for can to be loaded in loading chamber
    case ST1_WAITLOADED:
      if (bottomIRState == CAN_DETECTED)
      {
        // close top gate
        closeGate(SOLENOID_TOPGATE);

        currentState = ST1_OPENBOTTOMGATE;
      }
      break;
    // open bottom gate
    case ST1_OPENBOTTOMGATE:
      openGate(SOLENOID_BOTTOMGATE);
      // wait a little
      delay(1000);

      currentState = ST1_STARTCRUSH;
      break;
    // start crushing
    case ST1_STARTCRUSH:
      motorForward();

      currentState = ST1_WAITCRUSHCOMPLETE;
      break;
    // wait for crushing to be completed
    case ST1_WAITCRUSHCOMPLETE:
      if (fwCutoffState == SW_ACTIVATED)
      {
        motorStop();

        currentState = ST1_REVERSE;
      }
      break;
    // reverse piston after crushing
    case ST1_REVERSE:
      motorReverse();

      currentState = ST1_WAITREVERSECOMPLETED;
      break;
    // wait for piston to be retracted
    case ST1_WAITREVERSECOMPLETED:
      if (revCutoffState == SW_ACTIVATED)
      {
        motorStop();

        currentState = ST1_WAITCANATTOP;
      }
      break;
  }
}

You can give this a try; I think it will work but have no way of testing. Step 5 from reply #12 has fallen away; the closing of the bottom gate is now done before the top gate is opened.

I have not looked at initial setup of piston and gates; in setup() you can add some code to make sure the piston is retracted and the gates are closed.

If you use delays, do not use them in the beginning of a state but just before a state changes.

This is not perfect code; I would get rid of the delays and use a millis() based approach. It will require a slightly different approach (possibly some different steps).

Let's first see if this does what you need.