Arduino BARC
(Brilliant Auto Reverse Controller)
d. bodnar  3-08-2017 -- 01-02-2019 -- 12-29-2020

jump to latest photos

Overview
Some years I designed a very nice auto-reverse unit called the BARC (Blinking Auto Reverse Controller) - it is based on a 16F88 PIC processor and slowly accelerates and decelerates a train or trolley on a point-to-point line.  The controller works very well as evidenced by the fact that one has been operating 24/7 on the layout at Pittsburgh's Children's' Hospital since 2009 .  For more information on that unit see:
http://www.trainelectronics.com/autoreverse/basic/NEW_unit.htm

I have been working with Arduino microcontrollers for a few years and decided that it was time to redo the BARC (now renamed the Brilliant Auto Reverse Controller ) for the Arduino.  The hardware is composed of an Arduino Pro Mini or Uno, one of a number of solid state H-bridges, a small but very readable OLED display, a rotary encoder and a few reed switches.

This photo is of the Uno based prototype.  As you can see the wiring is minimal.  The only parts that attach to the Arduino are an H-Bridge (at far left) a 0.9" OLED display and a rotary encoder.

This prototype is based on the Arduino Pro Mini.

Setup 
Before using the BARC you need to set a few parameters including speed, time to run, how you want to deal with station stops and if you want some events to happen somewhat randomly.  To enter the setup mode press the button on the rotary encoder when the first BARC screen is displayed.

You will  be greeted by the first of a number of MENU screens.

Set High Speed: Set a train or trolley on the track and turn the rotary encoder knob to set the speed at which you want the engine to run.  The engine will move at the selected speed so that you can determine a proper speed.  You can reverse the engine by briefly pressing the encoder's button in.  When you have set the speed that you want press the button in for a few seconds to move to the next screen.  This "LONG Button" is used to move from menu screen to menu screen throughout the setup process.  After holding the button for a second or two you will see a confirmation screen that says that the setting has been saved.

Set Low Speed: Now you need to set the low speed for the engine.  This is the speed that the engine will decelerate to as it nears the end of the point-to-point.  Make sure it is not so low that the engine may stop.  Again, a brief press of the button will reverse the engine while a longer press will move to the next step.

Set Run Time: Next use the rotary encoder to set the number of seconds that you want to run the engine in one direction on the point-to-point.  This includes any time that you want the engine to remain stopped the end of the track.  When done long press the button to move on.

Set Time Delay at Station:  If you use station stops you can set how long the train stays at each stop.  This can be from 1 to 100 seconds.  Station stop time can be fixed or random.  When in the Station Stop menu press the button to change the Random setting from zero (off) to one (on).  The random time is selected based on this formula:  Random Time = Time Selected for Station Stop/ 2 + a Randomly Selected portion of 1/2 of the Time Selected.  For example, if you chose a 20 second stop time and you selected random the time at the station would be a minimum of 10 seconds (1/2) and a maximum of the full time (100% of the remaining 10 seconds).

Set Station Stop Skip Percentage:  On the next screen you can select a percentage (between 0% and 100%) that will determine if the train will stop at each station stop or will randomly skip some.  To have the train stop at each station stop turn the knob to 100%.  To have it stop about 1/2 of the time set it to 50, for 1/3 of the time select 33, and so on.  Setting to 0% will skip all station stops.

The train will begin to run with these settings.  All of the settings are saved to memory.  To change any of them just repeat the above. 

Select Deceleration Point: The last setting is the point (based on run time) where you want the engine to decelerate from its high speed to its low speed as it gets close to the end of the point-to-point.  To set this just let the train run back & forth a few times to make sure it has stopped at one end at least once.  As the train gets within a short distance from the diodes at either end briefly press the encoder's button.  This will have the engine decelerate to its low speed as it enters the end.  The time for that deceleration to take place is displayed on the screen.  If you want to change it just press the button at some other point and the new deceleration point will be recorded.

Station Stops
Two station stop inputs are provided.  They connect to digital pins 10 and 11.  One input is used to select station stops while the trolley goes forward and the other when it goes backwards.  For example, lets say you have 5 station stops, numbered 1, 2, 3, 4 and 5.  If you want the trolley to pause at stop 2 and stop 3 while it is going forwards connect the reed switches from those locations to the forward input.   To have it pause at 1 and 4 when on the return trip connect those reed switches to the backward input.  If you want the trolley to always stop at station stop 5 (regardless of the direction of travel) connect the reed switch from station 5 to both inputs. 

The use of two inputs helps to more precisely position the stop as the engine always stops a bit beyond the reed switch.  If you want a trolley to stop in the center of a station platform while going either way place a reed switch a foot or so short of the station and wire one to the forward input and one to the backward input.  You can then adjust the speed and/or reed switch position to get a very repeatable stop by the center of the platform.

Operation
Connect the power output from the H-bridge to a point in the track between the two diodes. 

This drawing shows the correct wiring for a G-scale layout.  Note that the diode has its band to the right.  For an HO scale layout the diodes must be installed with the opposite polarity, that is with the band on the diode to the left.

This photo shows a section of HO track with the diode's band to the left.

Place as many reed switches as you wish for station stops.  Note that there are two reed switch inputs (SW1 and SW2 in the schematic) - one is for station stops when the engine is going in one direction and the other is for when the engine is reversed and going the other way.  You can, of course, connect some or all reed switches to both inputs so that the engine stops when going out and when coming back.

Schematic
The schematic shows the Arduino Pro Mini along with three different H-Bridge units.

VNH2SP30 - 16 volts at 30 amps - from eBay or Amazon - this device works very well but is limited to 16 volts.  It is ideal for smaller scales but 16 volts may be too low for large scale use.  Its pins include a single PWM pin that is sent an analog PWM signal.  Pins 5 and 6 on the Arduino connect to ina and inb on the h-bridge.  When ina is high and the other low the output spins the motor in one direction.  When ina is low and the other high the motor spins in the other direction.  Arduino pin 7 connects to en (Enable) - when this pin is high the power is on, when low  it is off.  Arduino pin a0 connects to the cs output on the h-bridge.  It puts out a signal that can be used by the Arduino to measure power output.  This pin is not yet implemented in the software.

 

 BT-2 - 27 volts at 43 amps from eBay or Amazon - this device is well suited for large scale use as it works on voltages as high as 27 volts and can supply 43 amps!  This device has 8 control pins.  It has two PWM inputs.  When rpwm receives PWM pulses and lpwm is held low the motor spins in one direction.  When rpwm is held low and lpwm receives PWM the motor spins in the other direction.

miniIBT - 30 volts at 5 amps from eBay or Amazon - This H-Bridge works in the same manner as the BT-2.

Most of my development was done with the red VNH2SP30 board.  Unless you are working with large scale trains which need more than 16 volts it should serve your needs nicely.

The OLED display connects to +5 volts, ground and pins A4 and A5.  On some Pro Mini's these pins are not on the edge of the unit but on the inner portion.  On the Uno they are easily identifiable.

The rotary encoder works best if two small capacitors are soldered directly to the output pins as shown.  The button goes to digital pin 12 which is held high with a 10K resistor.  Note that the encoder output pins go to digital pins 2 and 3.  This cannot be changed as those pins support interrupts which are used in the sketch.

Two or more reed switches can be connected to digital pins 10 and 11.  If you use more than one they should be wired in parallel, not in series.

 
Software
The program has to be set up somewhat differently depending on which H-Bridge you use.  
If you are using the red VNH2SP30 H-bridge one procedure is needed.  If you use either of the other two H-Bridges the commands used are different.
To simplify this process for you I have added code that performs a conditional compilation.  If you leave the line that reads
#define motorType 
as it is the program will compile for the VNH2SP30.  If you comment out that line so that is looks like this:
//#define motorType 
the program will compile so that either of the other two H-Bridges will work.  The conditional sections start out with
#if defined (motorType)
and end with
#endif
TEST CODE FOR ENCODER & H-Bridge
file name:  BARC-Interrupt-encoder_motor_OLED_-test-V3-7
 
/*  d. bodnar - 2-15-2017
     working fairly well
     Conditional Compilation based on #define motorType
     comment it out for miniIST 5 amp board
     remove comment /// for 15 volt red board

*/

//#define unsigned char byte
char Ver[ ] = "3.7";
#define motorType  // comment this out for miniIST board - leave active for red board
int menuCode = 0;
#include<EEPROM.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Entropy.h>
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
int n = 1;
int i = 0;
#define ledPin    13       // the number of the LED pin
#define buttonPin 12   // 8; // button on rotary
static int pinA = 2; // Our first hardware interrupt pin is digital pin 2  - don't change
static int pinB = 3; // Our second hardware interrupt pin is digital pin 3 - don't change
volatile byte aFlag = 0; //
volatile byte bFlag = 0; //
volatile byte encoderPos = 0; //
volatile byte oldEncPos2 = 0; //
volatile byte oldEncPos = 0; //
volatile byte reading = 0; //
int old_pos = encoderPos;
int dir = 0; // direction
int buttonState = 0;
unsigned long currentTime;
//unsigned long lastTime;
unsigned long interruptTime;
unsigned long holdTime;// check to see if it is held for 1 sec +
int FwdRev = 0; // Rotary- 0 for backwards, 1 for forwards
int b0;
int b1;
int b2;
int accelDecelRate = 10; // speed of accelerate & decelrate
// pins 4, 5, 6, 7 are available for motor board
#define motorPWM  9  // only on PWM: 3, 5, 6, 9, 10, and 11.
#if defined (motorType)
// for red single VNH2SP30 board
#define motor1    5
#define motor2   6
#define enable   7
#define currentDraw  A0
//#define motorPWM  9  // only on PWM: 3, 5, 6, 9, 10, and 11.
#else
//// works with L6201P board
#define motor1 = 5;
#define motor2 = 6;
#define enable = 7;
#define currentDraw = A0;
#endif
#define Station1   10  // station stop trigger 1
#define Station2   11  // station stop trigger 2
int oldSp = 999;
int timeToDecel = 5;
int timeToStop = 10;
boolean decelFlag = 0;
int speedFast = 220;
int speedSlow = 64;
boolean exitFlag = 0;
int stationStopTime = 0;
int stationStopTimeSave = 0;
int stationStopPercent = 0;
int stationStopCount = 0;
int stationStopDone = 0;
int laps = 0;
int SScount = 0;
boolean randomSS = 0; // flag for random @ Station Stop (1) or not (0)
boolean randomSSchangeFlag = 0;
void setup() {
  Entropy.Initialize();
  pinMode( Station1, INPUT_PULLUP );
  pinMode( Station2, INPUT_PULLUP );
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // I2C addr 0x3D (for the 128x64)
  display.clearDisplay();
  display.display();
  //currentTime = millis();
  // lastTime = currentTime;
  pinMode(motorPWM, OUTPUT);
  pinMode(motor1, OUTPUT);
  pinMode(motor2, OUTPUT);
  pinMode(enable, OUTPUT);
  pinMode(ledPin, OUTPUT);
  pinMode(pinA, INPUT_PULLUP);
  pinMode(pinB, INPUT_PULLUP);
  pinMode(buttonPin, INPUT_PULLUP);
  attachInterrupt(0, PinA, RISING);
  attachInterrupt(1, PinB, RISING);
  Serial.begin(115200);
  Serial.print("BARC " );   Serial.println(Ver);
  display.setTextSize(2);
  display.setCursor(30, 0);
  display.setTextColor(WHITE);
  display.print("BARC");
  display.display();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 16);
  display.print("Version ");
  display.println(Ver);
  display.setCursor(0, 24);
  display.print ("BUTTON for MENU");
  display.display();
  digitalWrite(enable, HIGH);
  currentTime = millis();
  readEEPROM();
  do {
    if (digitalRead(buttonPin) == 0) {
      //     Serial.println("MENU SELECTED ON STARTUP");
      doMenu();
      break;
    }
  }
  while (millis() - currentTime <= 1500);

  digitalWrite(enable, HIGH);
  display.clearDisplay();
  updateDisplay();
  // delay(1000);
  currentTime = millis();
  accelerate();
}

//////////////////////////MAIN LOOP
void loop() {
  display.clearDisplay();
  updateDisplay();
  checkStationStop();
  checkTimeToSlowDown();
  checkTimeExpired();
  oldSp = encoderPos;
  setMotorSpeed();
  buttonState = digitalRead(buttonPin);  // new deceleration time?
  if (buttonState == LOW) {
    Serial.print ("LOW "); Serial.println((millis() - currentTime) / 1000);
    timeToDecel = ((millis() - currentTime) / 1000);
    writeEEPROM();
  }
}

void checkTimeToSlowDown() {
  if ((millis() - currentTime) / 1000 >= timeToDecel && decelFlag == 0) {
    decelerate();
    decelFlag = 1;
  }
}

void checkTimeExpired() {
  if ((millis() - currentTime) / 1000 >= timeToStop) {
    dir = !dir;
    decelFlag = 0;
    accelerate();
    currentTime = millis();
    laps++;

  }
}

void checkStationStop() {//button hit?  direction OK?  if so Do Station Stop
  if ((digitalRead(Station1) == 0 && dir == 0) || (digitalRead(Station2) == 0 && dir == 1) ) {
    unsigned long tempTime = millis(); // store time to skip timing here
    //  Serial.println("");
    //   Serial.print(" before   CT=   "); Serial.print((currentTime - millis()) / 100);
    //    Serial.println("");
    //    Serial.print("  millisBefore= "); Serial.println(millis() / 100);
    unsigned long tempTime2 = (millis() / 100);
    doStationStop();
    //   Serial.print("  millisAfter=  "); Serial.println(millis() / 100);
    //   Serial.print("  Diffv After=  "); Serial.println((millis() / 100) - tempTime2 );

    currentTime = currentTime  - tempTime + millis() ; //- tempTime - 500;

    //   Serial.print("          CT=  "); Serial.print(currentTime / 100);
    //Serial.println("");

  }
}

void doStationStop() {
  //  Serial.print(" percent = ");
  //  Serial.print(stationStopPercent);
  //  Serial.println(millis() / 100);
  int ssGoNoGo =  Entropy.random(101); // 0--100
  // Serial.println(millis() / 100);
  /// Serial.print("  ssGoNoGo = ");
  /// Serial.println(ssGoNoGo);
  stationStopCount++;
  if (ssGoNoGo <= stationStopPercent && stationStopPercent != 0) { // none if zero, all if 100%
    stationStopDone++;
    Serial.print(" Station Stops ");
    Serial.print(stationStopDone);
    Serial.print(" / ");
    Serial.print(stationStopCount);
    Serial.print(" = ");
    Serial.print(((float)stationStopDone / (float)stationStopCount) * 100);
    Serial.println("%");
    unsigned long tempTime = millis(); // store time to skip timing here
    decelerate();
    analogWrite(motorPWM, 0); // full stop
    analogWrite(motor1, 0); // full stop
    analogWrite(motor2, 0); // full stop
    stationStopTime  = stationStopTimeSave ; // keep a copy before becoming random
    if (randomSS == 1) {
      stationStopTime = Entropy.random(stationStopTimeSave / 2) + stationStopTimeSave / 2;
    }
    display.setTextColor(BLACK, WHITE); // 'inverted' text
    SScount++;
    for (int xx = 0; xx < stationStopTime; xx++) {
      Serial.println(millis() / 100);
      display.clearDisplay();
      updateDisplay();
      ////     int tempDelayValue =  16 * ((11 - accelDecelRate)  * (speedFast - speedSlow)); //10-1000, 5=6000, 1=10000
      int tempDelayValue =  (speedFast / accelDecelRate * 10) ;   // for accel at 10ms each in FOR loop
      tempDelayValue = tempDelayValue / stationStopTime * 2; // split it for each second pause and double for accel/decel
      tempDelayValue = 1000 - tempDelayValue;
      Serial.print("tdv= ");
      Serial.println(tempDelayValue);
      delay(tempDelayValue);
      Serial.println(millis() / 100);
    }
    //    stationStopTime = stationStopTimeSave ; ////+1;  // add 1 to make up for accel / decel delays
    display.setTextColor(WHITE, BLACK); // 'inverted' text
    accelerate();

    delay(500); // in case the engine is sitting on the switch time to get away from it
  }
  else (delay(500));  // time to pass by reed switch
}

void updateDisplay() {  //0,0=line 1; 0,8=line 2; 0,16=line 3; 0,24=line4
  display.setCursor(0, 0);
  if (dir == 0) {
    display.print("FWD");
  }
  else display.print("BKW");
  display.print(" Max ");
  display.print(speedFast);
  display.print(" Min ");
  display.print(speedSlow);
  display.setCursor(0, 8);
  display.print("Time ");
  display.print((millis() - currentTime) / 1000);
  display.print("/");
  display.print(timeToStop);
  display.print(" Decel@ ");
  display.print(timeToDecel);
  display.setCursor(0, 16);
  display.print("StStop ");
  if (randomSS == 0) {
    display.print("Nor");
  }
  else display.print("Rnd");
  display.print(" Time ");
  display.print(stationStopTime);
  display.setCursor(0, 24);
  display.print("SS ");
  display.print(stationStopPercent);
  display.print("% ");
  display.print(SScount);
  display.print("   <> ");
  display.print(laps);
  display.display();
}

void accelerate() {
  if (decelFlag == 0) {
    encoderPos = speedFast;
  }
  else {
    encoderPos = speedSlow;
  }
  for (i = 0; i < encoderPos; i = i + accelDecelRate) {
    if (dir == 0) {
#if defined (motorType)
      digitalWrite(motor1, LOW); // pull pin low
      digitalWrite(motor2, HIGH);
      analogWrite(motorPWM, i);
#else
      analogWrite(motor1, 0); // pull pin low
      analogWrite(motor2, i);
#endif
    }
    else {
#if defined (motorType)
      digitalWrite(motor2, LOW); // pull pin low
      digitalWrite(motor1, HIGH);
      analogWrite(motorPWM, i);
#else
      analogWrite(motor1, i); // pull pin low
      analogWrite(motor2, 0);
#endif
    }
    delay (10);
  }
}

void decelerate() {
  if (decelFlag == 1) {
    encoderPos = speedSlow;
  }
  else encoderPos = speedFast;

  for (i = encoderPos; i > speedSlow; i = i - accelDecelRate) {
    if (dir == 0) {
#if defined (motorType)
      digitalWrite(motor1, LOW); // pull pin low
      digitalWrite(motor2, HIGH);
      analogWrite(motorPWM, i);
#else
      analogWrite(motor1, 0); // pull pin low
      analogWrite(motor2, i);
#endif
    }
    else {
#if defined (motorType)
      digitalWrite(motor2, LOW); // pull pin low
      digitalWrite(motor1, HIGH);
      analogWrite(motorPWM, i);
#else
      analogWrite(motor1, i); // pull pin low
      analogWrite(motor2, 0);
#endif
    }
    delay (10);
  }
}

void setMotorSpeed() {
  if (dir == 0) {
#if defined (motorType)
    digitalWrite(motor1, LOW); // pull pin low
    digitalWrite(motor2, HIGH);
    analogWrite(motorPWM, i);
#else
    analogWrite(motor1, 0); // pull pin low
    analogWrite(motor2, i);
#endif
  }
  else {
#if defined (motorType)
    digitalWrite(motor2, LOW); // pull pin low
    digitalWrite(motor1, HIGH);
    analogWrite(motorPWM, i);
#else
    analogWrite(motor1, i); // pull pin low
    analogWrite(motor2, 0);
#endif
  }
}

void checkRotaryShort() {
  if (oldEncPos != encoderPos) {
    if (menuCode == 3) encoderPos = constrain(encoderPos, 1, 10);
    if (menuCode == 6) encoderPos = constrain(encoderPos, 0, 100);
    interruptTime = millis();
    int x = old_pos - encoderPos;
    oldEncPos = encoderPos;
    i = encoderPos;
  }
}

void writeEEPROM() {
  EEPROM.write(0, speedFast );
  EEPROM.write(1, speedSlow);
  EEPROM.write(2, timeToStop);
  EEPROM.write(3, accelDecelRate);
  EEPROM.write(4, randomSS);
  EEPROM.write(5, stationStopTime);
  EEPROM.write(6, timeToDecel);
  EEPROM.write(7, stationStopPercent);
}

void readEEPROM() {
  speedFast = EEPROM.read(0);
  speedSlow = EEPROM.read(1);
  timeToStop = EEPROM.read(2);
  accelDecelRate = EEPROM.read(3);
  accelDecelRate = 10;  // no longer a menu item
  randomSS = EEPROM.read(4);
  stationStopTime = EEPROM.read(5);
  timeToDecel = EEPROM.read(6);
  stationStopPercent = EEPROM.read(7);
  stationStopTimeSave = stationStopTime;
  Serial.println( speedFast );
  Serial.println( speedSlow);
  Serial.println( timeToStop);
  Serial.println( accelDecelRate );
  Serial.println( randomSS );
  Serial.println( stationStopTime );
  Serial.println( timeToDecel);
  Serial.println( stationStopPercent );
}

void doMenu() {
  display.clearDisplay();
  display.display();
  menuCode = 1;  getTopSpeed();
  menuCode = 2;  getLowSpeed();
  // menuCode = 3;  getAccelDecelRate();
  menuCode = 4;  getRunTime();
  menuCode = 5;  getStationStopTime();
  menuCode = 6;  getStationStopPercentage();
  writeEEPROM();
  readEEPROM();
}

void getStationStopPercentage() {
  encoderPos = stationStopPercent;
  digitalWrite(enable, LOW); // no motor here
  getCommonStuff();
  exitFlag = 0;
  stationStopPercent = i;
  saveData(i);
  exitFlag = 0;
}

void getStationStopTime() {
  encoderPos = stationStopTime;
  digitalWrite(enable, LOW); // no motor here
  getCommonStuff();
  exitFlag = 0;
  stationStopTime = i;
  saveData(i);
  exitFlag = 0;
}

void getTopSpeed() {
  encoderPos = speedFast;
  digitalWrite(enable, HIGH);
  getCommonStuff();
  speedFast = i;
  digitalWrite(enable, LOW);
  saveData(i);
  i = 0; // reset to display next routines screen without turning rotary
  exitFlag = 0;
}

void getLowSpeed() {
  encoderPos = speedSlow;
  digitalWrite(enable, HIGH);
  getCommonStuff();
  if (i >= speedFast) {
    i = speedFast;
    encoderPos = speedSlow;
  }
  speedSlow = i;
  saveData(i);
  exitFlag = 0;
}

void getAccelDecelRate() {
  encoderPos = accelDecelRate;
  digitalWrite(enable, HIGH);
  getCommonStuff();
  encoderPos = constrain(encoderPos, 1, 10);
  exitFlag = 0;
  accelDecelRate = encoderPos;
  saveData(i);
}

void getRunTime() {
  encoderPos = timeToStop;
  digitalWrite(enable, LOW); // no motor here
  getCommonStuff();
  exitFlag = 0;
  timeToStop = i;
  saveData(i);
}

int saveData(int i) {
  display.clearDisplay();
  display.setCursor(0, 0);
  display.print("Data Saved ");
  display.setCursor(0, 8);
  display.print("New Value = ");
  display.print(encoderPos);
  display.display();
  delay(1000);
}

void getCommonStuff() {
  exitFlag = 0;
  buttonState = HIGH;
  do {
    buttonState = digitalRead(buttonPin);
    if (buttonState == LOW) {
      holdTime = millis();
      do {
        buttonState = digitalRead(buttonPin);
        if (buttonState == HIGH) {
          break; // exit from quick button if let go
        }
        if (millis() - holdTime >= 500) { // held for 1 sec+
          exitFlag = 1;
          break;
        }
      }
      while (0 == 0);
      if (exitFlag == 0) {
        dir = !dir;
      }
      setMotorSpeed();
      delay(300);
      if (menuCode == 5) {
        if (exitFlag == 0) {
          randomSS = !randomSS;
        }
        Serial.print("rndSS = "); Serial.println(randomSS);
        randomSSchangeFlag = 1;
      }

      if (menuCode == 3) {
        if (exitFlag == 0) {
        }
        accelDecelRate = encoderPos;
        if (exitFlag == 0) {
          doAccelDecelDemo();

        }

        encoderPos = accelDecelRate;
      }
    }
    oldEncPos2 = i;
    checkRotaryShort();
    if (encoderPos != oldEncPos2) {
      display.clearDisplay();
      setMotorSpeed();
    }
    display.setCursor(0, 0);
    if ( menuCode == 1) {
      display.print("Turn for TOP Speed");
      display.setCursor(0, 8);
      display.print("  Button to Reverse");
    }
    if ( menuCode == 2) {
      display.print("Turn for LOW Speed");
      display.setCursor(0, 8);
      display.print("  Button to Reverse");
    }
    if ( menuCode == 3) {
      display.print("Turn for AccDec Rate");
      display.setCursor(0, 8);
      display.print("  Button to do Test"); // accel, decel, rev, repeat
    }
    if ( menuCode == 4) {
      display.print("Turn for Run Time");
      display.setCursor(0, 8);
      display.print("  in Seconds");
    }
    if ( menuCode == 5) {
      if (randomSSchangeFlag == 1) {
        randomSSchangeFlag = 0;
        display.clearDisplay();
      }
      display.print("Turn for St Stop Time");
      display.setCursor(0, 8);
      display.print("Button for RANDOM =   ");
      display.setCursor(115, 8);
      display.print(randomSS);
    }
    if ( menuCode == 6) {
      display.print("Turn for SStop %");
      display.setCursor(0, 8);
      display.print("100=all, 0=none");
      display.setCursor(115, 8);
    }

    display.setCursor(0, 16);
    display.print("LONG Button=Done");
    display.setCursor(0, 24);
    display.display();
    display.setCursor(72, 24);
    display.print(encoderPos);
    display.display();
  }
  while (exitFlag == 0);
  writeEEPROM();
}

void doAccelDecelDemo() { // accel, delay 1000 or so, decel / reverse / repeat once
  //Serial.println("at Accel Decel Demo");
  digitalWrite(enable, HIGH);
  dir = !dir;
  accelerate();
  delay(500);
  decelerate();
  digitalWrite(enable, LOW);
  delay(500);
  dir = !dir;
  digitalWrite(enable, HIGH);
  accelerate();
  delay(500);
  decelerate();
  digitalWrite(enable, LOW);
}

void PinA() {
  cli(); //stop interrupts happening before we read pin values
  reading = PIND & 0xC;
  if (reading == B00001100 && aFlag) {
    if (encoderPos >= 1) {
      encoderPos --;
    }//decrement the encoder's position count
    FwdRev = 0;
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00000100) bFlag = 1;
  sei(); //restart interrupts
}

void PinB() {
  cli(); //stop interrupts happening before we read pin values
  reading = PIND & 0xC;
  if (reading == B00001100 && bFlag) {
    if (encoderPos <= 249) {
      encoderPos ++;
    } //increment the encoder's position count
    FwdRev = 1;
    bFlag = 0;
    aFlag = 0;
  }
  else if (reading == B00001000) aFlag = 1;
  sei(); //restart interrupts
}


REVISED for Run Once Function - most changes in void checkTimeExpired() {
/*  d. bodnar - 2-15-2017

     RunOnce added 01-02-2019

     working fairly well
     Conditional Compilation based on #define motorType
     comment it out for miniIST 5 amp board
     remove comment /// for 15 volt red board

  12-08-2018 - working well with new boards with red H-bridge & 5 amp unit
*/

//Run once pin 4
// pull high weak pullup
// pull low permanently to run normally (no run once)
// leave open to run back & forth onece - hit again to restart

//#define unsigned char byte
char Ver[ ] = "4.1 ";
//#define motorType  // comment this out for miniIST board - leave active for single mosfet red board
int menuCode = 0;
#include<EEPROM.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Entropy.h>
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
int n = 1;
int i = 0;
#define ledPin    13       // the number of the LED pin
#define buttonPin 12   // 8; // button on rotary

#define runOnce 4 // use pin 4

static int pinA = 2; // Our first hardware interrupt pin is digital pin 2  - don't change
static int pinB = 3; // Our second hardware interrupt pin is digital pin 3 - don't change
volatile byte aFlag = 0; //
volatile byte bFlag = 0; //
volatile byte encoderPos = 0; //
volatile byte oldEncPos2 = 0; //
volatile byte oldEncPos = 0; //
volatile byte reading = 0; //
int old_pos = encoderPos;
int dir = 0; // direction
int buttonState = 0;
unsigned long currentTime;
//unsigned long lastTime;
unsigned long interruptTime;
unsigned long holdTime;// check to see if it is held for 1 sec +
int FwdRev = 0; // Rotary- 0 for backwards, 1 for forwards
int b0;
int b1;
int b2;
int accelDecelRate = 10; // speed of accelerate & decelrate
// pins 4, 5, 6, 7 are available for motor board
#define motorPWM  9  // only on PWM: 3, 5, 6, 9, 10, and 11.
#if defined (motorType)
// for red single VNH2SP30 board
#define motor1  5
#define motor2  6
#define enable  7
#define currentDraw  A0
//#define motorPWM  9  // only on PWM: 3, 5, 6, 9, 10, and 11.
#else
//// works with L6201P board
#define motor1  5
#define motor2  6
#define enable  7
#define currentDraw   A0
#endif
#define Station1   10  // station stop trigger 1
#define Station2   11  // station stop trigger 2
int oldSp = 999;
int timeToDecel = 5;
int timeToStop = 10;
boolean decelFlag = 0;
int speedFast = 220;
int speedSlow = 64;
boolean exitFlag = 0;
int stationStopTime = 0;
int stationStopTimeSave = 0;
int stationStopPercent = 0;
int stationStopCount = 0;
int stationStopDone = 0;
int laps = 0;
int xx = 0;
int SScount = 0;
boolean randomSS = 0; // flag for random @ Station Stop (1) or not (0)
boolean randomSSchangeFlag = 0;
void setup() {
  Entropy.Initialize();
  pinMode( Station1, INPUT_PULLUP );
  pinMode( Station2, INPUT_PULLUP );
  pinMode( runOnce, INPUT_PULLUP);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // I2C addr 0x3D (for the 128x64)
  display.clearDisplay();
  display.display();
  //currentTime = millis();
  // lastTime = currentTime;
  pinMode(motorPWM, OUTPUT);
  pinMode(motor1, OUTPUT);
  pinMode(motor2, OUTPUT);
  pinMode(enable, OUTPUT);
  pinMode(ledPin, OUTPUT);
  pinMode(pinA, INPUT_PULLUP);
  pinMode(pinB, INPUT_PULLUP);
  pinMode(buttonPin, INPUT_PULLUP);
  attachInterrupt(0, PinA, RISING);
  attachInterrupt(1, PinB, RISING);
  Serial.begin(115200);
  Serial.print("BARC " );   Serial.println(Ver);
  display.setTextSize(2);
  display.setCursor(30, 0);
  display.setTextColor(WHITE);
  display.print("BARC");
  display.display();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 16);
  display.print("Version ");
  display.println(Ver);
  display.setCursor(0, 24);
  display.print ("BUTTON for MENU");
  display.display();
  digitalWrite(enable, HIGH);
  currentTime = millis();
  readEEPROM();
  do {
    if (digitalRead(buttonPin) == 0) {
      //     Serial.println("MENU SELECTED ON STARTUP");
      doMenu();
      break;
    }
  }
  while (millis() - currentTime <= 1500);

  digitalWrite(enable, HIGH);
  display.clearDisplay();
  updateDisplay();
  // delay(1000);
  currentTime = millis();
  accelerate();
}

//////////////////////////MAIN LOOP
void loop() {
  //  int RUNONCE = digitalRead(runOnce);
  //  Serial.println(RUNONCE);
  display.clearDisplay();
  updateDisplay();
  checkStationStop();
  checkTimeToSlowDown();
  checkTimeExpired();
  oldSp = encoderPos;
  setMotorSpeed();
  buttonState = digitalRead(buttonPin);  // new deceleration time?
  if (buttonState == LOW) {
    Serial.print ("LOW "); Serial.println((millis() - currentTime) / 1000);
    timeToDecel = ((millis() - currentTime) / 1000);
    writeEEPROM();
  }
}

void checkTimeToSlowDown() {
  if ((millis() - currentTime) / 1000 >= timeToDecel && decelFlag == 0) {
    decelerate();
    decelFlag = 1;
  }
}

void checkTimeExpired() {
  if ((millis() - currentTime) / 1000 >= timeToStop) {
    dir = !dir;
    //
    Serial.println(dir);
    if (dir == 0) {
      int  runOnceValue = digitalRead (runOnce);
      if (runOnceValue == 1) { // open/off button
        //stay here till runOnce hit
        Serial.println("PAUSING");
        do {
          xx++;
          if ( xx <= 15000) {
            digitalWrite(LED_BUILTIN, HIGH);
          }
          else {
            if (xx >= 15000) {
              digitalWrite(LED_BUILTIN, LOW);
              if (xx >= 30000) xx = 0;
            }
          }
          runOnceValue = digitalRead (runOnce);
          //  Serial.print(runOnceValue);

        } while (runOnceValue == 1);
        Serial.println("button pushed");
        delay (200);
      }
    }
    digitalWrite(LED_BUILTIN, LOW);
    decelFlag = 0;
    accelerate();
    currentTime = millis();
    laps++;

  }
}

void checkStationStop() {//button hit?  direction OK?  if so Do Station Stop
  if ((digitalRead(Station1) == 0 && dir == 0) || (digitalRead(Station2) == 0 && dir == 1) ) {
    unsigned long tempTime = millis(); // store time to skip timing here
    //  Serial.println("");
    //   Serial.print(" before   CT=   "); Serial.print((currentTime - millis()) / 100);
    //    Serial.println("");
    //    Serial.print("  millisBefore= "); Serial.println(millis() / 100);
    unsigned long tempTime2 = (millis() / 100);
    doStationStop();
    //   Serial.print("  millisAfter=  "); Serial.println(millis() / 100);
    //   Serial.print("  Diffv After=  "); Serial.println((millis() / 100) - tempTime2 );

    currentTime = currentTime  - tempTime + millis() ; //- tempTime - 500;

    //   Serial.print("          CT=  "); Serial.print(currentTime / 100);
    //Serial.println("");

  }
}

void doStationStop() {
  //  Serial.print(" percent = ");
  //  Serial.print(stationStopPercent);
  //  Serial.println(millis() / 100);
  int ssGoNoGo =  Entropy.random(101); // 0--100
  // Serial.println(millis() / 100);
  /// Serial.print("  ssGoNoGo = ");
  /// Serial.println(ssGoNoGo);
  stationStopCount++;
  if (ssGoNoGo <= stationStopPercent && stationStopPercent != 0) { // none if zero, all if 100%
    stationStopDone++;
    Serial.print(" Station Stops ");
    Serial.print(stationStopDone);
    Serial.print(" / ");
    Serial.print(stationStopCount);
    Serial.print(" = ");
    Serial.print(((float)stationStopDone / (float)stationStopCount) * 100);
    Serial.println("%");
    unsigned long tempTime = millis(); // store time to skip timing here
    decelerate();
    analogWrite(motorPWM, 0); // full stop
    analogWrite(motor1, 0); // full stop
    analogWrite(motor2, 0); // full stop
    stationStopTime  = stationStopTimeSave ; // keep a copy before becoming random
    if (randomSS == 1) {
      stationStopTime = Entropy.random(stationStopTimeSave / 2) + stationStopTimeSave / 2;
    }
    display.setTextColor(BLACK, WHITE); // 'inverted' text
    SScount++;
    for (int xx = 0; xx < stationStopTime; xx++) {
      Serial.println(millis() / 100);
      display.clearDisplay();
      updateDisplay();
      ////     int tempDelayValue =  16 * ((11 - accelDecelRate)  * (speedFast - speedSlow)); //10-1000, 5=6000, 1=10000
      int tempDelayValue =  (speedFast / accelDecelRate * 10) ;   // for accel at 10ms each in FOR loop
      tempDelayValue = tempDelayValue / stationStopTime * 2; // split it for each second pause and double for accel/decel
      tempDelayValue = 1000 - tempDelayValue;
      Serial.print("tdv= ");
      Serial.println(tempDelayValue);
      delay(tempDelayValue);
      Serial.println(millis() / 100);
    }
    //    stationStopTime = stationStopTimeSave ; ////+1;  // add 1 to make up for accel / decel delays
    display.setTextColor(WHITE, BLACK); // 'inverted' text
    accelerate();

    delay(500); // in case the engine is sitting on the switch time to get away from it
  }
  else (delay(500));  // time to pass by reed switch
}

void updateDisplay() {  //0,0=line 1; 0,8=line 2; 0,16=line 3; 0,24=line4
  display.setCursor(0, 0);
  if (dir == 0) {
    display.print("FWD");
  }
  else display.print("BKW");
  display.print(" Max ");
  display.print(speedFast);
  display.print(" Min ");
  display.print(speedSlow);
  display.setCursor(0, 8);
  display.print("Time ");
  display.print((millis() - currentTime) / 1000);
  display.print("/");
  display.print(timeToStop);
  display.print(" Decel@ ");
  display.print(timeToDecel);
  display.setCursor(0, 16);
  display.print("StStop ");
  if (randomSS == 0) {
    display.print("Nor");
  }
  else display.print("Rnd");
  display.print(" Time ");
  display.print(stationStopTime);
  display.setCursor(0, 24);
  display.print("SS ");
  display.print(stationStopPercent);
  display.print("% ");
  display.print(SScount);
  display.print("   <> ");
  display.print(laps);
  display.display();
}

void accelerate() {
  if (decelFlag == 0) {
    encoderPos = speedFast;
  }
  else {
    encoderPos = speedSlow;
  }
  for (i = 0; i < encoderPos; i = i + accelDecelRate) {
    if (dir == 0) {
#if defined (motorType)
      digitalWrite(motor1, LOW); // pull pin low
      digitalWrite(motor2, HIGH);
      analogWrite(motorPWM, i);
#else
      analogWrite(motor1, 0); // pull pin low
      analogWrite(motor2, i);
#endif
    }
    else {
#if defined (motorType)
      digitalWrite(motor2, LOW); // pull pin low
      digitalWrite(motor1, HIGH);
      analogWrite(motorPWM, i);
#else
      analogWrite(motor1, i); // pull pin low
      analogWrite(motor2, 0);
#endif
    }
    delay (10);
  }
}

void decelerate() {
  if (decelFlag == 1) {
    encoderPos = speedSlow;
  }
  else encoderPos = speedFast;

  for (i = encoderPos; i > speedSlow; i = i - accelDecelRate) {
    if (dir == 0) {
#if defined (motorType)
      digitalWrite(motor1, LOW); // pull pin low
      digitalWrite(motor2, HIGH);
      analogWrite(motorPWM, i);
#else
      analogWrite(motor1, 0); // pull pin low
      analogWrite(motor2, i);
#endif
    }
    else {
#if defined (motorType)
      digitalWrite(motor2, LOW); // pull pin low
      digitalWrite(motor1, HIGH);
      analogWrite(motorPWM, i);
#else
      analogWrite(motor1, i); // pull pin low
      analogWrite(motor2, 0);
#endif
    }
    delay (10);
  }
}

void setMotorSpeed() {
  if (dir == 0) {
#if defined (motorType)
    digitalWrite(motor1, LOW); // pull pin low
    digitalWrite(motor2, HIGH);
    analogWrite(motorPWM, i);
#else
    analogWrite(motor1, 0); // pull pin low
    analogWrite(motor2, i);
#endif
  }
  else {
#if defined (motorType)
    digitalWrite(motor2, LOW); // pull pin low
    digitalWrite(motor1, HIGH);
    analogWrite(motorPWM, i);
#else
    analogWrite(motor1, i); // pull pin low
    analogWrite(motor2, 0);
#endif
  }
}

void checkRotaryShort() {
  if (oldEncPos != encoderPos) {
    if (menuCode == 3) encoderPos = constrain(encoderPos, 1, 10);
    if (menuCode == 6) encoderPos = constrain(encoderPos, 0, 100);
    interruptTime = millis();
    int x = old_pos - encoderPos;
    oldEncPos = encoderPos;
    i = encoderPos;
  }
}

void writeEEPROM() {
  EEPROM.write(0, speedFast );
  EEPROM.write(1, speedSlow);
  EEPROM.write(2, timeToStop);
  EEPROM.write(3, accelDecelRate);
  EEPROM.write(4, randomSS);
  EEPROM.write(5, stationStopTime);
  EEPROM.write(6, timeToDecel);
  EEPROM.write(7, stationStopPercent);
}

void readEEPROM() {
  speedFast = EEPROM.read(0);
  speedSlow = EEPROM.read(1);
  timeToStop = EEPROM.read(2);
  accelDecelRate = EEPROM.read(3);
  accelDecelRate = 10;  // no longer a menu item
  randomSS = EEPROM.read(4);
  stationStopTime = EEPROM.read(5);
  timeToDecel = EEPROM.read(6);
  stationStopPercent = EEPROM.read(7);
  stationStopTimeSave = stationStopTime;
  Serial.println( speedFast );
  Serial.println( speedSlow);
  Serial.println( timeToStop);
  Serial.println( accelDecelRate );
  Serial.println( randomSS );
  Serial.println( stationStopTime );
  Serial.println( timeToDecel);
  Serial.println( stationStopPercent );
}

void doMenu() {
  display.clearDisplay();
  display.display();
  menuCode = 1;  getTopSpeed();
  menuCode = 2;  getLowSpeed();
  // menuCode = 3;  getAccelDecelRate();
  menuCode = 4;  getRunTime();
  menuCode = 5;  getStationStopTime();
  menuCode = 6;  getStationStopPercentage();
  writeEEPROM();
  readEEPROM();
}

void getStationStopPercentage() {
  encoderPos = stationStopPercent;
  digitalWrite(enable, LOW); // no motor here
  getCommonStuff();
  exitFlag = 0;
  stationStopPercent = i;
  saveData(i);
  exitFlag = 0;
}

void getStationStopTime() {
  encoderPos = stationStopTime;
  digitalWrite(enable, LOW); // no motor here
  getCommonStuff();
  exitFlag = 0;
  stationStopTime = i;
  saveData(i);
  exitFlag = 0;
}

void getTopSpeed() {
  encoderPos = speedFast;
  digitalWrite(enable, HIGH);
  getCommonStuff();
  speedFast = i;
  digitalWrite(enable, LOW);
  saveData(i);
  i = 0; // reset to display next routines screen without turning rotary
  exitFlag = 0;
}

void getLowSpeed() {
  encoderPos = speedSlow;
  digitalWrite(enable, HIGH);
  getCommonStuff();
  if (i >= speedFast) {
    i = speedFast;
    encoderPos = speedSlow;
  }
  speedSlow = i;
  saveData(i);
  exitFlag = 0;
}

void getAccelDecelRate() {
  encoderPos = accelDecelRate;
  digitalWrite(enable, HIGH);
  getCommonStuff();
  encoderPos = constrain(encoderPos, 1, 10);
  exitFlag = 0;
  accelDecelRate = encoderPos;
  saveData(i);
}

void getRunTime() {
  encoderPos = timeToStop;
  digitalWrite(enable, LOW); // no motor here
  getCommonStuff();
  exitFlag = 0;
  timeToStop = i;
  saveData(i);
}

int saveData(int i) {
  display.clearDisplay();
  display.setCursor(0, 0);
  display.print("Data Saved ");
  display.setCursor(0, 8);
  display.print("New Value = ");
  display.print(encoderPos);
  display.display();
  delay(1000);
}

void getCommonStuff() {
  exitFlag = 0;
  buttonState = HIGH;
  do {
    buttonState = digitalRead(buttonPin);
    if (buttonState == LOW) {
      holdTime = millis();
      do {
        buttonState = digitalRead(buttonPin);
        if (buttonState == HIGH) {
          break; // exit from quick button if let go
        }
        if (millis() - holdTime >= 500) { // held for 1 sec+
          exitFlag = 1;
          break;
        }
      }
      while (0 == 0);
      if (exitFlag == 0) {
        dir = !dir;
      }
      setMotorSpeed();
      delay(300);
      if (menuCode == 5) {
        if (exitFlag == 0) {
          randomSS = !randomSS;
        }
        Serial.print("rndSS = "); Serial.println(randomSS);
        randomSSchangeFlag = 1;
      }

      if (menuCode == 3) {
        if (exitFlag == 0) {
        }
        accelDecelRate = encoderPos;
        if (exitFlag == 0) {
          doAccelDecelDemo();

        }

        encoderPos = accelDecelRate;
      }
    }
    oldEncPos2 = i;
    checkRotaryShort();
    if (encoderPos != oldEncPos2) {
      display.clearDisplay();
      setMotorSpeed();
    }
    display.setCursor(0, 0);
    if ( menuCode == 1) {
      display.print("Turn for TOP Speed");
      display.setCursor(0, 8);
      display.print("  Button to Reverse");
    }
    if ( menuCode == 2) {
      display.print("Turn for LOW Speed");
      display.setCursor(0, 8);
      display.print("  Button to Reverse");
    }
    if ( menuCode == 3) {
      display.print("Turn for AccDec Rate");
      display.setCursor(0, 8);
      display.print("  Button to do Test"); // accel, decel, rev, repeat
    }
    if ( menuCode == 4) {
      display.print("Turn for Run Time");
      display.setCursor(0, 8);
      display.print("  in Seconds");
    }
    if ( menuCode == 5) {
      if (randomSSchangeFlag == 1) {
        randomSSchangeFlag = 0;
        display.clearDisplay();
      }
      display.print("Turn for St Stop Time");
      display.setCursor(0, 8);
      display.print("Button for RANDOM =   ");
      display.setCursor(115, 8);
      display.print(randomSS);
    }
    if ( menuCode == 6) {
      display.print("Turn for SStop %");
      display.setCursor(0, 8);
      display.print("100=all, 0=none");
      display.setCursor(115, 8);
    }

    display.setCursor(0, 16);
    display.print("LONG Button=Done");
    display.setCursor(0, 24);
    display.display();
    display.setCursor(72, 24);
    display.print(encoderPos);
    display.display();
  }
  while (exitFlag == 0);
  writeEEPROM();
}

void doAccelDecelDemo() { // accel, delay 1000 or so, decel / reverse / repeat once
  //Serial.println("at Accel Decel Demo");
  digitalWrite(enable, HIGH);
  dir = !dir;
  accelerate();
  delay(500);
  decelerate();
  digitalWrite(enable, LOW);
  delay(500);
  dir = !dir;
  digitalWrite(enable, HIGH);
  accelerate();
  delay(500);
  decelerate();
  digitalWrite(enable, LOW);
}

void PinA() {
  cli(); //stop interrupts happening before we read pin values
  reading = PIND & 0xC;
  if (reading == B00001100 && aFlag) {
    if (encoderPos >= 1) {
      encoderPos --;
    }//decrement the encoder's position count
    FwdRev = 0;
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00000100) bFlag = 1;
  sei(); //restart interrupts
}

void PinB() {
  cli(); //stop interrupts happening before we read pin values
  reading = PIND & 0xC;
  if (reading == B00001100 && bFlag) {
    if (encoderPos <= 249) {
      encoderPos ++;
    } //increment the encoder's position count
    FwdRev = 1;
    bFlag = 0;
    aFlag = 0;
  }
  else if (reading == B00001000) aFlag = 1;
  sei(); //restart interrupts
}

 
Laser Cut Case
These photos show the laser cut case and various connections. 

The power connection (9-23 volts DC) is a 2.1mm plug - an adapter is shown in this photo.  The center pin is positive.

The "Run Once" connection has a jumper installed - if this is replaced by a pushbutton switch the unit will operate the train / trolley once, stopping till the button is pushed.

"Track" goes to the track (logical!)

The "FUSE" should be appropriate for you power supply and train.  Two amps should work for most applications.

"SS1" and "SS2" are connections to reed switches for station stops.  If station stops are not needed these wires can be unplugged.