Roundhouse Rebuild Part 2 – The Sketch

Completed roundhouse close up exterior 1The sketch that currently runs the Roundhouse lighting (it will become part of a larger Yard & Turntable sketch that will be developed down the road) includes the ALED control methods discussed in An Introduction to Arduino & Addressable RGB LEDs, so I won’t discuss that part at length here. What’s new is the use of a shift register to control multiple outputs and the introduction of Ethernet networking to receive commands from a separate control device.

First, the included libraries:

#include <Adafruit_NeoPixel.h>
#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
#include <EEPROM.h>
#include <SystemCommon.h>

SPI.h is required for all spi bus functionality, such as accessing the Ethernet shield and EEPROM memory. Ethernet.h supports the basic functionality of an Ethernet shield. In addition, since I have elected to use UDP (a simple, short datagram protocol) for messaging, I have to include the EthernetUDP.h library as well. For more about network addresses and using EEPROM, see Ethernet Shields, Addresses & EEPROM. SystemCommon.h is my personal library containing (among other things) a function to retrieve address information from EEPROM (see Ethernet Shields, Addresses & EEPROM).

Next. the predefined values used by the sketch, including pin assignments.

#define NUMSTALLS 5
#define NUMLEDS 10

// Stall Settings
#define STALL_HIGH 1
#define STALL_LOW -1
#define STALL_OFF 0

// pin assignments
const int stripPIN = 2; 
const int clockPIN = 3;
const int latchPIN = 4;
const int dataPIN = 5;

A few global variables are needed. This sketch uses preset RGB values for the LEDs; these are set in three global arrays:

// RGB color arrays
 byte base_high[] = {128, 76, 25};
 byte base_low[] = {32, 19 , 6};
 byte blue_low[] = {0, 0, 8};

We need a global object to manage the LED strip, and a global object for handling UDP communication.

// An EthernetUDP instance to let us send and receive packets over UDP
 EthernetUDP Udp;
 // Global LED strip object
 Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMLEDS, stripPIN, NEO_GRB + NEO_KHZ800);

Using Shift Registers

Before looking at setup() and loop(), we will need a method for handling shift register(s). For an excellent explanation and tutorial on shift registers, see the Arduino – ShiftOut Tutorial.

The short explanation of how they work is that each register has 8 outputs that can be ON or OFF. A pattern of bits is sent to each register via a single data connection, determining which outputs go on (binary 1) or off (binary 0). For example, sending the byte (8 bits) value of 6 to a register — 0110000 binary — turns the 2nd the 3rd outputs on and all the rest off. When there are multiple registers in series, a byte has to be sent out for each,

An important point about using shift registers: you must keep track of their state independently. This typically requires a global or static array representing all the shift register outputs and their current state.

This function maintains a static array of bytes representing the intended output state of all shift register outputs being tracked, and generates a corresponding bit mask to send to the registers. It is called every time you want to change the state of a register output.

void registerWrite(int whichOut, int whichState) {
  static byte outputStates[NUMSTALLS];
  outputStates[whichOut] = whichState;
  // the bits to send
  byte bitsToSend = 0;

  // turn off the output while shifting bits
  digitalWrite(latchPIN, LOW);  

  // Set bits according to the outputStates Array
  for(int i = 0; i < NUMSTALLS; i++){
    bitWrite(bitsToSend, i, outputStates[i]);
  }

  // shift the bits out:
  shiftOut(dataPIN, clockPIN, MSBFIRST, bitsToSend);

  // turn on the output 
  digitalWrite(latchPIN, HIGH);
}

Setting Lighting

All lighting is set by stall number using another function, which has to manage both the shift register and the LED strip to set the light in a given stall.

void stall_set(int stall, int state){
  byte *led_rgb; // pointer to a color array
  int lamp_state = HIGH;

  // set the led pointer to the correct color array for the requested state
  // set lamp off if new state is STALL_OFF
  switch(state){
    case STALL_HIGH:
      led_rgb = base_high;
      break;
    case STALL_OFF:
      led_rgb = blue_low;
      lamp_state = LOW;
      break;
    case STALL_LOW:
      led_rgb = base_low;
      break;    
  }
  registerWrite(stall, lamp_state); 
  strip.setPixelColor(4 - stall, strip.Color(led_rgb[0], led_rgb[1], led_rgb[2])); //rear LED
  strip.setPixelColor(stall + 5, strip.Color(led_rgb[0], led_rgb[1], led_rgb[2])); //front LED
  strip.show();
}

Setup()

Those preliminaries out of the way, here is the setup function:

void setup(){
  // start Ethernet and UDP:
  IPMAC ipm = readIPMAC(); 
  Ethernet.begin(ipm.mac, ipm.ip);
  Udp.begin(UDP_PORT);
  // set pins used by the shift register
  pinMode(clockPIN, OUTPUT);
  pinMode(latchPIN, OUTPUT);
  pinMode(dataPIN, OUTPUT);
  // initialize the lighting system
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  delay(500);
  for(int i = 0; i < NUMSTALLS; i++){ // set stalls to off / night mode
    stall_set(i, STALL_OFF); 
  }
}

Loop()

The main loop is conceptually simple. On each repeat of the loop, the sketch checks to see if it has received a UDP packet. If it has a packet, the sketch executes the requested command.

void loop(){
  // static array to hold stall states
  static int stalls[5] = {0, 0, 0, 0, 0};
  int i, stall, req;
  // poll the network
  // if no actual packet, pkt.function == "0"
  PKT_DEF pkt = pollNet();
  
  // process the packet
  // case pkt.function == 0 will fall through
  switch(pkt.function.toInt()){
    case 1: //night/off [low blue ambient light]
      for(i = 0; i < NUMSTALLS; i++){
        stall_set(i, STALL_OFF);
        stalls[i] = STALL_OFF;
      }
      break;
    case 2: //all lights low
      for(i = 0; i < NUMSTALLS; i++){
        stall_set(i, STALL_LOW);
        stalls[i] = STALL_LOW;
      }
      break;
    case 3: //all lights high
      for(i = 0; i < NUMSTALLS; i++){
        stall_set(i, STALL_HIGH);
        stalls[i] = STALL_HIGH;
      }
      break;
    case 4: //Stall Control 
      // stall id is in pkt.option
      // pkt.data is used to convey a specific state request
      stall = pkt.option.toInt();
      // stall id should be 1 - 5
      // convert to internal numbering, 0 - 4
      if(stall > 0) stall--;
      if(stall >= 0 && stall < NUMSTALLS){
        if(pkt.data.length() > 0){
          // specific state request was received
          req = pkt.data.toInt();
          if(req >= -1 && req <= 1){
            stall_set(stall, req);
            stalls[stall] = req;
          }
        } else {
          // general toggle request
          switch(stalls[stall]){ // act based on current state
            case STALL_LOW:
              stall_set(stall, STALL_HIGH);
              stalls[stall] = STALL_HIGH;
              break;
            case STALL_OFF:
            case STALL_HIGH:
              stall_set(stall, STALL_LOW);
              stalls[stall] = STALL_LOW;
              break;
          }
        }        
      }    
      break;
  }
}

Simple Network Command System

UDP is a simple protocol. You can send a small (up to 24 bytes) packet containing anything you want. Its up to the receiving sketch to interpret the packet.

I’ve decided on a numerical command system. For now, commands are structured as a function, an option for executing the function if needed, plus a third optional data segment. The function and option are numbers; the data segment could be anything, but in this sketch is treated as a number.

The simplest way to build or interpret UDP packets is to use strings — a more advanced and useful form of character arrays intended to support human readable text. That is because packets are handled with character arrays and string semantics makes manipulation easier. Using strings also makes debugging easier!

For example, lets say that you want to tell the roundhouse to turn on stall 3 at full brightness. Stall control is function “4”, the stall id “3” is the option, and “1” (the value of STALL_HIGH in the sketch) requests the high brightness state. That packet would be a string of characters looking like this:

4/3/1/

The slash (“/”) is a delimiter (used to mark the end of a field) to allow interpretation of a packet where the elements are variable length. To go to low brightness state, the data field contains the value of STALL_LOW:

4/3/-1/

The call to pollNet() returns data in the form of a structure that separates out the three possible components of a packet. Here’s the type definition (I keep this in my library instead of individual sketches):

typedef struct PKT_DEF {
  String function; 
  String option;
  String data;
};

The pollNet() function checks to see if a new packet is available, and if so reads it into local memory, then passes the packet to the parsePKT() function which returns the packet data in the form of a PKT_DEF structure. PollNet() then sends a packet containing the string “ack” back to the originator of the command.

struct PKT_DEF pollNet(){
  char packetBuffer[UDP_TX_PACKET_MAX_SIZE + 1]; 
  String reply, strpkt;
  PKT_DEF pkt;
  int packetSize;
  // check for a new packet
  packetSize = Udp.parsePacket();
  if(packetSize)
  {     
    // read the packet into packetBuffer
    Udp.read(packetBuffer,UDP_TX_PACKET_MAX_SIZE);
    // convert packet to a string
    for(int i = 0; i < packetSize; i++){
      strpkt += packetBuffer[i];
    }
    // parse the packet
    pkt = parsePKT(strpkt);
    // send a reply, to the IP address and port that sent us the command
    reply = "ack";
    reply.toCharArray(packetBuffer, UDP_TX_PACKET_MAX_SIZE);
    Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
    Udp.write(packetBuffer);
    Udp.endPacket();
  } else {
    // no new packet
    pkt.function = "0";
  }
  return pkt;
 }
struct PKT_DEF parsePKT(String packet) {
   PKT_DEF pkt;
   int segment = 1;
   char delimiter = '/';
   int delimIndex = packet.indexOf(delimiter);
   if(delimIndex == 0) { 
    // drop leading delimiter, if any
    // some older experimental sketches may still be using a leading delimiter
    packet.remove(0,1);
    delimIndex = packet.indexOf(delimiter);
   }
   int lastDelim = -1;
   while(delimIndex >= 0 && delimIndex < packet.length()) {
     switch(segment){
       case 1:
         pkt.function = packet.substring(lastDelim + 1, delimIndex);
         break;
       case 2:
         pkt.option = packet.substring(lastDelim + 1, delimIndex);
         break;
       case 3: 
         pkt.data = packet.substring(lastDelim + 1, delimIndex);
         break;
       default:      
         break;
     }
     segment++;
     lastDelim = delimIndex;
     delimIndex = packet.indexOf(delimiter, lastDelim + 1);  
   }
   // if we don't already have a data field  
   // any trailing element without a deliminter is data
   // makes the third delimiter optional
   if(pkt.data.length() == 0 && lastDelim < packet.length()){ 
     pkt.data = packet.substring(lastDelim + 1);
   }  
   return pkt;
 }

That is the entire sketch. Basically this is the first version of the command and control systems that will be running on the layout. As you can see, 24 bytes is more than enough to convey any command plus parameters I wish. This system, while rudimentary, is more than enough to command an entire layout full of Arduino controllers.

I’m already working on improvements.

Rebuilding & Lighting a Roundhouse

One of the casualties of 15+ years of moving box storage was a Walthers (Heljan) Union City Roundhouse with 6 bays. No longer in the catalog, this was a really nice brick structure with a little extra decorative masonry, as might have been built in a prosperous town in the early 20th century. The “modern” roundhouse models widely available today are more shed-like and (to my mind, anyway) less elegant.

Roundhouse in pieces

Roundhouse in pieces

The damage could have been a lot worse; mostly it came apart at glue joints. A little breakage, but not much. I originally built this twenty years ago as an exterior model; no interior lighting or details. It was nicely enough colored that it worked well unpainted (though I intended to get to that…. some day). No anti-crazing glue was available in those days, so the windows which look fine on the outside look atrocious on the inside.

Decisions, Decisions

The model did not have the common decency to completely disassemble itself, which would have made this much easier, so I had some choices to make. In contrast to the glue joints that failed, the rest were showing no signs of giving up easily.

First and most obvious was whether to rebuild the 6th bay, since most major pieces were intact. For the sake of the space available on the L&NC, cutting back to 5 bays makes sense without diminishing the impact of this model. I could have gone to 4, but the broken frame for the 5th bay door presented a nice weathering opportunity, which we’ll see later.

Secondly, what kind of lighting and how? I was thinking incandescent, and hanging lights would be authentic, so some sort of hanging lamp with an industrial style shade seemed like a good idea. I found both led and incandescent options; I decided to try the incandescent type.

Getting Started

Removing the floor turned out to be the easiest way to open up the model, so I decided to reconstruct the model with a solid top structure that is removable from a separate base. Putting aside the floor, I reassembled the top structure, leaving one roof section off where I planned to route wiring.

Main structure, partly reassembled

Main structure, partly reassembled

That decision, along with the intended orientation on the L&NC, spawned a few other choices.  Now, when in operation, the interior will be viewable primarily through the doors and secondarily through the windows and skylights. Since the most open sight line will be through the doors toward the back, I decided to attempt some improvement of interior finish along the back wall.

Painted Window Castings

Painted Interior Window Castings

I pried off the clear styrene, which was as difficult as I would have expected, requiring some grinding and sanding to get rid of stubborn bits holding on to the green window part.  Then I painted the window part brick, leaving muntins and a thin window frame green. Having done the back wall I re-evaluated the other sight lines and concluded that removing the glazing elsewhere was probably not worth the trouble (I’ll keep that option open for later).

Light coming through simulated glass.

Light coming through simulated glass.

Since the glazing was gone, I decided to try using Testors Clear Parts Cement & Window Maker to make ‘glass’ in one pair of rear windows. It was easy enough to do if you follow the instructions, and the effect is interesting.  I probably used a little too much, but the material seems to have a lens effect in windows this small. This simulates older glass nicely, but does not create a “see-through” window (for purposes of viewing the interior). The way the Roundhouse will be oriented on the L&NC, the back window will face a blocked (by the furnace) area, so you will not be able to directly look through these windows in any case. So I think I will do the rest of the rear windows the same way.

Paint Cheapskate

Model paint is notoriously expensive (over $5 per oz, discounted) and, frankly, nothing special beyond formulating specific colors that duplicate prototype colors. In this respect I’m lucky there is no convenient hobby shop here; that has forced me to look to other solutions which, to my delight, are vastly cheaper.

Liquitex BASIC Acrylic Paint (available at all major art/craft store and on Amazon), at $5 per 4 oz tube (cheaper in even larger quantities, or sets) is a good deal.  It is available in a wide range of standard artists colors that are easily mixed to create any color you want (just keep track of the formula!). These are formulated for general artist use so they are thicker than standard model paints. I mix small amounts of paint with acrylic thinners to get whatever consistency I want.

My brick color is a simple mix of 50%Red Oxide and 50% Burnt Umber. My color for light colored mortar, masonry, stone and concrete is Titanium White with 5% Yellow and 5% Grey (a 50% Grey) – basically a dab of each into a larger amount of white.

Installing Lights, Round 1

I found some Miniatronics parts, counter-intuitively called “Lamp Shade with Bulb,” that I thought would make good hanging lamps for the interior, so I bought 10 of them from Hobbylink.

Miniatronics 'Lamp Shade with Bulb"

Miniatronics ‘Lamp Shade with Bulb”

Here two are mounted in 1/8″ Styrene u-channel that has been painted Floquil Weathered Black (from my limited collection of old paints) to match the interior girders. To size them, I studied the interior clearances relative to locomotives and realized that clearance was limited in the tall section, and non-existent in the shorter fore and aft bay sections.

Hanging Lamps in Bay 1

Hanging Lamps in Bay 1

My feeling at that point was these lamps were not suitable for the short sections, since they would not be visible mounted up against the ceiling. That feeling increased after mounting and lighting the first pair.

Miniatronix Lamp Shade with Lamp - Lit

Miniatronix Lamp Shade with Lamp – Lit

 

I guess I should not have expected much from a 1.5 volt 30mA lamp.  Still, I’d hoped it would cast more light than that! As a decorative lamp it works fine at N scale – you get little peaks of them if you look in and they seem right. For casting light on the broader scene, even if I doubled up on them, another solution is needed.

Addressable LEDS

I’d already been working with Addressable RGB LED strips as a solution to bringing light to the layout, and it occurred to me that adding ALEDS as a hidden light source would allow me to more fully light up the interior. So that is what I did.

RGB LEDS for General Lighting

RGB LEDS for General Lighting

The individual pads are cut from a 5 meter strip and wired together with 30 GA wire. As you can see from the picture of the completed interior below, there are 10 of them with addresses 0 to 9.

Soldering Addressable LEDS

Soldering Addressable LEDS

To solder the very thin wire to the pads, I taped the leads down and slipped the pads underneath. That was more than stable enough to make the soldering fairly easy.  This particular LED is #0, so the left hand leads are heavier (22ga) for incoming power and data (for durability and handling), and the right hand leads out to the rest of the strand.

I fabricated the strand in two halves with 5 LEDS each, soldering leads from the two halves together during installation. Each half strand had to be carefully threaded through the girders.  I did break – and repair – a couple of connections the first time I tried.  After I got them threaded successfully, I glued the pads down to the styrene roof with dabs of Liquid Nails for Projects (low VOC, foam safe). You can bet I tested the  lights before gluing them down!

Completed Roundhouse, from below

Completed Roundhouse, from below

The LED wiring is simple and efficient – 1 connection to a digital pin on the Arduino, plus 1 wire each for power and ground.  The incandescent lamps were more complicated since I had no intention of giving over 10 pins on the Arduino. Further, turning them on and off individually was not necessary, so I decided to run them in “bay pairs.” Unfortunately, the 60 mA draw of a pair of these is more than you want on an Arduino pin, so that meant some sort of relay or transistor would be necessary to switch the current.

 A Shift Register and a Darlington Array

Current isn’t the only problem: there aren’t enough digital pins on an UNO to control everything I have planned.  So a pin preservation strategy is called for.

I decided to use a 74HC595 serial-in-parallel-out shift register. One data pin plus two timing pins (total of 3 pins) allow an Arduino to control 8 digital outputs; additional shift registers can be wired in series to extend the total number of outputs available without additional Arduino pins. The parallel output of these chips means that after the Arduino sends instruction bits serially, all the outputs are turned on or off at the same time (parallel output), a useful property for animation and lighting control.

Because of the higher current (and also different voltage) of the lamps, I put a UNL2803 darlington array — a transistor array that can sink high current — between the shift register and the lights. The only downside of a darlington is some current loss.

Both chips are inexpensive, running about $.60 each in small quantities at digikey.  Now that I’ve worked with them I’d say they are very useful basic parts for the Arduino enthusiast.

Power Distribution to Lamps

Power Distribution to Lamps

I fabricated a little circuit board for the roundhouse, to join the “bay pairs” together and provide a 1/8 watt resistor for each lamp to be able to run at 3.3v. The darlington array sinks current rather than sourcing it, so the circuit is a common anode (common power) with the resistors and control on the ground side.  Works fine. The circuit board was cut from a standard Radio Shack pre-etched PC board, and fits into the wiring space in the back of the Roundhouse.

I fabricated another board–version 1 of what will become a standard, chainable shift register board for layout control (but about half the size of this one) —  to be mounted under the roundhouse base, it interconnects the two ICs, taps into layout power, connects to an Arduino and connects to the structure above. The board uses +5v power for logic and LEDs, and +3.3 for distribution to the incandescent lamps.

Roundhouse Controller with an Uno

Roundhouse Controller with an Uno

The roundhouse board has an unused connector that would allow 3 more +3.3v lights to be added. I’ll probably use that capacity for some lighting outside the roundhouse structure when it is mounted on the layout, but I haven’t decided anything yet. That, by  they way, is part of the essential elegance of the Arduino way; so long as a connection pin is available, adding more lights or functionality is as easy plugging things in and modifying your software to use them.

Here is a video tour of the project and demonstration Roundhouse lighting!



The sketch loaded on the Uno is different enough from previous examples, that I will cover it in Roundhouse Rebuild Part 2.