Model Train controller - Please comment and advise

Hello all - I am new to Arduino, this is my first project.

I have a N-Scale model train display and have not been happy with the old analog DC speed controller, so am now building this sketch to control train speeds, directions, and display lights. I will be adding additional functions as time goes to also control a number of track switches.

Please comments and advise. I also want to add a WiFi board and build a iOS app to provide a remote control function.

SKETCH FOLLOWS


//      ARDUINO MEGA 2560
//      MODEL TRAIN CONTROLLER
//
// Purpose of this sketch is to control model train display with following specifications
//  - Two independent tracks capable of both directions using H-Bridge control
//  - Both tracks capable of independent speed control using pulse width modulation (PWM)
//  - Both tracks capable of independent direction control
//  - Relay for lights control
//  - Relay to turn on power for 18 VAC transformer
//  - Spare relays for possible future use
//  - LCD display to show when Arduino is powered on, and to show train speeds by percent of maximum
//  - Future integration of WIFI module for remote control ability using mobile app
//  - Stepper POTs to facilitate remote control
//
//  Written by Stephen Breedlove
// 
// Version information
//
// Beta 3 - Written for Uno R3
// Beta 4 05/24/2022 - Modify for Mega 2560 board to add LCD display and additional inputs/outputs for Relay 3 and 4, addition of Track B speed and direction control
// Beta 4A 05/24/2022 - Add LCD display coding
// Beta 5  05/25/2022 - Added LCD display to coding, beta tested on single track - tested GOOD
//
//_____________________________________________________________________________________________________________________________________________________________________

// TO-DO CHECKLIST

// 1    add directional control switch for B track - A and B currently tied together
// 2    add toggle switches to board
// 3    build 18VDC to 5VDC converter


//_____________________________________________________________________________________________________________________________________________________________________
//_______________________________________________________________________________________________________________________
//  MEGA PINOUTS      _______                      |          BOARD END CONNECTORS              |     SPECIFICATIONS
//                   |       O --- SCL / D21       |                                            |
//                   |       O --- SDA / D20       |        INSIDE              EDGE            |
//                   |       O --- AREF            |        ___________________________         |
//           NC ---- O       O --- GND             |                                            |
//         IOREF --- O       O --- D13 PWR LED     |  LCD +5v  +5V --- O O --- +5V LCD+ A +5V   |
//         +3.3V --- O       O --- D12 STATUS LED  |           D22 --- O O --- D23              |
//          +5V  --- O       O --- D11 enB         |  LCD RS   D24 --- O O --- D25              |
//          GND  --- O       O --- D10 enA         |  LCD R/W  D26 --- O O --- D27              |
//          GND  --- O       O --- D9  in4         |  LCD ENA  D28 --- O O --- D29              |
//  PWR IN +5VIN --- O       O --- D8  in3         |  REL D    D30 --- O O --- D31              |
//    POT A  A0  --- O       O --- D7  in2         |  REL C    D32 --- O O --- D33              |
//    RUN SW A1  --- O       O --- D6  in1         |  REL B    D34 --- O O --- D35              |
//     relA  A2  --- O       O --- D5  RELAY2      |  REL A    D36 --- O O --- D37              |
//     relB  A3  --- O       O --- D4  RELAY1      |  LCD D4   D38 --- O O --- D39              |
//     dirA  A4  --- O       O --- D3  relB        |  LCD D5   D40 --- O O --- D41              |
//    POT B  A5  --- O       O --- D2  relA        |  LCD D6   D42 --- O O --- D43              |
//           A6  --- O       O --- D1  / TX0       |  LCD D7   D44 --- O O --- D45              |
//           A7  --- O       O --- D0  / RX0       |           D46 --- O O --- D47              |
//           A8  --- O       O --- D14 / TX3       |           D48 --- O O --- D49              |
//           A9  --- O       O --- D15 / RX3       |           D50 --- O O --- D51              |
//           A10 --- O       O --- D16 / TX2       |           D52 --- O O --- D53              |
//           A11 --- O       O --- D17 / RX2       |  LCD GND  GND --- O O --- GND LCD- K GND   |
//           A12 --- 0       O --- D18 / TX1      /|                                            |
//           A13 --- O       O --- D19 / RX1     / |____________________________________________|                                      
//           A14 --- O       O --- D20 / SDA    /                                               |
//           A15 --- O       O --- D21 / SCL   /                                                |
//                    00000000 ______________ /                                                 |
//                    00000000  see inset  __/                                                  |
//                                                                                              |
//________________________________________________________________________________________________________________________
//_______________________________________________________________________________________________________________________
// pin outs L298N                                |    L298  H-BRIDGE MOTOR DRIVE - MED POWER - 2 AMP, 25W MAX
//               ___________________             |    +12V - 5V TO 35V FOR MOTOR DRIVE
//              |                   |            |     +5V - 5V BOARD POWER  
//     OUT 2a---O                   O--OUT 1a    |
//     OUT 2b---O                   O--OUT 2b    |
//              |     ___           |            |
//      +18AC -- O O O   O O O O O O -- UNO 10   |
//       GND ------| |   | | | | |----- UNO 6    |
//      +5DC --------|   | | | |------- UNO 7    |
//                       | | |--------- UNO 8    |
//      UNO 11 ----------| |----------- UNO 9    |
//_______________________________________________|
//_______________________________________________________________________________________________________________________
// relay board pinouts          |  relay pins    |    RELAY BOARD
//                              |                |    4-CHANNEL RELAY 
//                              |   O   O   O    |    +5V DC BOARD POWER
//    relD------| |------relB   |   |   |   |    |    HIGH CURRENT RELAYS, 250 VAC 10A, 30VDC 10A
//    relC----| | | |----relA   |  NO   C   NC   |
//    VCC---| | | | | |---GND   |________________|
//          | | | | | |                          |
//        __O_O_O_O_O_O_______________           |
//       |                            |          |
//       |                            |          |
//       |_O_O_O__O_O_O__O_O_O__O_O_O_|          |
//         | | |  | | |  | | |  | | |            |
//        relayD relayC relayB relayA            |
//_______________________________________________|
//______________________________________________________________________________________________________________________
//   LCD display                                              |     LCD DISPLAY
//                                                            |     16 CHARACTERS
//     ______                                                 |     2 LINES
//    |      |                                                |     Character blocks 5 x 8
//    |      O ---  1 / GND                                   |     +5V main power
//    |      O ---  2 / +5V LOGIC FROM ARDUINO                |     +5V LED power through 220 ohm resistor
//    |      O ---  3 / V0  CONTRAST INPUT                    |     Constrast 0V - 5V
//    |      O ---  4 / RS  REGISTER SELECT                   |         best viewed at 4V
//    |      O ---  5 / R/W READ/WRITE SELECT                 |
//    |      O ---  6 / E   ENABLE                            |
//    |      O ---  7 / DO  DATA BUS (8 BIT MODE)             |
//    |      O ---  8 / D1  DATA BUS (8 BIT MODE)             |
//    |      O ---  9 / D2  DATA BUS (8 BIT MODE)             |
//    |      O --- 10 / D3  DATA BUS (8 BIT MODE)             |
//    |      O --- 11 / D4  DATA BUS IN                       |
//    |      O --- 12 / D5  DATA BUS IN                       |
//    |      O --- 13 / D6  DATA BUS IN                       |
//    |      O --- 14 / D7  DATA BUS IN                       |
//    |      O --- 15 / A   LED+ 5V BACKLIGHT ANODE   (+5V)   |
//    |      O --- 16 / K   LED- 5V BACKLIGHT CATHODE (GND)   |
//    |______|                                                |
//                                                            |
//___________________________________________________________________________________________________________________

#include <LiquidCrystal.h>

  int relA = 36;            // RELAY A OUT
  int relB = 34;            // RELAY B OUT
  int relC = 32;            // RELAY C OUT
  int relD = 30;            // RELAY D OUT
  int in1 = 6;              // DIRECTION A
  int in2 = 7;              // DIRECTION A  
  int in3 = 8;              // DIRECTION B
  int in4 = 9;              // DIRECTION B
  int enA = 10;             // PWM A OUT
  int enB = 11;             // PWM B OUT
  int toggleOut = 12;       // RUN LED
  int unoLED = 13;          // POWER ON LED
  int potenA = A0;          // PWM CONTROL A IN
  int toggleIn = A1;        // RUN SWITCH
  int relayA = A2;          // RELAY A IN
  int relayB = A3;          // RELAY B IN
  int dirA = A4;            // DIRECTION A IN
  int potenB = A5;          // PWM CONTROL B IN
  int dirB = A6;            // DIRECTION B IN
  int relayC = A7;          // RELAY C IN
  int relayD = A8;          // RELAY D IN
  int speedA;               // PWM CONTROL A OUT
  int speedB;               // PWM CONTROL B OUT
  int pastPCT = 0;          // SPEED CONTROL BASE


// LCD DISPLAY

  const int rs = 24, en = 28, d4 = 38, d5 = 40, d6 = 42, d7 = 44;
  LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
  int lcdVo = 5;            // Contrast
  int lcdAK = 4;            // Backlight


// ACTIVE LED
  int press = 0;
  boolean toggle = true;

// BOARD SETUP
void setup()
  {
  // Set all the pin functions
    pinMode(enA, OUTPUT);       // PWM A
    pinMode(enB, OUTPUT);       // PWM B
    pinMode(in1, OUTPUT);       // DIRECTION A
    pinMode(in2, OUTPUT);       // DIRECTION A
    pinMode(in3, OUTPUT);       // DIRECTION B
    pinMode(in4, OUTPUT);       // DIRECTION B
    pinMode(toggleOut, OUTPUT); // RUN LED
    pinMode(toggleIn, INPUT);   // RUN SWITCH
    pinMode(potenA, INPUT);     // SPEED CONTROL A
    pinMode(relayA, INPUT);     // RELAY1 SWITCH
    pinMode(relayB, INPUT);     // RELAY2 SWITCH
    pinMode(relayC, INPUT);     // RELAY3 SWITCH
    pinMode(relayD, INPUT);     // RELAY4 SWITCH
    pinMode(dirA, INPUT);       // DIRECTION A
    pinMode(dirB, INPUT);       // DIRECTION B
    pinMode(potenB, INPUT);     // SPEED CONTROL B
    pinMode(relA, OUTPUT);      // RELAY1 OUT
    pinMode(relB, OUTPUT);      // RELAY2 OUT
    pinMode(relC, OUTPUT);      // RELAY3 OUT
    pinMode(relD, OUTPUT);      // RELAY4 OUT
    pinMode(unoLED, OUTPUT);    // POWER LED
    pinMode(lcdVo, OUTPUT);     // LCD Contrast
    pinMode(lcdAK, OUTPUT);     // LCD Backlight
    
  // STARTUP - Initial state - Inputs to HIGH, Outputs to LOW
    digitalWrite(enA, LOW);     // PWM OUT
    digitalWrite(enB, LOW);     // PWM OUT
    digitalWrite(relA, HIGH);   // RELAY A
    digitalWrite(relB, HIGH);   // RELAY B
    digitalWrite(relC, HIGH);   // RELAY C
    digitalWrite(relD, HIGH);   // RELAY D
    digitalWrite(relayA, HIGH); // RELAY A
    digitalWrite(relayB, HIGH); // RELAY B
    digitalWrite(relayC, HIGH); // RELAY C
    digitalWrite(relayD, HIGH); // RELAY D
    digitalWrite(A0, LOW);      // POT A LOW
    digitalWrite(A1, HIGH);     // RUN SWITCH
    digitalWrite(A2, HIGH);     // RELAY A IN
    digitalWrite(A3, HIGH);     // RELAY B IN
    digitalWrite(A4, HIGH);     // DIRECTION A
    digitalWrite(A5, LOW);      // POT B LOW
    digitalWrite(A6, HIGH);     // DIRECTION B
    digitalWrite(A7, HIGH);     // RELAY C IN
    digitalWrite(A8, HIGH);     // RELAY D IN
    digitalWrite(in1, LOW);     // H-BRIDGE OFF
    digitalWrite(in2, LOW);     // H-BRIDGE OFF
    digitalWrite(in3, LOW);     // H-BRIDGE OFF
    digitalWrite(in4, LOW);     // H-BRIDGE OFF
    digitalWrite(unoLED, HIGH); // POWER LED ON
    analogWrite(lcdVo, 25);     // Set LCD Contrast
    analogWrite(lcdAK, 128);    // Set LCD Backlight
    Serial.begin(9600);         // Begin serial communications
    lcd.begin(16, 2);           // Turn on LCD Display 
    lcd.setCursor(0,0);         // LCD Cursor

  }

// CONTROL LOOP
void loop()
{

    if (digitalRead(toggleIn) == HIGH)    //Read Run Switch
      {
          lcd.setCursor(0,0);
          lcd.print("    COMPUTER    ");
          lcd.setCursor(0,1);
          lcd.print("  POWERED   ON  ");
      }
      
    else if (digitalRead(toggleIn) == LOW && digitalRead(dirA) == LOW)  // STANDARD DIRECTION
      {
          digitalWrite(toggleOut, HIGH);
    
      // SPEED CONTROL
          int potA=analogRead(potenA);
          int potB=analogRead(potenB);
          int speedA=map(potA,0,1023,0,255);
          int speedB=map(potB,0,1023,0,255);
            analogWrite(enA,speedA);        // MOTOR A SPEED
            analogWrite(enB,speedB);        // MOTOR B SPEED
          int sensorValueA = analogRead(potenA);
          float pctA = sensorValueA * (100.0 / 1023.0);
          int percentA = pctA + 0.5;
          int sensorValueB = analogRead(potenB);
          float pctB = sensorValueB * (100.0 / 1023.0);
          int percentB = pctB + 0.5;
      // RELAYS
          int relayoutA=digitalRead(relayA);
          int relayoutB=digitalRead(relayB);
            digitalWrite(relA, relayoutA);  // RELAY A
            digitalWrite(relB, relayoutB);  // RELAY B
      // DIRECTION CONTROL
            digitalWrite(in1, HIGH);        // MOTOR A
            digitalWrite(in2, LOW);         // MOTOR A
            digitalWrite(in3, HIGH);        // MOTOR B
            digitalWrite(in4, LOW);         // MOTOR B
      // SEND TO DISPLAY
          String PCT = "A: ";
            PCT.concat(percentA);
            PCT += "  |  B: ";
            PCT += percentB;
          int currentPCT = (percentA + percentB);
            if (currentPCT != pastPCT);
                {
                    Serial.println(PCT);
                      lcd.setCursor(0,0);
                      lcd.print(" INNER   OUTER  ");
                      lcd.setCursor(0,1);
                      lcd.print(PCT);
                    int pastPCT = currentPCT;
                }
      }
    
    else if (digitalRead(toggleIn) == LOW && digitalRead(dirA) == HIGH) //REVERSE DIRECTION
    {         

    // RUN LED
          digitalWrite(toggleOut, HIGH);
    // SPEED CONTROL
          int potA=analogRead(potenA);
          int potB=analogRead(potenB);
          int speedA=map(potA,0,1023,0,255); 
          int speedB=map(potB,0,1023,0,255);
            analogWrite(enA,speedA);        // MOTOR A SPEED
            analogWrite(enB,speedB);        // MOTOR B SPEED
          int sensorValueA = analogRead(potenA);
          float pctA = sensorValueA * (100.0 / 1023.0);
          int percentA = pctA + 0.5;
          int sensorValueB = analogRead(potenB);
          float pctB = sensorValueB * (100.0 / 1023.0);
          int percentB = pctB + 0.5;
    // RELAYS
          int relayoutA=digitalRead(relayA);
          int relayoutB=digitalRead(relayB);
            digitalWrite(relA, relayoutA);  // RELAY A
            digitalWrite(relB, relayoutB);  // RELAY B
    // DIRECTION CONTROL
            digitalWrite(in1, LOW);         // MOTOR A
            digitalWrite(in2, HIGH);        // MOTOR A
            digitalWrite(in3, LOW);         // MOTOR B
            digitalWrite(in4, HIGH);        // MOTOR B
    // SEND TO DISPLAY
          String PCT = "A: ";
              PCT.concat(percentA);
              PCT += "  |  B: ";
              PCT += percentB;
          int currentPCT = (percentA + percentB);
              if (currentPCT != pastPCT);
                {
                  Serial.println(PCT);
                    lcd.setCursor(0,0);
                    lcd.print(" INNER   OUTER  ");
                    lcd.setCursor(0,1);
                    lcd.print(PCT);
                  int pastPCT = currentPCT;          
                }
    }

    else if (digitalRead(toggleIn) == HIGH);
    {

            digitalWrite(toggleOut, LOW);   // RUN LED
            digitalWrite(in1, LOW);
            digitalWrite(in2, LOW);
            digitalWrite(in3, LOW);
            digitalWrite(in4, LOW);
            digitalWrite(enA, LOW);
            digitalWrite(enB, LOW);
            digitalWrite(relA, HIGH);
            digitalWrite(relB, HIGH);
            digitalWrite(relC, HIGH);
            digitalWrite(relD, HIGH);
    }

    delay(10);      
} 

If the pin isn't HIGH, there's no need to read it again to see if it is LOW.

All those pin numbers?
Better as "const byte"

Oops.
(You've got a few of those)

L298 is pretty old now.
Modern FET bridges are preferred.

I can't really see the need for using String

Why are writing to the pot pins?

Overall, it looks like you've written a lot of code, without testing it as you went along

int speedA = potA / 4;

Is easier to read, more intuitive and arguably more correct

I’m curious about selecting forward / reverse…
The current code allows you to slam the motors in the opposite direction at any time.

Why not centre bias the pots (or 70/30%), and determine the direction control from the speed control position.
That way, you naturally have to slow down the motors across the pot’s centre point before changing direction

You have two large pieces of code in loop which are essentially the same apart from the direction settings. Consider refactoring the identical bits out into another function that you can simply call in each section.

Less than no need, it's a bad idea: input pins can/will can change in between readings.

You can just digitalRead() all your inputs at the beginning, saving the values in variables, and use those variables in your decision making process.

If/when you strat using millis() for timing, the same idea applie: read the millis() value, save it and use that for all your references to time in the loop().

a7

Agreed the L298 is old, but also cheap.
String is for the LCD display, is what I'm sending to the display since I'm taking readings from two POTS, then sending to the display.
Probably don't need to write the pot pins in the beginning, and can remove that.
Using a mockup board with LED's to test the code as I was building, which is why you see the revision information (corrections as well as additions).
I have also hooked this up to a single track model train and it does test good.

Given the problems I pointed out with some of your "if"s, I would suggest your test strategy is flawed or incorrect.

The LCD is capable of quite a lot, without the risks of the String class.

Tried using the millis() for timing to make my RUN led blink, didn't work out. I could make the LED blink as a standalone sketch, as it's supposed to, but would not work within this sketch, and could not figure out where I went wrong. Probably won't use millis() on this anyway as the LCD screen now tells me when the "computer" is ON, and when I have it running. I really don't have anything that would utilize timing (other than MAYBE adding a blinking LED).

Agreed. Just not sure how that is going to work. I do eventually need to get this written so I can independently change directions of the 2 tracks, but that means adding 2 more large pieces of code in the loop.

I have considered going with naming 4 functions, 2 for each track to set track directions, then using GOTO / RETURN commands, but everything I'm reading says I should not do this???

I'm going to read up on the pot center bias idea. This would solve a few issues.

1hpost #3

int speedA = potA / 4;

What does the "potA / 4" do?

Divides the variable potA by four.

Divides the variable potA by four.

So I could divide by, say 10 or 20, and it would set speeds in those increments.

Four functions named whatever is fine.

And to execute the code in a function, you call it, and it will return.

There is no need to use goto, there is no GOTO, and 'return' in a function means you done, go back where the call happened.

Ppl prolly just telling you goto is bad, and for reasons it can be so considered. It is unlikely you will need it now, maybe even ever.

Google

functions in C/C+=

Or

using functions with Arduino

Or consult your favorite learning materials.

a7

Or for that matter,

int speedA = potA >> 2;

Which should be the same code. :grin:

But shift is not intuitive.

Oh. :grimacing:

It is to me. :face_with_open_eyes_and_hand_over_mouth:

It is to me too - after 45+ years of programming, but not to people new to binary.

Hmmm, let me see - FOCAL at a school maths weekend at New College in 1971, FORTRAN at UNSW in 1972, custom built SC/MP machine in intern year out of Uni in 1978 - that was binary, pure machine code. Appears I have been doing it a while. :grin: