Lego Roller Coaster Realistic Automation

Hello all this is my first time doing anything of this scale with an arduino. To get straight to the point, I am trying to realistically automate a LEGO roller coaster model. It seems like similar things have been done before but its as if the coding specifics are a close guarded secret by its creators lol. Basically I want to operate the coaster with realistic user interface. I have worked at a theme park in management for almost 6 years so I have a very above average knowledge of all the different control functions and safety features of a coaster. I am wanting to model something similar to this persons controller minus the block systems and sensors (that will be for a later date) Arduino Mini Coaster Control Panel Explained - YouTube I have mapped out which pins I plan on using on my Mega 2560 and what buttons/ and lights I will be using, but that’s about as far as I can get. I am clueless as to where I should go from here. Any help from the community would be awesome! Like I mentioned before I have no prior arduino experience.

coaster_code.ino (2.85 KB)

jdelacruz1157:
Basically I want to operate the coaster with realistic user interface

You need to tell us the exact functionality that you want to create.

Sorry, but I'm not watching a 17 minute video.

...R
Planning and Implementing a Program

Hi,
Welcome to the forum.

Please read the post at the start of any forum , entitled "How to use this Forum".
OR
http://forum.arduino.cc/index.php/topic,148850.0.html.
Then look down to item #7 about how to post your code.
It will be formatted in a scrolling window that makes it easier to read.

Can you please post a copy of your circuit, in CAD or a picture of a hand drawn circuit in jpg, png?

Interesting project.

Tom.... :slight_smile:

Hi,
If you need extra I/O you only need to use a port expander IC or shield, they usually connect the the I2C comms of the Mega, so take up no more pins than are available.

Tom... :slight_smile:

Here is what I have so far, it only defines what inputs and outputs I wish to use.

The goal of this project is to simulate how a real ride/ roller coaster operates in regards to the controls and user input. (I.e. preventing operation unless all "safety" conditions are met or using a maintenance mode to bypass certain functions or to reset lift stops or emergency stops) I'm fairly certain with the inputs and outputs I should have more than enough pins with a mega but I may be wrong since I am fairly new to this. In regards to the video I posted, yes it is long, but it goes into great depth on what I'm trying to accomplish just on a smaller scale. (just in case what I'm trying to explain is complete nonsense.)

const int dispatchone = 4;     // green momentary pushbutton
const int dispatchtwo = 5;     // green momentary pushbutton
const int estop = 6;           // red pushdown pull up button
const int esr = 7;             // yellow momentary pushbutton
const int liftstop = 8;        // red momentary pushbutton
const int restraints = 9;      // green momentary pushbutton
const int gates = 10;           // two position switch
const int maint = 11;           // 3 position key switch with left being maint mode, middle being auto
const int ghostmode = 12;      // 3 position key switch with right being ghostmode (coaster runs by itself) middle being auto
const int panelenable = 13;    // 2 position key switch with left being off, right being on
const int lockout = 14;        // toggle switch will remove power to all other functions
const int dispatchled = 15;    // 5V LED Without resistor
const int faultalarm = 16;     // small audible alarm controlled via SSR
const int estopled = 17;       // 5V LED Without resistor
const int esrled = 18;         // 5V LED Without resistor
const int liftstopled = 19;    // 5V LED Without resistor
const int Liftmotor = 20;      // Lego motor which will be controlled Via SSR
const int ridestartalarm = 21; // small audible alarm (different sound) controlled via SSR
const int restraintsled = 22;  // 5V LED Without resistor

int buttonState = 0; 
void setup() {
  Serial.begin(9600);
  
  pinMode(dispatchled, OUTPUT);
  pinMode(faultalarm, OUTPUT);
  pinMode(estopled, OUTPUT);
  pinMode(esrled, OUTPUT);
  pinMode(liftstopled, OUTPUT);
  pinMode(Liftmotor, OUTPUT);
  pinMode(ridestartalarm, OUTPUT);
  pinMode(restraintsled, OUTPUT);
 
  pinMode(dispatchone, INPUT_PULLUP);  //dispatchone and dispatchtwo must be used as an andgate to start lift 
  pinMode(dispatchtwo, INPUT_PULLUP);  //dispatchone and dispatchtwo must be used as an andgate to start lift 
  pinMode(estop, INPUT_PULLUP);        //When activated will remove functionality of all functions of the controller and the lift motor
  pinMode(esr, INPUT_PULLUP);          //must be used to reset the estop
  pinMode(liftstop, INPUT_PULLUP);     //when activated will remove functionality to lift motor and dispatch buttons
  pinMode(restraints, INPUT_PULLUP);   //when pressed will release ficticous restraints must be "locked" to start lift motor
  pinMode(gates, INPUT_PULLUP);        //when switched to open will not allow dispatch must be "closed" to start lift motor
  pinMode(maint, INPUT_PULLUP);        //must be enabled to allow inital start up sequence, or to use esr function
  pinMode(ghostmode, INPUT_PULLUP);    //when enabled coaster will cycle constantly without user input
  pinMode(panelenable, INPUT_PULLUP);  //when turned on Must use start up sequence to test all buttons (like a real coaster)
  pinMode(lockout, INPUT_PULLUP);      //When activated will act like an estop
}
void loop() {

  }

I do have the inputs coded as (INPUT_PULLUP) in hopes of avoiding the use of external resistors for the 5V LED's. But I'm not sure if the board's built in resistors are capable of handling that many lights at once.

The internal pullups are not for LEDs. Thieir values are 30K to 50K, way too high for LED current limit resistors.

Sorry I confused myself with my own madness. The Pullup is so I can take the 5V from the board, run that lead to the button or switch, and then run the line from the button/switch back to whatever pin is set to read. It seems to be the "easy" albeit probably not proper way of hooking up input buttons.

Also, I’ve attached my rough diagram on how I wish to wire the control panel to the arduino if you wish to download it.

Image from Reply #7 so we don't have to download it. See this Simple Image Posting Guide

...R

jdelacruz1157:
Here is what I have so far, it only defines what inputs and outputs I wish to use.

The goal of this project is to simulate how a real ride/ roller coaster operates in regards to the controls and user input. (I.e. preventing operation unless all "safety" conditions are met or using a maintenance mode to bypass certain functions or to reset lift stops or emergency stops) I'm fairly certain with the inputs and outputs I should have more than enough pins with a mega but I may be wrong since I am fairly new to this. In regards to the video I posted, yes it is long, but it goes into great depth on what I'm trying to accomplish just on a smaller scale. (just in case what I'm trying to explain is complete nonsense.)

At this stage it would be a good idea to forget about code and just write an English description (a list) of all the parts and what they are to do. Saying that you want to simulate something is not at all specific enough.

Keep in mind that you have a lot of information about this in your head and we don't have that.

Also, the time writing the description will not be wasted - planning and thinking is 80% or more of the time needed to create a successful computer program. If you think carefully about it now it will save wasting twice as much time debugging the project.

And don't worry now about the electrical connections to the Arduino - one way or anther there will be a solution for them.

...R

I apologize if it seems like I'm "jumping the gun" this is my first big arduino project I've ever done. After reading some comments, I agree, I do need to write out everything I want the control system to do. So I have taken the time to vividly explain the functions of the control panel. The purpose of this project is to simulate the user interface of an amusement ride control system. This will be controlling a LEGO roller coaster via a control panel utilizing an Arduino mega. The control panel is designed to have 10 control points: (2) dispatch buttons, Emergency Stop, Emergency Stop Reset (ESR), Lift Stop, Restraints Release, Gates Open/Close, Panel Function Enable, Maintenance Bypass, Ghost Mode, And a lock out- tag out switch. Certain functions can only be used if the proper “safety” criteria are met and will be described below.

PANEL FUNCTION ENABLE- This is a key switch that when in the “on” position will allow the control panel to be used and will flash the control panel lights when in idle. When the ride is turned on: in order to “enable” the panel the MAINTENANCE BYPASS key must be turned on, and all buttons must be pushed at least once. This is called a lamp and function test on a real coaster. (They should flash to indicate which button needs to be pressed).

DISPATCH ONE AND TWO- Two green momentary buttons with LEDs. When pressed in conjunction these buttons will start the ride cycle (turn on the lift motor for 10-15 seconds). When not pressed and with panel power on the lights should flash intermittently. Once pressed to start ride they will stay solid the while the lift is running. In order to start the ride the PANEL ENABLE must be “on”, the GATES must be in the “closed” position, and the RESTRAINTS must be in the “closed” position.

LIFT STOP - A red momentary button with a LED. When in idle, the LED will flash intermittently, when pressed the LED will stay solid until the LIFT STOP has been reset, also when pressed this will cause the ESR to flash rapidly. Once pressed will stop the lift and turn off the LEDs on the Dispatch Buttons. In order to restart the lift’s timed cycle and the LIFT STOP press, the ESR must be pressed. Once pressed panel and ride shall resume normal operation.

EMERGENCY STOP- A red position-maintained button with a LED. When in idle, LED will flash intermittently along with the LIFT STOP LED. When pressed, LED will flash rapidly, all control functions of the ride will lose functionality, and the lift (if running) will stop. In order to reset the EMERGENCY STOP; the button must be pulled back up, the panel must be turned to maintenance mode via the MAINTENANCE BYPASS key switch (which will cause the ESR to flash), then the ESR must be pressed. Once the ESR has been pressed the MAINTENANCE BYPASS must be disabled to resume normal operation.

EMERGECNCY STOP RESET (ESR)- An amber momentary button with an LED. When in idle, this LED will remain off. The LED will only illuminate if it is to indicate an EMERGENCY STOP, LIFT STOP, PANEL ENABLE, or LOCK OUT must be reset.

GATES OPEN/CLOSE- A position maintained switch. When enabled it “pretends” gates are open and will prevent the ride from being dispatched. (Eventually I will actually control the gates in the station with this switch) when closed ride may be dispatched.

RESTRAINTS OPEN/CLOSE- A green momentary button with a LED. When in idle or “restraints closed” position, the LED will remain off and the ride may be dispatched. When pressed the LED with flash intermittently signaling the restraints are “open” and will prevent the ride from being dispatched. To “close” the restraints the button must be pressed again.

MAINTENANCE BYPASS- a position maintained key switch. When enabled it puts the controls into “maintenance mode”. This will allow the ESR to be used if a LIFT STOP, EMERGENCY STOP, or a LOCK OUT needs to be reset. This must also be used to initiate the “lamp test” when the ride is first turned on via the PANEL ENABLE. When off, the ride will be in normal operation mode.
GHOST MODE- A position maintained key switch. When enabled will cause the coaster to continuously run without user input.

LOCK OUT- a position maintained switch. When enabled it will act almost exactly like the EMERGENCY STOP described above. The only difference is the EMERGENCY STOP’s LED will illuminate solid when the lock out is engaged. Reset the same way as the EMERGENCY STOP.
Hopefully this gives an insight to exactly what I want this Arduino to accomplish. Luckily the actual LEGO coaster is on back order and won’t be here for a while so this will give me plenty of time to hopefully get this code close to working order.

Excellent description. If I were the one about to write the code though, I would next convert that to a state diagram. You may find this a useful read.

State* diagrams (and or the transition table as shown on p8 of that link) show all the possible states of a system, and more to the point, how to get from one to another, and under what circumstances such a move may be possible.

So for example you say the ride may not start until the restraints are closed. That means in the "restraints open" state, the despatch button is essentially invisible. Even if it's being pressed, it's ignored: it would actually not be being read in the "restraints open" state.

(*No magic in the word "state", same as in normal English, where a parent might say to a kid "Look at the state of your room, you're not going out until it's tidy" so roomState can be either messy_state or tidy_state. There may be many independent states of course, so roomState as messy_state or tidy_state has nothing to do with homeWorkState as homework_done or homework_not_done yet the kid might only be allowed out if the requirement is homework_done and tidy_state.)

Agree, that description is an excellent basis to get the project off the ground.

I confess that I have managed to implement my projects without making State diagrams but some people here find them very helpful.

Have you got all the switches you plan to use?

If so I suggest you start by writing a short program for one type of switch (on its own) to ensure that you know how to make it do what you want. Then do the same for another type of switch (on its own) until you are satisfied that you can get them all to work. If you need a second switch to cause a reset then, for that test you will obviously need two switches.

Under no circumstances use delay() to manage timing. The functions delay() and delayMicroseconds() block the Arduino until they complete. Have a look at how millis() is used to manage timing without blocking in Several Things at a Time.

And see Using millis() for timing. A beginners guide if you need more explanation.

For the later stages of the project think in terms of using the switches to set the value of variables - for example one might be gatesClosed which could be true or false. Then if some other action can only happen if the gates are closed you can check the value of the variable rather than the switch. This allows you to write the program in pieces that are logically separate from one another and which can be tested separately. See Planning and Implementing a Program

…R

Robin2:
I confess that I have managed to implement my projects without making State diagrams but some people here find them very helpful.

Not that it's anything to "confess" to, in the sense of admitting to something being wrong or nasty. We all do things our own way: I find code almost falls out of a state diagram and it makes the coding simpler (for me).

Main thing, OP, is to take it a step at a time. As Robin2 points out, for example, figure out to read switches outside of the "main" project. And when you do put the switch code into the main project, don't try to do them all at once. Work to some kind of checklist, even if it's a mental one only, and build the project in stages. Implicit in that, is that you keep working versions of code as you go, so when new thing "B" breaks old thing "A" which was working a minute ago, it's easy to revert to a known-good sketch.

And put loads of comments in....

Excellent ideas! I will definitely make a state diagram to better organize everything. I do have the control panel already assembled and wired with the buttons and switches I wish to use so I will also attempt to write an "individual" code for each function and slowly add them together. I will post back here with my findings. Thanks for the helpful tips.

So I have done some research and learned some of the basics on finite state machines. I've done a small test just as a proof of concept and it seems it works (I can get teh LED's to flash how I want them to), however I am having difficulty adapting the basic button debouncing example to work in my code.

//Coaster Panel Automation

int panel_state = 0;

//Pin usage
const int DispatchONE = 4;     // green momentary pushbutton
const int DispatchTWO = 5;     // green momentary pushbutton
const int estop = 6;           // red pushdown pull up button
const int esr = 7;             // yellow momentary pushbutton
const int LiftStop = 8;        // red momentary pushbutton
const int Restraints = 9;      // green momentary pushbutton
const int Gates = 10;           // two position switch
const int Maintenance = 11;           // 3 position key switch with left being maint mode, middle being auto
const int GhostMode = 12;      // 3 position key switch with right being ghostmode (coaster runs by itself) middle being auto
const int PanelEnable = 13;    // 2 position key switch with left being off, right being on
const int Lockout = 14;        // toggle switch will remove power to all other functions
const int DispatchLED = 15;    // 5V LED Without resistor
const int FaultAlarm = 16;     // small audible alarm controlled via SSR
const int estopLED = 17;       // 5V LED Without resistor
const int esrLED = 18;         // 5V LED Without resistor
const int LiftStopLED = 19;    // 5V LED Without resistor
const int LiftMotor = 20;      // Lego motor which will be controlled Via SSR
const int RideStartAlarm = 21; // small audible alarm (different sound) controlled via SSR
const int RestraintsLED = 22;  // 5V LED Without resistor
//Timing

const unsigned long LEDIntervalslow = 800;
const unsigned long LEDIntervalfast = 250;
const unsigned long liftInterval = 15000;
unsigned long previousTime = 0;
int LEDstate_1 = LOW;
int LEDstate_2 = HIGH;
int LiftState = LOW;

//Debouncing
unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers

int buttonState;             // the current reading from the input pin
int lastButtonState = LOW;   // the previous reading from the input pin

void setup() {
    Serial.begin(9600);
  pinMode(DispatchLED, OUTPUT);
  pinMode(FaultAlarm, OUTPUT);
  pinMode(estopLED, OUTPUT);
  pinMode(esrLED, OUTPUT);
  pinMode(LiftStopLED, OUTPUT);
  pinMode(LiftMotor, OUTPUT);
  pinMode(RideStartAlarm, OUTPUT);
  pinMode(RestraintsLED, OUTPUT);
 
  pinMode(DispatchONE, INPUT_PULLUP);  
  pinMode(DispatchTWO, INPUT_PULLUP);  
  pinMode(estop, INPUT_PULLUP);        
  pinMode(esr, INPUT_PULLUP);          
  pinMode(LiftStop, INPUT_PULLUP);     
  pinMode(Restraints, INPUT_PULLUP);   
  pinMode(Gates, INPUT_PULLUP);        
  pinMode(Maintenance, INPUT_PULLUP);        
  pinMode(GhostMode, INPUT_PULLUP);    
  pinMode(PanelEnable, INPUT_PULLUP);  
  pinMode(Lockout, INPUT_PULLUP);
}
void loop() {

unsigned long currentTime = millis();

switch (panel_state) {
  case 0: //Panel Enable State---------------------------------------------------------
    //Reading PanelEnable
    if (digitalRead(PanelEnable) == HIGH) {
      
      // check if enough time has passed to consider it a switch press
      if ((millis() - lastDebounceTime) > debounceDelay) {
        panel_state = 1;
        lastDebounceTime = millis();
       
      }
    } else {
      if (digitalRead(PanelEnable) == LOW){
        panel_state=0;
      }
    }
  break;
  case 1: //Idle_Parked State----------------------------------------------------------
  Serial.println("IDLE");
  //LIGHTS
  if (currentTime - previousTime >= LEDIntervalslow) {
    // save the last time you blinked the LED
    previousTime = currentTime;

    // if the LED is off turn it on and vice-versa:
    if (LEDstate_1 == LOW) {
      LEDstate_1 = HIGH;
    } else {
      LEDstate_1 = LOW;
    }
    if (LEDstate_2 == HIGH){
      LEDstate_2 = LOW;
    } else {
      LEDstate_2 = HIGH;
    }
    // set the LED with the ledState of the variable:
    digitalWrite(estopLED, LEDstate_1);
    digitalWrite(LiftStopLED, LEDstate_1);
    digitalWrite(DispatchLED, LEDstate_2);
  }
  
  break;
  case 2: //Dispatching State----------------------------------------------------------
  
  break;
  case 3: //Gates Open State-----------------------------------------------------------
  
  break;
  case 4: //Restraints Open State------------------------------------------------------

  break; 
  case 5: //ESTOP State----------------------------------------------------------------

  break;
  case 6: // ESTOP Reset State---------------------------------------------------------

  break;
  case 7: // Lift Stop State-----------------------------------------------------------

  break;
  case 8: // Lift Stop Reset State-----------------------------------------------------

  break;
  case 9: //Lock Out State-------------------------------------------------------------

  break;
  case 10: //Lock Out Reset State------------------------------------------------------

  break;
  case 11: //Ghost Mode State----------------------------------------------------------

  break;
 
}

}

First off I would like to apologize for my code being a mess. I'm still working on doing some housekeeping to it. The issue is when the arduino is powered up it automatically jumps to case 1;. Which leads me to believe my debouncing adaptation is not working. It should be if (PanelEnable) = HIGH then it will switch to the next case statement.

case 0: //Panel Enable State---------------------------------------------------------
    //Reading PanelEnable
    if (digitalRead(PanelEnable) == HIGH) {
      
      // check if enough time has passed to consider it a switch press
      if ((millis() - lastDebounceTime) > debounceDelay) {
        panel_state = 1;
        lastDebounceTime = millis();
       
      }
    } else {
      if (digitalRead(PanelEnable) == LOW){
        panel_state=0;
      }
    }
  break;

I'm almost certain I'm missing something somewhere but I haven't been able to figure out what. If I can get a working template for this then I should be in the home stretch to finish this coding project. Can anyone see what I'm doing wrong?

Don't include code to check switches inside the case statement like this

switch (panel_state) {
  case 0: //Panel Enable State---------------------------------------------------------
    //Reading PanelEnable
    if (digitalRead(PanelEnable) == HIGH) {
      
      // check if enough time has passed to consider it a switch press
      if ((millis() - lastDebounceTime) > debounceDelay) {
        panel_state = 1;
        lastDebounceTime = millis();
       
      }
    } else {
      if (digitalRead(PanelEnable) == LOW){
        panel_state=0;
      }
    }
  break;

Instead, have a function to read all the switches and save their values and use the saved values in your case statements. Something like this

switch (panel_state) {
  case 0: //Panel Enable State---------------------------------------------------------
    if (PanelEnableSwitchVal == HIGH) {
      panel_state = 1;
    } 
    else {
      panel_state = 0;
      }
    }
  break;

Which makes me think there may be scope for more simplification.

...R

I'm afraid I'm completely lost now. How can I implement a code to read all functions and save their values?

Robin2:
Instead, have a function to read all the switches and save their values and use the saved values in your case statements.

I understand what you mean by leaving the debouncing code out of the case statements but It would seem like there would be miles of setup code to read all 11 inputs. Everything I've researched seems completely out of my coding abilities.

The code to read the 11 inputs would be in loop() not in setup(). If you use an array to hold the list of switch pins and another for the list of switch states the code to read 11 could be as simple as this

for (byte n = 0; n < numberOfSwitches; n++) {
   switchState[n] = digitalRead(switchPin[n]);
}

You could define a series of variables to allow easy-to-follow access to the array of states - for example

byte panelEnableSwitch = 0;

and

if (switchState[panelEnableSwitch] == HIGH) {

If you learn about structs you could make the code even tidier

…R

PS … I know from personal experience that trying to take in all these new concepts can feel like trying to run out of the way of an express train - but take things slowly and you will get on top of them.