Subscribe via RSS
12Apr/220

Z Scale Layout – Arduino Control and Track Plan Updates

Actual trackwork on the layout has stalled recently. I failed to order all of the components I needed to build my previous plan and so used a few other off-cuts to, at least, make a circuit I could run trains on. First testing of trains was done with a 9v battery and the results were sub-optimal. Whilst browsing Jaycar recently, I saw a motor-controller shield for Arduino with 4 outputs and decided that was the way to go. It even has two servo controllers... I wonder what I can animate?

20220407 174142

I also had an Arduino Uno Wifi, so decided, instead of building some physical box with switches and dials, I'd make a webpage to control the layout! I've programmed this Uno Wifi before, so go and check that post out for caveats when dealing with this unit. The main point being that you need to program the WIFI module independent from the Arduino itself, using the serial connection as the conduit to communicate between the units. This all then gets a little difficult as you may also want to use that Serial port for logging output.

The Web Server

Fortunately, there are hundreds of examples to run with when creating these mini-webservers. I used the example over here, adjusting the page to control two throttles. Whenever a user presses a button, it updates the internal throttle variable and then sends both throttle values out over the serial port.

#include <ESP8266WiFi.h>

const char* ssid     = "wifi accesspoint name";
const char* password = "wifi password";
WiFiServer server(80);
String header;
unsigned long currentTime = millis();
unsigned long previousTime = 0; 
const long timeoutTime = 2000;

void setup() {
  delay(3500);
  Serial.begin(115200);
  Serial.print("AP:");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    //Serial.print(".");
  }
  // Print local IP address and start web server
  //Serial.println("");
  //Serial.println("WiFi connected.");
  //Serial.println("IP address: ");
  //Serial.println(WiFi.localIP());
  Serial.println("OK");
  server.begin();
}

int t1 = 100, t2 = 100;
int lastT1, lastT2;
void loop(){
  WiFiClient client = server.available();   // Listen for incoming clients
  if (client) {                             // If a new client connects,
    //Serial.println("New Client.");          // print a message out in the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    currentTime = millis();
    previousTime = currentTime;
    while (client.connected() && currentTime - previousTime <= timeoutTime) {
      currentTime = millis();         
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        header += c;
        if (c == '\n') {                    // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();
            
            if (header.indexOf("GET /1/less") >= 0) {
              t1 -= 5;
            } else if (header.indexOf("GET /1/off") >= 0) {
              t1 = 100;
            } else if (header.indexOf("GET /1/more") >= 0) {
              t1 += 5;
            } else if (header.indexOf("GET /2/less") >= 0) {
              t2 -= 5;
            } else if (header.indexOf("GET /2/off") >= 0) {
              t2 = 100;
            } else if (header.indexOf("GET /2/more") >= 0) {
              t2 += 5;
            }

            if (t1 < 0) t1 = 0;
            if (t1 > 200) t1 = 200;
            if (t1 < 0 && t1 > -15) t1 = -15;
            if (t1 > 0 && t1 <  15) t1 =  15;
            if (t2 < 0) t2 = 0;
            if (t2 > 200) t2 = 200;
            if (t2 < 0 && t2 > -15) t1 = -15;
            if (t2 > 0 && t2 <  15) t1 =  15;

            if (lastT1 != t1 || lastT2 != t2) {
              Serial.print("THR:");
              Serial.print(t1);
              Serial.print(":");
              Serial.println(t2);
              lastT1 = t1;
              lastT2 = t2;
            }
            
            // Display the HTML web page
            client.println("<!DOCTYPE html><html>");
            client.print("<head><meta name=\"viewport\" ");
            client.println("content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            // CSS to style the on/off buttons 
            // Feel free to change the background-color and font-size attributes to fit your preferences
            client.print("<style>html { font-family: Helvetica; display: inline-block;");
            client.println("margin: 0px auto; text-align: center;}");
            client.print(".button { background-color: #195B6A; border: none;");
            client.println("color: white; padding: 16px 40px;");
            client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
            client.println(".button2 {background-color: #77878A;}</style></head>");
            
            // Web Page Heading
            client.println("<body><h1>Train Controller</h1>");

            client.println("<h1>Throttle 1</h1>");
            client.print("<p>Requested Speed: ");
            client.print(t1 - 100);
            client.println("</p>");
            client.println("<p><a href=\"/1/less\"><button class=\"button button2\"><<</button></a> ");
            client.println("<a href=\"/1/off\"><button class=\"button button2\">Stop</button></a> ");
            client.println("<a href=\"/1/more\"><button class=\"button button2\">>></button></a></p>");

            client.println("<h1>Throttle 2</h1>");
            client.print("<p>Requested Speed: ");
            client.print(t2 - 100);
            client.println("</p>");
            client.println("<p><a href=\"/2/less\"><button class=\"button button2\"><<</button></a> ");
            client.println("<a href=\"/2/off\"><button class=\"button button2\">Stop</button></a> ");
            client.println("<a href=\"/2/more\"><button class=\"button button2\">>></button></a></p>");

            client.println("</body></html>");
            
            // The HTTP response ends with another blank line
            client.println();
            // Break out of the while loop
            break;
          } else { // if you got a newline, then clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    header = "";
    client.stop();
  }
}

Note that the ESP2866 throws out a stupid line of text when it switches on. It's at 74880 baud or somesuch and, if you're already listening on the arduino-side, you'll receive a random pile of junk. Due to this, I've added a 3 second delay to the start of both units.

Arduino Motor Control Code

So, we have the webserver sending the throttle over the serial port... let's now read it and control the motors as expected.

#include <AFMotor.h>

AF_DCMotor innerLoop(1, MOTOR12_64KHZ); 
AF_DCMotor outerLoop(2, MOTOR12_64KHZ); 

bool is_ready = false;

void setup() {
  //just to skip the bios text output from ESP8266
  delay(1000);
  Serial.begin(115200);
  Serial.println("Starting...");
  innerLoop.run(RELEASE);
  outerLoop.run(RELEASE);
}

void loop() {
  while(Serial.available() == 0) { }
  String in_str = Serial.readString();

  Serial.print(in_str);
  while(in_str.length() > 0) {
    if (in_str.startsWith("OK")) {
      Serial.println("OKAY!");
    } else if (in_str.startsWith("THR:")) {
      String this_str = in_str.substring(0, in_str.indexOf('\n'));
      in_str = in_str.substring(this_str.length());
      Serial.print("Dealing with: ");
      Serial.println(this_str);
      int next_colon = this_str.indexOf(':', 4);
      int tt1 = this_str.substring(4, next_colon).toInt();
      int tt2 = this_str.substring(next_colon + 1).toInt();
      //Serial.println(next_colon);
      int t1 = (tt1 - 100) * 2.5;
      int t2 = (tt2 - 100) * 2.5;
      Serial.print("Throttles: ");
      Serial.print(t1);
      Serial.print(", ");
      Serial.println(t2);

      if (t1 == 0) { 
        innerLoop.run(RELEASE);
      } else {
        innerLoop.run(t1 >= 0 ? FORWARD : BACKWARD);
        if (t1 < 0) t1 *= -1;
        innerLoop.setSpeed(t1);
      }
      if (t2 == 0) { 
        outerLoop.run(RELEASE);
      } else {
        outerLoop.run(t2 >= 0 ? FORWARD : BACKWARD);
        if (t2 < 0) t2 *= -1;
        outerLoop.setSpeed(t2);
      }
    }
    in_str = in_str.substring(1);
  }
}

The AFMotor library was fun to wrangle. You choose the polarity via the run call, passing either FORWARD or BACKWARD. Turns out though, if you pass a negative value into the speed, it'll reverse the direction automatically. I initially was setting both and wondering why my train wasn't reversing!

Layout Updates

With the hobbled-together track, I'd made the basic figure-of-eight dual-track loop and was nearly bored of it once the controller was working properly. Friends also then came over for dinner that evening and asked how more complicated I was going to make the track... slightly insulted, I went back to the drawing board. Instead of dual-track all the way round, let's reduce it to single with a few loops and a yard.

newestplan

The result is ridiculous, but nicely uses the track I'd accumulated... only needing a little bit more. No problems with that as I got to order more trains at the same time. That track is still in the mail, so instead here's a video of the current trains.

Excuse the audio. How nice is that Marklin Santa Fe F7A? Love it. The Akia 485-Series hated the incline straight away and no amount of throttle got it going. I also need to fix the throttle increments as you can see the trains only start moving after 4 presses. Work to do!

Filed under: MRR Leave a comment
Comments (0) Trackbacks (1)

No comments yet.


Leave a comment


*