When the MEGA is the Solution

This is a continuation of High Density DCC Block Detection. In this post I’ll talk about putting all the pieces together to create a unified controller for module 1, lower level of the L&NC.

But first, lets finish out the block detection system.

Modifying Current Sensing Procedures.

I’ve talked extensively about current sensing and block occupancy detection throughout the blog. I started with ACS712 sensors because, in part, they work with both DC and DCC. Their flaw, if you will, is that the sensor produces so much noise that its hard to discern a reading below 20 mA (officially, you can’t); and impossible under 12 mA.  That matters because 1) today’s DCC decoders typically consume 5 mA when idling with all functions off; so to detect an idle DCC locomotive, you have to be able to detect down to 5 mA; and 2) You can take advantage of the fact that DCC track power is always on, and use 10k resistor wheels to make rolling stock detectable. To do that successfully you have to be able to sense 1 mA of current flow at N scale voltage.

Current Transformer

Since the L&NC is a DCC layout I ended up going with Current Transformers, a DCC only solution that, with the right ADC, can detect current flow under 1 mA. With my 14 bit Mayhew Labs Extended ADC Shield, I’ve successfully detected current flow just under 500 µA. The Root Mean Square algorithm for detecting current has been effective with both sensor types, but with CT sensors you can get an accurate read with fewer samples than you would need with the ACS712.

Note that you can successfully read CT sensors with the Arduino built-in ADC; I’ll be doing that in the next module and will share any tweaks needed to get satisfactory detection.

As previously mentioned, the Mayhew Labs Shield and the software library that goes with it is optimized to sequentially read the ports. With this ADC, the ports have to be configured immediately before being read. So, the read function supplied in the library does just that: reads one port and prepares the next. The upshot of that is when you start a read cycle you have to do a throw away read to setup your first port. Hence, reading all the ports you can in a single pass saves time and steps.

The code to do a single read pass in DIFFERENTIAL BIPOLAR mode on three channels:

 // throw away read to prepare channel pair 0-1
 extendedADCShield.analogReadConfigNext(0, DIFFERENTIAL, BIPOLAR, RANGE5V);
 // read prepared and prepare next channel pairs; 2-3, 4-5, 6-7 (unused)
 raw[0] = extendedADCShield.analogReadConfigNext(2, DIFFERENTIAL, BIPOLAR, RANGE5V) - blocks[sensor_map[row][0]].aqv;
 raw[1] = extendedADCShield.analogReadConfigNext(4, DIFFERENTIAL, BIPOLAR, RANGE5V) - blocks[sensor_map[row][1]].aqv;
 raw[2] = extendedADCShield.analogReadConfigNext(6, DIFFERENTIAL, BIPOLAR, RANGE5V) - blocks[sensor_map[row][2]].aqv;

Each command executes in about 5μs, so the read completes in 20μs; doing it this way limits throw away reads and the time they waste.

I created a special version of my current reading function for the Mayhew Labs Extended ADC Shield. The algorithm is identical to the current sensing algorithm I’ve previously posted, except that it works on three inputs at once. The full code (available on the github site) is specific to my situation but it does demonstrate how to adapt the base current sensing method I have posted to different hardware setups.

void readSensorRow(int row){
 extern BLOCK_DEF blocks[];
 float accumulator[3];
 float raw[3];
 float delta, rms;
 unsigned int count;
 unsigned long curMicros, prevMicros = micros() - sampleInterval;
 // set the address lines to access the row
 digitalWrite(pinA, addrBits[row].A);
 digitalWrite(pinB, addrBits[row].B);
 digitalWrite(pinC, addrBits[row].C);

 count = 0;
 for(int j = 0; j < 3; j++){
   accumulator[j] = 0;
 }

 while(count < numSamples){
   curMicros = micros();
   if (curMicros - prevMicros >= sampleInterval) {
     prevMicros = curMicros;
     // prepare channel 0
     extendedADCShield.analogReadConfigNext(0, DIFFERENTIAL, BIPOLAR, RANGE5V);
    // read/prepare subsequent 3 channel pairs; 0-1, 2-3, 4-5
    raw[0] = extendedADCShield.analogReadConfigNext(2, DIFFERENTIAL, BIPOLAR, RANGE5V) - blocks[sensor_map[row][0]].aqv;
    raw[1] = extendedADCShield.analogReadConfigNext(4, DIFFERENTIAL, BIPOLAR, RANGE5V) - blocks[sensor_map[row][1]].aqv;
    raw[2] = extendedADCShield.analogReadConfigNext(6, DIFFERENTIAL, BIPOLAR, RANGE5V) - blocks[sensor_map[row][2]].aqv;
    for(int j = 0; j < 3; j++){
      raw[j] /= SENSITIVITY; // convert to amperes
    }
    for(int j = 0; j < 3; j++){
      accumulator[j] += (raw[j] * raw[j]); // sum the squares
    }
    ++count;
   }
 }
 for(int j = 0; j < 3; j++){
   //https://en.wikipedia.org/wiki/Root_mean_square
   rms = sqrt((float)accumulator[j] / (float) numSamples);
   delta = abs(blocks[sensor_map[row][j]].aqc - rms);
   blocks[sensor_map[row][j]].occ = delta > DETECTION_THRESHOLD;
 }
}

The “DETECTION_THRESHOLD” is .0006 (approx 600 µA); the “SENSITIVITY” factor is 3.3. The “sensor_map” associates specific sensors on a “row” of sensors with specific blocks. If you set up the sensors specifically so they will read in block numeric order then you wouldn’t need a “map.” In this case, the sensors are arranged according to the proximity of feeders, not numeric block order.

Constructing an Integrated Controller

So, with working Block Occupancy Detection, 9 turnouts and a few signals installed it was time to create the “controller” sketch for this module/level. In my experience to date, putting all the track functions together on a single controller is the most efficient way to ensure that all the pieces interact in a timely and correct manner. So, starting with an UNO, I built a controller stack with the UNO, an Ethernet shield, the Mayhew Labs Sheild and a connection board (a simple prototyping shield with screw terminals installed at all the pin locations, making connections easy and secure).

In essence, the controller sketch is an improved version (including my turnout class, and a signals class I’m developing) of the test loop sketch; the main difference being that there is more of everything. I compiled the sketch and had about 600 bytes of local memory left. That was pretty much the same as what I got on the test loop, so I thought it would work OK.

At first it seemed to work, but then became erratic and wouldn’t boot consistently. Uh oh. I’ve seen that sort of thing before: Memory problems! Clearly, the increased number of objects (turnouts, signals & blocks) was swamping memory resources at run-time. I’ve always known that 600 bytes remaining for local variables is right on the edge of what you can get away with, and my sketch was jumping the edge and into the void!

Time to Go MEGA

I frequently see people use MEGA’s because of all the additional pins it comes with. Since I use various pin multiplication methods, I don’t find that aspect of the MEGA compelling. But when you need more memory for code or data, the MEGA is unbeatable. When I compile the controller sketch for the MEGA, there are 6700 bytes of local memory remaining.  Plenty!

Arduino MEGA

Needless to say, everything works reliably now. I’ve tested it by letting it run for hours undisturbed, then running a train to see if things work. Perfect.

Module 1, Lower Level Controller Stack

Given that there can be a lot of “pieces” to controlling trackage, its likely that I’ll need a couple more MEGA’s before I’m done. I’ll use UNO’s and NANO’s mostly except key places where the amount of track/turnouts/signals requires a more robust solution.

I’m really done with the knock-offs and am making the effort to buy genuine. Arduino prices are down a little, which helps. This genuine MEGA cost me a little over $30 (its up a little today; prices do fluctuate some) on Amazon which I think is fine for what it is and what I’m able to get out of it (current official list is $38). You still can’t beat the overall cost of the system; off-the shelf “model railroading” equivalents (BOD systems, signals, etc) will run double or more for the amount of track, turnouts and signals involved.

The Challenges of Stacking

Even with a MEGA, stacking shields can present problems because each shield needs to monopolize at least one pin (for SS [Slave Select] on the SPI interface), which means that pin cannot be used for anything else. Further, some shields come with “extras” that require an additional dedicated pin. The Ethernet shields I use, for example, include a MicroSD Card reader; so in addition to taking over pin 10 for the network SS, pin 4 is required for the MicroSD and becomes unusable for anything else. Some shields give you some ability to modify pin assignments.  The Mayhew Labs Extended ADC shield also wants to use pin 10 for its SS, but you can change that and a couple of other pin assignments. Sometimes the change is made by closing a solder jumper. Other times you need a jumper wire to relocate a pin assignment. The Mayhew Labs shield requires a little of both but is fairly adaptable as a consequence.

 

Adafruit PWM Driver

Pin usage issues is one reason why the Adafruit 16 Channel PWM Driver for the turnouts is so important here: it uses the I2C wire interface, totally separate from SPI and the regular pins, so it runs happily along side the other devices without consuming pins. If you look at a recent UNO or MEGA, on the digital pin side after D13 there are a couple of additional slots labeled SCL and SCA; some shields (like the Ethernet shield) don’t even have pins in those locations (see the image of the Controller Stack, above). Consequently, I have to jumper the pins from the Mayhew Labs shield to the connection shield on top of the stack.

This leaves all the regular analog ports  and several regular digital/pwm pins available for other uses, such as connecting to the DuinoNode network that controls lights and animation. On the Mega, there are dozens of remaining pins, both digital and analog should I ever need them.

Timing Issues

My “round robin” sensor reading function for this module reads one “row” of three sensors; there are 8 rows total, so the function is called for each row.

Why did I do it this way? Well, as previously discussed, it takes time to perform Block Occupancy Detection. With the Mayhew Labs shield and a sketch that is optimized for sequential reads, I’ve got the process of reading all 24 blocks down to about 1/2 second.

That’s great all things considered. But unfortunately, it dramatically slows down turnout motion because that big block of busy prevents running the turnout.update() method frequently enough. The first time I ran it without any changes it took an average of 30 seconds to move a turnout. Way too long.

So, I did two things to adjust and speed up turnout motion. 1) I added a movement increment option to the turnout class, so double or triple moves are possible if needed; and 2) I broke the block occupancy detection process down to one row at a time interleaved with turnout updates. The key loop() code looks like this:

 for(int i = 0; i < 8; i++){
   readSensorRow(i);
   for(int j = 0; j < NUM_TURNOUTS; j++){
     turnouts[j]->update(currentMillis);
   }
   currentMillis = millis();
 }

Turnout motion is satisfactory now

Coming Soon

I know quite a few people are interested in the turntable. While I’ve talked about the mechanism, I haven’t gotten into programming and control, so that’s next. That will probably be the last major post exclusively focused the lower level of module 1 as I move onto the adjacent modules and completing the lower level. I’m way over due for a video, too, and I think there’s enough working now to justify a little video tour.

Until then, Happy Railroading.

 

High Density DCC Block Detection

If you have been following along, you already know that the L&NC module 1, lower level is a complex beast. Here’s the state of the accumulated electronics as of today. I’ve talked about quite a bit of it, but there are still key areas to discuss.

Block detection on this first module turned out to be somewhat challenging because of the large number of blocks—24— in a relatively small space.

24 Blocks?

Obviously, the mainline, secondary/yard lead track and a siding are only part of the story. Two big factors in the block count are Red Bluffs Yard (5) and the Roundhouse turntable, tracks and lead in (9). Block arrangement is sometimes dictated by the Peco power routing turnouts I use and the need for insulating gaps; block 11 is ultra small (I’m rethinking the block boundaries here — adding/consolidating feeders and re-gapping is no big deal) because it is at the edge of the module and includes a turnout. The feeder map below will give you some idea of the block layout.

Module 1 LL Feeder Map. Block 1 includes all three turnouts that follow feeder1

Current sensing takes time.

Regardless of the type of sensor you use, the single most important factor is the time it takes for an ADC (analog-to-digital converter) to read the sensor. On an Arduino board with a 10-bit ADC (most boards), it takes 50 μs; if you need to do 100 reads to get an RMS reading, the total read time will be more than 5 ms; add waiting between readings plus calculation time and you need 80 – 100 ms to read a single current sensor. If you are doing only a few, that should present no problems.

But with 24 blocks you are looking at 2+ seconds to do a complete read cycle at that rate. That is an eternity if you are trying to do more than that with the microcontroller. Frankly, its even an eternity just in the context of BOD, because that 2+ seconds for a read cycle limits how fast the system can respond to changes in block occupancy.

Mayhew Labs Extended ADC Shield

I knew I would need a faster and more capable ADC, so I’ve been working with the Mayhew Labs 14 bit Extended ADC Shield. The Mayhew Labs Extended ADC Shield, which is sometimes available at Amazon, comes in 12, 14 and 16 bit flavors. Bits, in the case of an ADC, determine the smallest detectable voltage out of a given range. An ADC with 14 bits of resolution can divide a 0 – 5 volt range into 16,384 steps (2 ^ 14), making the lowest detectable current (and the detection step interval) 305 μV. In contrast, the 10 bit ADC built into an UNO is capable of only 1024 ( 2 ^ 10) steps, making the lowest detectable current (and step interval) 4.88 mV.  That is a huge difference in detection sensitivity.

Mayhew Labs Extended ADC Shield

In addition to sensitivity, the shield is fast, producing a reading in 5 µs. That is 10 times faster than the conversion rate of a built-in Arduino ADC; in fact, the conversion is completed by the time the UNO is ready to execute the next instruction after triggering the read.

The shield and the supporting Arduino Library are optimized to scan the ports sequentially. The system is at its most efficient when you read all the ports in sequence in a single pass. That requires some adaptation of my current sensing methods, although the detection algorithm itself remains unchanged.

The Mayhew Labs Extended ADC shield has eight ports that can be used in one of two fundamental ways: single ended or differential. Single ended — the same mode used by the Arduino ADC — reads each port individually; a sensor is attached to the port and to ground. Differential mode reads and compares two adjacent ports. With CT sensors, each port of the differential pair connects to one side of the sensor creating a complete circuit. CT sensors produce an AC current at the same frequency as DCC; in single ended mode the ADC only sees the positive phase of the cycle; in DIFFERENTIAL BIPOLAR mode the ADC see both phases of the cycle. Accordingly, reading CT sensors in differential bipolar mode will produce the most accurate results and require the fewest reads for calculating an RMS. However, because the DCC cycle is high frequency (8 Khz), its possible to get reasonably accurate results in single-ended mode because the ADC will see multiple positive DCC phases during each read.

It didn’t take many experiments to convince me to use the Mayhew Labs ADC in differential mode.  That means the shield is limited to 4 sensor connections. Made little difference inasmuch as I already had a problem with 24 blocks to watch from a limited number of ADC ports. I’ll concede that at $35, the Mayhew Labs shield is an expensive board; but given how cheap the CT sensors are, the overall cost of the system for 24 blocks is under $100.  I don’t think it is possible to do the job at this scale with an off-the-shelf BOD system for anything like $100. I anticipate using using the shield in one or two more areas where there are 8 or more blocks to watch.

Muxing/Demuxing to BOD Bliss

I’ve frequently talked about techniques for multiplying digital pins, using shift registers and other devices.  It is also possible to multiply analog connections to an Arduino or an external ADC using a Multiplexer/Demultiplexer IC. A mux/demux is basically a device with a common I/O port and 8 selectable I/O ports, plus 3 address lines to select which port is connected to the common I/O. Think of it as a rotary switch with 8 positions, selectable via the address line. The important part is that each connection is isolated from the others so that only one selectable port can be active and cannot by affected by the other ports.

I use the Texas Instruments CD4051B CMOS Single 8-Channel Analog Multiplexer/Demultiplexer [ Digi-Key ]. This functional diagram of the CD4051B show how it works and includes the address bits required to select an I/O channel. [Note: other CD405XB variants have different port arrangements that are useful in other situations.]

TI CD4051B Functional Diagram

To use these, you have to supply power, ground, three digital address lines and an I/O line back to your microprocessor—two power connections and four logic connections. So you are getting a 8 to 1 improvement in analog capacity, at the cost of three digital address lines, which is OK; with connection sharing (sounds odd, but you’ll see it works), my three address lines can control multiple banks of 8 sensors, further improving pin efficiency.

Round Robin Reads

Since I have 24 blocks, and each CD4051B IC supports 8 channels, three groups of 8 sensors seemed like the optimal approach. The idea is to do an optimized read of all three boards on each pass, using the address lines to select which sensors are read. I think of this as a “round robin” technique. I’ll get into the details of the reading technique in second part of this two part post.

I set out to create sensor boards and the very first question that arose was this: how wise was it to have one side of each CT connected to a shared I/O line, with only the other side of the CT’s isolated through the mux/demux? I’m not an electrical engineer, but I had a feeling that connecting all CT’s together on one side might not be a good idea.

I decided to follow my instincts and build two boards using two 4051B’s to fully isolate each sensor. Here are pictures of that version (annotated as much as possible for those who are interested in building their own):

Top of CT Sensor Board

CT Sensor Board, Top View with ICs

Bottom side of CT Sensor Board.

A couple of notes about this board:

  1. I start with a Busboard Prototype Systems B1 Solderable Breadboard. The board has 6 rails that, with good planning, can cut the number of jumpers you have to use. Use the outer rails for power (+5v on one side, Ground on other); the other two rails on each side can be used to connect sensors to the mux/demux ICs. I didn’t get it quite right on any of them and left some unused rails as a result.
  2. Use high accuracy resistors at the CT’s so that they are essentially identical. I use 100Ω resistors with .1% tolerance [Digi-Key].
  3. I get CT sensors in quantity from Digi-Key. I use universal jumper sets that I get on Amazon.
  4. I have a fetish for power indication LED’s so I know a board is live. I’m starting to get wise about the brightness of today’s LEDs, though, and am using 1K resistors to cut the brightness back (skip the calculations and use a bigger resistor!).
  5. Once I decided on a connection protocol (see the pictures), building the board is a matter of patiently matching connections per the IC pin-outs — like many IC’s this one will make you wish the designers had shown some mercy in arranging the pin-outs! But basically its this simple: connect channel 0 of mux1 to one side of sensor 0, then channel 0 of mux 2 to the other side of sensor 0. Do that for 7 more CT sensors and you’ll have an eight sensor detection board that is as effective as anything out there. Be consistent about which side of the sensors are connected to which mux.

    CD4051B Pin Outs NOTE: INH, VEE and VSS will be grounded; supply +5 volts at VDD. A, B and C are the three address lines. COM OUT/IN is the common I/O channel that connects back to the ADC.

  6. I use 22 gauge wire for feeders. I’ve learned to cut feeder leads for each sensor when I build the board — about 12″ long. The lead needs to wrap 3 times around the sensor, with a tail on each side for splicing to the feeders and connecting to the power distribution board. This makes the installation process a whole lot easier. The “wrap three times” thing is almost a catechism; 2 times will work but the signal is significantly weaker. This is an induction system, so the more times wrapped the better.
  7. I secure the wrapped feeder on the CT coil with a dab of hot glue.
  8. I’m sure you color code your feeders. I recommend that you always read the same rail on all CT sensors.

That 8 sensor board installed on the layout.

How NOT to do it.

But, since part of the point of this exercise is to learn and apply knowledge, I also made a single mux/demux version with one side of the CT’s on a shared line. That would simplify wiring and omit an IC; a good thing, right? Reduces your IC count and the number of connections you have to solder. Initial tests on the bench seemed to show that it worked just fine, thus making me further question my sanity.

But then I installed the whole system, and wouldn’t you know that under load my pessimism was rewarded.  The odd board produced odd readings, clearly off the norms of the other two boards. Lots of cross-talk was evident; occupancy of one block would cause other blocks to show false readings. I’m sure a real electrical engineer would find that pretty funny and predictable.

Here is a picture of the errant board.  I put this here as a warning NOT to do this or you will be profoundly disappointed.

Bad board with all sensors connected to a common line on one side. Looks attractively simple. Don’t do it!

The Interface board.

To consolidate the three boards and their connections, I had to create an interface board.

Interface board linking detector boards to power and microcontroller.

The trick here is that the address lines are shared; address lines set the same address on all three boards at the same time.  So, when a “round robin” read cycle occurs, the same channel is read on each board on each pass: the first pass is channel 0, next channel 1 and so on.

To Be Continued

I had to modify my current sensing algorithm to accommodate the “round robin reads.” That was easy enough. But when I got around to integrating all the functionality on an UNO, I ran into the proverbial brick wall.

More in the companion post that follows.

C++ Objects for Layout Control, Part 2 — Turnouts

In C++ Objects for Layout Control, Part 1 I did an introduction to C++ objects, and demonstrated the basics of OOP with a simple “fire” object that I use to run an LED to simulate a fire in the turntable operator’s hut on the L&NC. In the demo sketch I did two instances of the fire object with two LED’s to demonstrate object independence and scalability.

In this post, we’ll take the basics of C++ objects for animation and extend them to the most common animated object on our layouts: turnouts.

L&NC Progress

Before I go hardcore programmer on you, I thought’ I’d share a few progress notes & pictures. Last weekend we rejoined the three lower level modules together for the first time in over a year.

All Three lower level sections reassembled.

Crossing from module 1 to module 2 for the first time.

Red Bluffs Yard

Another view of Modules ! & 2

I was especially pleased that the three sections came back together perfectly, even though they have been apart for over a year. I attribute the stability of the structures and the accuracy of the alignment to the style of framing, use of hardwoods and the McMaster-Carr alignment pins I use to assure the sections align correctly when put back to together. It all looked good on paper; but until proven you can’t be completely sure. Now I’m confident I can take the layout apart and put it back together again reliably.

I’m getting the basic wiring and interconnections with modules 2 & 3 completed, following the same basic methods as on module 1 (the main difference from where I began is I’ve moved to current transformers for occupancy detection). Once the basic wiring is in and the turnouts are mated with servos, I’ll start in with scenicking. I’ll cover and further shape the mountain with plaster cloth and sculptamold. Since I am doing more roads (using the Woodland Scenics road system which I think works pretty well; more about that at the end of this previous post) on both modules 2 & 3, I’ll pour those first. I find it best to get the roads in before applying any paint or other material. All that should keep me busy for a while.

Turnout Issues & Requirements

Like the fire object, the turnout object requires certain basic parameters to run, and has to be able to keep track of its own state. The turnout object has to respond to positioning commands and manage the turnouts’ motion to achieve a slow, scale appropriate movement from one alignment to the other.

The added complication is that the turnout class will need to deal with different hardware configurations and interfaces in different situations. For example, on the L&NC module I’ve been working on there are 9 turnouts and I’m using the Adafruit PWM Driver to run the servos and additional PWM devices, which I discussed in L&NC Update: Running Lots of Turnouts. The adjacent module has only two turnouts, so I’ll use the standard servo library and a couple of pins there. But regardless of the hardware interface, I want to use the same software objects in both places so they perform the same way.

Properties

The turnout class is a little more complicated than the fire class. Its basic properties include the pin (in the case of the Adafruit driver, the channel) the servo is attached to, the current alignment of the turnout, the default alignment and the servo settings for the main and divergent positions (pos_main and pos_divergent). Then, to facilitate motion, there are a variety of properties that work together: delay between moves, the increment (1 or more units) of each move, the current position, the target position and the target alignment.

class turnout
{
private:
 // object properties
 int pin;
 int pos_main;
 int pos_div;
 int align_default;
 int alignment;
 
 // motion data
 bool is_moving;
 int move_delay;
 int increment;
 int pos_now;
 int target_pos;
 int target_alignment;
 unsigned long last_move;
}

I mentioned that I am using the turnout class in different situations where the hardware that runs the servos could be a standard pin, or an external driver board such as the Adafruit PWM Driver. To complicate matters, while the standard Servo Library positions by degrees, the Adafruit PWM Driver positions by the “tick” value of the desired PWM setting.  These are different units and different scales; but it doesn’t matter. So long the turnout class is initialized with a correct set of values (in the same units) for the hardware interface in question, it works consistently.

Methods

For the turnout class I have created an interface of public methods for interacting with the class. Additionally, the class has a private hardware interface for interacting with the hardware environment.

The Constructor

The constructor is straight forward with one twist. Because a lot of arguments are required to set up the object, I’m using a data structure to pass most of the arguments.

typedef struct TURNOUT_PARAMS {
 int pin;
 int pos_main;
 int pos_div;
 int align_default;
 int move_delay;
};

On the L&NC Module1, Lower Level the array of turnout parameters looks like this (STANDARD_DELAY is 20):

TURNOUT_PARAMS tdef[NUM_TURNOUTS] = {
 {0, 375, 310, ALIGN_MAIN, STANDARD_DELAY},
 {1, 335, 408, ALIGN_MAIN, STANDARD_DELAY},
 {2, 330, 370, ALIGN_MAIN, 30},
 {3, 284, 345, ALIGN_MAIN, STANDARD_DELAY},
 {4, 355, 415, ALIGN_MAIN, STANDARD_DELAY},
 {5, 291, 390, ALIGN_MAIN, STANDARD_DELAY},
 {6, 285, 373, ALIGN_MAIN, STANDARD_DELAY},
 {7, 355, 285, ALIGN_MAIN, STANDARD_DELAY},
 {8, 305, 372, ALIGN_MAIN, STANDARD_DELAY}
 };

The constructor takes a pointer to a TURNOUT_PARAMS variable, plus an optional movement_increment argument. I added the movement increment parameter (and the corresponding class property) as an additional factor in turnout motion after encountering problems integrating multiple processes on the Uno on Module 1. Manipulating both delay and increment values improves control over the movement of servos in different situations. On the L&NC Module 1, Lower Level there are both a lot of turnouts and a lot of block detectors. I’ll discuss the issues more in an upcoming post, but the resources required for block detection make it difficult to move the servos fast enough without adjusting the movement increment.

// Constructor
 turnout(TURNOUT_PARAMS *parameters, int movement_increment = 1){
   pin = parameters->pin;
   pos_main = parameters->pos_main;
   pos_div = parameters->pos_div;
   align_default = parameters->align_default;
   move_delay = parameters->move_delay;
   is_moving = false;
   increment = movement_increment;
   init_servo();
 }

After capturing the parameter values, the constructor invokes the private init_servo() method which I will talk about below in the hardware interface section.

Using the array of parameters above, the turnout objects are instantiated with this bit of code in setup, taking the default value for movement_increment (turnouts is global, declared before setup):

 turnout *turnouts[NUM_TURNOUTS];
 for(int i = 0; i < NUM_TURNOUTS; i++){
   turnouts[i] = new turnout(&tdef[i]);
 }

What’s all this Pointer Stuff?

If you’ve been working with Arduino for a while you’ve likely dealt with pointers before. For those who are unfamiliar with the term (and it is a complex subject, worth learning), pointers are the memory address of a variable. Pointers are another way to access variables unique to the C/C++ languages. In the case of the Constructor, passing a pointer to the parameters array grants access to a complex data structure without having to copy it first. The Reference (&) operator returns a pointer to an variable (&tdef[i]); the dereference (*) operator declares a pointer variable used to access a pointer’s value — such as TURNOUT_PARAMS *parameters in the Constructor. With pointers to data structures or objects, use “->” to access members instead of “.”; eg: params->data instead of params.data or object->method() instead of object.method().

The Public Interface

The public interface is comprised for four methods. The getAlignment() method is a good example of the correct way to share the value of an internal property with an external process. Its correct because it outputs the value of the alignment property without breaking its protection as a private property (and thus exposing it to being changed externally).

 int getAlignment(){
   return alignment;
 }

Another simple method provides a way to toggle the position of the turnout back and forth.

void toggle(){
   if(alignment == ALIGN_MAIN){
     set(ALIGN_DIVERGENT);
   } else {
     set(ALIGN_MAIN);
   }
 }

The alignment can be one of three values at any given time.

#define ALIGN_NONE 0
#define ALIGN_MAIN 1
#define ALIGN_DIVERGENT 2

As you’ll see, ALIGN_NONE indicates that the turnout is in motion.

The heart of the logic for moving the turnout is in the set() and update() methods.

void set(int align){
 if(align != alignment){
   is_moving = true;
   last_move = 0;
   target_alignment = align;
   alignment = ALIGN_NONE;
   switch(align){
     case ALIGN_MAIN:
       target_pos = pos_main;
       break;
     case ALIGN_DIVERGENT:
       target_pos = pos_div;
       break;
     }
   }
 }

 void update(unsigned long curMillis) {
   if(is_moving){ 
     if((curMillis - last_move) >= move_delay){
       last_move = curMillis;
       if (pos_now < target_pos) { // if the new position is higher
         pos_now = min(pos_now + increment, target_pos);
         setServo(pos_now);
       } else { // otherwise the new position is equal or lower
         if (pos_now != target_pos) { // not already at destination
           pos_now = max(pos_now - increment, target_pos);
           setServo(pos_now);
         }
       }
       if (pos_now == target_pos) {
          is_moving = false;
          last_move = 0;
          alignment = target_alignment;
       }
     }
   }
 }

Motion works by setting motion variables in the set() method then calling update() repeatedly to execute the motion. Update() is intended to be called continually as part of the main loop() while the sketch is running—my multitasking model is to get the current time (millis()) at the start of every iteration of the main loop(), then pass that value to every object that uses time to manage its own state. It is very important that the very first logic test within the update() method is whether or not the turnout is in motion; if not in motion the method exits immediately. With nine turnouts on the lower lever of module 1, efficiency is necessary or the sketch bogs down.

Hardware Interface

Up to this point there has been no direct interaction with hardware. Instead there have been calls to two private methods: init_servo() and setServo(). These two private methods interact directly with the servo hardware.

I should emphasize that any motor type can be used with appropriate connections, even 12 volt stall motors. I like servos because they are cheap and easily supported in the Arduino world. But don’t feel that just because you have a different motor type you can’t run them with an Arduino. The point of creating a private, protected hardware interface is to isolate all the hardware specific stuff in one place while presenting a more general public interface. That makes it much easier to drive a wide variety of hardware while behaving consistently.

private:
 void init_servo(){
   int data;
   switch(align_default){
     case ALIGN_MAIN:
     data = pos_main;
     break;
   case ALIGN_DIVERGENT:
     data = pos_div;
     break;
   }
   setServo(data);
   is_moving = false;
   pos_now = data;
   alignment = align_default;
 }
 // hardware interface 
 void setServo(int data){
   // use compiler directives to determine which
   // method is used to drive the actual hardware
   #ifdef ADAF_DRIVER
   extern Adafruit_PWMServoDriver pwm;
   pwm.setPWM(pin, 0, data);
   #endif
   #ifdef SERVO_LIB
   extern servo servos;
   servos[pin]->write(data);
   #endif
 }

Init_servo() is for doing whatever your motor needs to set up. Here the method sets the default alignment and commands the servo to move to that position immediately. This is private so that it can never be called from outside a turnout object.

setServo() moves the servo to a specific position. I use a compiler directive — by defining either ADAF_DRIVER or SERVO_LIB (but not both)— to determine which hardware system is in use. In some cases I’ll be using the Adafruit 16 Channel PWM Driver and its library; in other cases regular pins with the native servo library.

The Whole Enchilada

I’m posting the turnout class on the github site.

This time I’m posting it in the form of a header — *.h — file. To use it, copy it into your sketch directory. The Arduino IDE will recognize and allow you to edit the “h” file, but it won’t automatically include it in your build.  To in include it in the build you must explicitly include it near the top of your main sketch this way:

#include "turnout.h"

Why do it this way? Because the class definition has to be seen by the compiler before it can be used to create run-time objects. Therefore, in a single file sketch you’d have to put the class definition at the top of the sketch. If you have a lot of header material of that sort (class definitions, typdefs, etc.), the top of your sketch can get long and the whole thing harder to maintain.  By putting the class definition in a header file you can segregate different elements of your sketch, control exactly when the compiler sees it during the build process, ensure all dependencies are satisfied and keep your main sketch file clean and uncluttered. The bigger your sketch gets the more important this becomes.

Coming Soon

Back to block occupancy detection with 24 blocks and one Arduino to rule them all! To say scale started to be a problem would be an understatement. More about that in the next post.

Until then, Happy Railroading!

 

 

C++ Objects for Layout Control, Part 1

The programming language supported by the Arduino IDE is C++, an object-oriented super-set of the C language (one of the fundamental languages of the computer programming world). This post is geared to newbies who have little or no previous experience with object-oriented programming. Experienced hands can skip past the basic explanations and see what I’m doing with it in a practical example.

Object-oriented programming (OOP) allows the programmer to conceptualize and write software in a way that is less like how the machine runs it (a list of instructions) and more like how humans think (objects, abstractions, relationships, etc.). Of course, you still have to write specific procedures, but they are placed in context within a software object. Don’t worry if this doesn’t make sense yet.

Without going into the more exotic aspects of OOP theory (we may get to some of those eventually, but not today), the point of OOP is to create the software equivalent of a “black box”—something that you can give data to and get a result from without knowing anything about how it works internally. This makes code highly reusable, the second major point of OOP.

OOP and the Arduino IDE

The Arduino IDE is a simplified environment within which to write C++ code. You can create and edit the formal file structures of C++ (.h header files and .cpp implementation files) within the IDE, but the IDE doesn’t require it and allows you do do things with less formality than traditional C++ environments, so its a good place to get to know C++ objects. Those who write in C++ (whether professionally or as a dedicated amateur) can include their own work by installing it as a library the Arduino IDE can compile and include.

Given the context, from here on I’ll limit myself to how to do things in the Arduino IDE. To find out more about formal C++, here is one of many sites devoted to the subject: http://www.cplusplus.com/doc/tutorial/


Consider the things you are trying to control around the layout; would it be easier to manage turnouts, signals or animated/lit things as C++ objects? I think you’ll agree that it would. Lets explore an example.

A Basic Animated Object

My turntable bridge includes an operator’s hut containing a small stove with a fire for warmth.

If you Google Arduino fire simulations, you’ll find all kinds of algorithms and methods for simulating fire with LEDS and an Arduino. This fire is intended as a low, banked fire; the kind that has a variable glow and occasional sparks with no flame; probably coal is the fuel. So I’m keeping it simple: a single red LED on a PWM pin, its brightness rising and falling over time with random bright flashes. To do that, the code has to track the state of the led and whether brightness is rising or falling, periodically increment or decrement the brightness within a defined range, and randomly go high brightness for one cycle to represent a spark.

A First Class

The code that defines an object and its implementation is called a CLASS. Begin with the keyword class, give it a name and setup a pair of curly braces and a trailing semicolon to contain the definition:

class fire
{
};

In the Arduino IDE we can do this exactly the same way we create a TYPEDEF to define a data structure: put it in the head of your INO file (the sketch) before you use it and before the setup() function.

Now then, we need some content for this class: what data does a fire object need to do its job? It needs the know the pin it will use, the low and high values for brightness it will use, and the rate or frequency of updates. In order to run, it has to also keep track of its own state and when it was last updated.

class fire
{
  private:
   int ledPin;
   int high;  // value for maximum brightness
   int low;   // value for minimum brightness
   int rate;  // update frequency in milliseconds

   bool dirUp; // true if brightness is increasing
   int state;
   unsigned long lastUpdate;  // in milliseconds; can be a big number
  
};

That looks like a bunch of variable definitions, right? Exactly; just like local variables within a function. By convention we refer to these variables as the properties of the object. The private keyword that precedes the properties tells the compiler that these properties can only be accessed from within the object. While you can make properties public and thus directly accessible from outside the object, you should not do that without good cause because it could break the integrity of the “black box”. In general, the best practice is to create a public method to retrieve a private property from outside the object.

Methods

In order to do something useful a class has to have methods, the OOP term for class internal functions. Public methods are accessible from outside the object and constitute the interface you use to manipulate the object. Classes can also contain private methods, accessible only from within the objects. All objects require a special public method called the constructor which is called once when an object is instantiated—an instance is created in memory and initialized.

The Constructor

The constructor is a public method, with the same name as the class.

 public:
 
 fire(int pin, int zhigh, int zlow, int zrate){
   ledPin = pin;
   high = zhigh;
   low = zlow;
   rate = zrate;
 
   state = low;
   dirUp = true;
   lastUpdate = 0; 
   analogWrite(ledPin, state);
 }

The constructor takes 4 arguments. Upon entering the constructor, the first step is to transfer argument values to the object properties. Then the method sets the initial LED state to low brightness, sets the direction of brightness change to up (increasing brightness), initializes the lastUpdate property and turns the LED on at the desired PWM level.

The Update Method

In addition to the constructor, the fire object requires a public update method that is called regularly to allow the object to update itself and its LED.

void Update(unsigned long curMillis) {
   if(curMillis - lastUpdate >= rate){
     lastUpdate = curMillis;
     dirUp ? state++ : state--;
     if(random(80) == 1){ // do a spark at random intervals
       analogWrite(ledPin, 255);
     } else { // otherwise write the updated state to the LED
       analogWrite(ledPin, state);
     }
     if(state == high || state == low){ //if at end of range
       dirUp = !dirUp; // boolean logic flip
     }
   }
}

The update method takes a single argument, the current “time” (since the program started) in milliseconds. The intention here is that this method will get called more frequently than absolutely necessary, terminating immediately if it is not yet time to update. Note that here I need to use the random() function to trigger the spark, but up to this point I have not seeded the psuedo random number generator. In the final sketch, I include a call to randomSeed() in the constructor so that each instance will reseed the generator when it starts.

You might wonder why the update method doesn’t retrieve the time value on its own. Efficiency is important when running a stack of simultaneous animations, or your sketch will bog down and perform poorly. The best practice is to retrieve the time value at the beginning of your main loop() then pass that value to each animated object to allow it to determine what it should do at that time value. This also gives the function calling the update methods some flexibility in allocating time among competing priorities (for example, favoring a high priority object over a low priority one). If the update methods are efficient everything runs smoothly.

Putting it All Together

Now that we have defined a CLASS, lets create some objects and see how it works.

To try this out, create a simple double LED circuit with an UNO and a breadboard, like so — I put the resistors on the cathode side as a matter of habit because I’m using common anode wiring everywhere; put them on the anode side of the LEDs if you prefer:

A simple double LED circuit on PWM pins 5 & 6. The 220Ω resistors are on the ground side.

Why two LEDs? We are going to further enhance the class by generating random values for the initial state and direction of the fire object. That, plus instantiating each object with slightly different parameters, guarantees that each fire instance starts and progresses differently. Once started, each instance will do its own thing.

Here’s the sketch (download from the github site):

///////////////////////////////////////
// CLASS fire
// A demonstration C++ class 
// for simulating a fire with LEDS
//
// Author: Robin Simonds, theNscaler.com
// License: CC BY-SA 4.0
// https://creativecommons.org/licenses/by-sa/4.0/
///////////////////////////////////////

class fire {
 private:
   int ledPin;
   int high; // maximum brightness 
   int low; // minimum brightness 
   int rate; // update frequency in milliseconds 
   bool dirUp; // true if brightness is increasing 
   int state;
   unsigned long lastUpdate; // in milliseconds; can be a big number

public: 
 fire(int pin, int zhigh, int zlow, int zrate){
   ledPin = pin;
   high = zhigh;
   low = zlow;
   rate = zrate;

   lastUpdate = 0;
   // seed the psuedo random number generator by
   // reading an unconnected analog pin
   randomSeed(analogRead(0));
   // randomize the starting state of the object
   state = random(low + 1, high);
   dirUp = random(2) == 1;
   analogWrite(ledPin, state);
 }
 // call the Update method frequently to run the animation
 void Update(unsigned long curMillis) {
   if(curMillis - lastUpdate >= rate){
     lastUpdate = curMillis;
     dirUp ? state++ : state--;
     if(random(80) == 1){ // a possible spark
       analogWrite(ledPin, 255);
     } else {
       analogWrite(ledPin, state);
     }
     if(state == high || state == low){
       dirUp = !dirUp; // boolean logic flip
     }
   }
 } 
};

// create global instances of the fire class
fire demo_fire1 = fire(6, 80, 20, 30);
fire demo_fire2 = fire(5, 75, 15, 35);

void setup() {
 // no setup currently needed
}

void loop() {
 unsigned long current_millis = millis();
 
 demo_fire1.Update(current_millis);
 demo_fire2.Update(current_millis);
}

When you run it, it should look something like this:

Want to do a two or three LED fire algorithm (maybe red with amber/yellow for more of flame effect)? Its just a matter of additional properties and additional instructions in the Update method to implement the algorithm. Give it a try if you are so inclined. You’ll see first hand how OOP can make code more reuseable and easier to modify. For animation with Arduino, OOP provides just the right framework to create and manipulate multiple animated objects of all kinds.

In part 2 of this post, we’ll tackle a common and more complex layout object: turnouts.

L&NC Update; Running Lots of Turnouts

Its been a long stretch where there has been too much going on in real life and little time to write about model railroading. But I’ve been working away on the first module of the L&NC and have made lots of progress, so there’s plenty to write about.

This module has nine turnouts, presenting resource management problems that would arise in any substantial yard or staging facility. If you are familiar with the servo library then you know that you are limited to about 10 servos per microcontroller. With that many servos, your microcontroller will have few resources left to do anything else. This post will focus on a solution that problem, expanding the number of servos and other PWM devices a single microcontroller can manage.

Progress Tour

But first, a quick overview of progress to date.

Here’s the module in its current state:

Progress on the 1st Module as of June 2017. Fascia and dressing up of the edges will be the very last step; its pointless to do that while I’m disturbing things with new features and gear. Wires hanging out the pipe at the bottom of the photo are Anderson Powerpole connectors (track and master power) to the upper level.

As you can see I’ve done quite a bit of detailing. I realized early on that I need to complete all the basic scenicking and detailing of the module before moving on to the next. The big reason is having the module alone on a work table gives the best possible access, especially for electronic or animated items that require access to the underside. That’s not to say detailing will not continue after I move on to the next module, but it will mostly be passive rather than active elements.

By the way, if I were to build a room-sized layout, I’d use a modular (some call it “sectional” because you build it in sections) approach to construction even though the layout would not be portable. After laying mainline and other track spanning sections and cutting gaps between sections, I’d pull each section and do most of the remaining work in a work area under optimal conditions. When ready its just a matter of returning the section to its place in the layout and (literally) plugging it in.

Lets take a quick tour of some details so far.

Roundhouse / Turntable

If you’ve read the previous posts about the Roundhouse and the Turntable, you know these have been long term projects.

The turntable rotating beacon comes on whenever the turntable is in motion.

Then stove fire simulation inside the hut is visible through the door. It’s managed by a little PWM code that will the subject of an upcoming post.

The parking lot side of the Roundhouse has been enhanced with Rix power poles and some EZLINE power cables (which comes in two thicknesses and several colors. I use fine, and chose green–old copper–for its visibility). I fabricated a simple power connection and breaker box for the roundhouse out of a piece of styrene and a brass rod, and an EZLINE cable.

A Woodland Scenics light pole casts a pleasant white-yellow light over the parking lot in night mode. Figures, such as the worker (a Woodland Scenics prepainted figure) at the turntable end of the parking lot breath life into a scene.

Having gone to all the trouble to light the Roundhouse, I’ve started populating the space with some appropriate gear and figures.

A view of the lit Roundhouse interior.

Red Bluffs Yard

The Red Bluffs Yard area has its first structure — a fully lit Yard Office — plus a pickup truck with lighting passing by on the adjacent road.

The Red Bluffs Yard Office is fully lit for night operations. The Woodland Scenics light pole works just fine with my Duino Nodes controlled by an Arduino; treat it like any other 20 mA LED. The truck tailights in the background are from LEDS placed in the rear wheel wells, with the light allowed through tiny holes in the fenders.

Its amazing what a couple of SMD LED headlights can do for a really basic pot metal pickup truck kit from Micro Engineering.

The first of three planned scratch built Signal Bridges has been erected to control one of the approaches to the yard interchange.

And, finally, here is the underside, which is rapidly filling with gear supporting the layout above. This module, with its yard, multiple main tracks and turntable is one of the most electronically “dense” parts of the layout plan, to be exceeded only by the city scene planned for the upper level — that is going to be quite a project and I can hardly wait to finish the lower level and get started on the top!.

The underside of this module is rapidly filling with gear. Obviously overhead soldering is not an issue since I can put the module on its side. That said, I rely primarily on screw terminals and crimped fittings for connections.

PWM Drivers for Turnouts and Other Uses

Pulse Width Modulation (PWM) is used to output a timed pulse, where the output is on only part of the time. The width of the pulses — the percentage of time the pulse in the on state — is used to control external devices like servos or to vary the brightness of an LED.

Some, but not all, Arduino digital outputs are hardware PWM capable. Some of the PWM pins are SPI pins and the two serial pins, leaving only 5 or 6 PWM pins available for unrestricted use depending on the board model. If you want to make extensive use of PWM, that just won’t cut it.

PWM can also be synthesized with timed interrupts on any pin, which is how the servo library works and why it does not require you to attach to PWM pins. Unlike hardware PWM pins, PWM synthesized with interrupts represents a hidden load on your board that can affect the performance of your sketch.

External PWM Boards or “Drivers”

External PWM drivers allow  you to greatly expand the number of PWM devices a single Arduino can manage. PWM is used extensively in robotics, so PWM drivers are fairly ubiquitous and inexpensive. Aside from expanding the number of PWM devices you can control, PWM drivers allow you to off-load all of the PWM overhead and timing routines to the external device, freeing your Arduino for other tasks.

I decided to try Adafruit 16-Channel 12-bit PWM/Servo Driver for servo control and a couple of lighting applications on this module. Adafruit also sells a similar device in shield form.

Adafruit 16-Channel 12-bit PWM/Servo Driver, assembled with original terminal block (blue) that did not hold up to use. I eventually soldered leads to the underside of the board.

I chose the independent board rather than the shield because it has a number of advantages, not least of which it is chain-able with up to 61 additional boards, for a total 992 PWM outputs. A single chain of these can handle the servo needs of most club and museum sized layouts! A more modest layout could use these for both turnout servos and all lights and lighting effects, effectively centralizing and simplifying control of all connected devices. It uses the shared I2C interface for fast communication without using any regular pins on your Arduino. For more details, and a tutorial, see the Adafruit product page.

Assembling the board was straight forward, though there are a lot of pins to solder. The terminal block in the center provides independent power for the servo outputs (V+ center pin on outputs) per standard servo wiring; independent power is required by servos because of their substantial current draw. LED’s and other devices that draw their power from the PWM signal itself will not use the independent power.  Be warned: the terminal block Adafruit supplies is poor quality—substitute a better quality part or solder power leads directly to the board. The headers on the sides are for input and output, transferring both data and power to subsequent boards in a chain.

Adafruit 12 bit 16 Channel PWM Driver installed and connected to servos and lighting.

Connecting servos is just a matter of having a male->female servo extension the right size, or combining multiple extensions for longer runs. Any robotics supply store should have an assortment of extensions; as does Amazon. I have three different sizes to work with, which has worked well so far.

On the board positions 0 through 8 (1st 2 banks of four, plus the first pin of bank 3) are attached to the 9 turnout servos. Positions 9 and 10 are for headlights and taillights on the pickup truck. Using PWM I can have the headlights go between low beam and high beam, or have the taillights brighten as if the brakes have been applied. I have some thoughts about an animated animal crossing in front of the truck from time to time….

Using Adafruit’s PWM Driver Software

Adafruit’s software library for this device is available from their GitHub site. Using the software you create an object that you then use to control the board outputs:

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

Creating the Adafruit_PWMServoDriver object without arguments uses the base SPI address to access the board; any different address has to be specified as an argument (and the appropriate jumpers on the board have to be closed). With multiple boards, you create a pwm object for each board using its unique SPI address.

From there, the PWM pulse is set on any output by calling the setPWM() member function:

pwm.setPWM(pin, on, off);

where pin is the board output (a number between 0 and 15), on sets the point in the cycle when the signal goes from low to high (usually 0, the beginning of the cycle, but it can be another value) and 0ff is a number between 0  and 4095 setting the point in the cycle when the signal transitions from high to low.

With the Adafruit driver board you do not use degrees to set a servo’s position. Instead we use timing “tick” values that control the signal transitions from low to high and back. There are 4096 “ticks” (12 bits of resolution) during each cycle. That turns out to be a good thing. For servos, the correct off tick values (assuming the on tick is 0) range from about 150 (the minimum or 0 degree position) to 600 (maximum position, 180 degrees).

Directly setting the cycle through ticks at 12 bits of resolution confers highly granular control and extra smooth servo motion.  Using degrees for position, as the standard servo library does, results in jerkier motion since a degree represents a lower resolution–between 8 and 9 bits–than the 12 bit resolution of the Adafruit board. For LEDS and other lighting, you can vary brightness from off to full on in 4096 steps, allowing fine control of lighting effects.

If you ask me, the smooth motion you can achieve with this board makes its $14.95 price more than worthwhile.

The only difference in your code between working with the standard servo library and the Adafruit driver, is in the object and member function you use to cause the servo to move. Every other aspect of your code and logic should remain the same.

What’s Next?

More coding, and I promise I won’t make you wait long. In the next installment I’m going to introduce you to simplified Object Oriented Programming (OOP) in C++ with the Arduino IDE. I’ll demonstrate a different way to code that, I think, improves several aspects of working with multiple turnouts, and makes the intent and flow of your code easier to understand and maintain. We’ve done it procedurally; we are going to take what we’ve learned and create some OOP code to do the work using either the Adafruit driver or the standard servo driver (Hint: we’ll use a compiler directive to select which driver gets implemented, making the object itself agnostic on the issue and universally usable around the layout).

Until then, happy railroading!

Announcing theNscaler Github Repository

While I try to put complete, working sketches in my posts, there have been many instances where putting entire sketches in a post is impractical, so I frequently field requests for more complete code.  I also have noticed that, for whatever reason, some readers have difficulty copying code from the blog to their own work. So, I’ve opened a GitHub repository to make sharing code a little easier for everyone. Over time I’ll share out examples and useful library code.

The new repository can be found here: https://github.com/rpsimonds/thenscaler/

I’ll be using the “branch” feature to organize the repository.  I’ve seeded it with two branches: Current Sensing and Testloop.

Current Sensing — currently contains a single file with the current sensing functions originally developed for the ACS712.  I’ll be adding a comparable file for CT sensors shortly.

Testloop — contains the entire code set created for the test loop.  Note that this project contains multiple files that should all be copied into a single directory (named testloop to match the main file testloop.ino) in your Arduino documents. This code is provided for those who are ready to wade into deeper levels of complexity to multitask and get multiple threads working together. Some of the code — especially networking and the code for Duino Nodes is “library” reliable and part of my basic code set.  Other code was more experimental. Nothing here is the last word on anything, of course.  But it works and is worth studying on that ground alone.

I hope you find this useful.

Current Sensing and Occupancy Detection for DCC

I’ve had several projects going on more or less simultaneously over the last 6 weeks in connection with the build-out of the 1st module of the L&NC. One project has been to further refine block occupancy detection in DCC to achieve 1 mA detection sensitivity—the sensitivity needed to detect rolling stock equipped with a 10kΩ resistor wheel set. High sensitivity occupancy detection will be especially useful in the Red Bluffs yard part of the layout I’m working on right now

This part of the L&NC is a Yard/Staging facility with a roundhouse & turntable, so it has lots of blocks

Commercial block occupancy detection systems for DCC typically use current transformers to sense current draw from locomotives or resistor wheelsets. The sensors are usually attached to stand-alone logic boards that turn outputs on or off to signal occupancy detection. Typically, these outputs are used to directly power signals. The December 2016 issue of Model Railroader Magazine included an article (Build a signal system with Arduino micro-controllers) where the author connected the outputs of RRCir-Kits BOD boards to an Arduino Mega, and ran his signals from there.

I’ve been avoiding solutions like that because of the high cost and inflexibility of commercial detection systems. All I need is a way to sense very low current flow; the Arduino can do all the rest of the work.Thinking about the issue further, I wondered why not just use current transformers as sensors that can be directly read by an Arduino?

Finding Experimental Sensors

Current Transformer

The type of current transformer useful in model railroading are designed to allow you to wrap a wire (e.g. track feeders) around it through the central hole making the wire the “primary” coil of the transformer. The secondary coil produces an A/C current, that you can measure, proportional to the A/C current running through the primary wire.

I had seen little about using CT sensors with Arduino until I bumped into a seller on Amazon of boards with current transformers marketing them as Gikfun DIY 5A Range AC Current Transformer Module for Arduino. The assembled boards were about $7.50 each, a bit expensive, but then I found a 5 sensor unassembled kit from the same vendor for just under $9. Less than $2 per sensor is my price point for BOD sensors, so I bought the kit to see what i could do with it.

Gikfun Current Transformer Kit

The kit consisted five current transformers, five 82Ω resistors, connector pins and five 19mm x 19mm pc boards on a break-apart strip, drilled and traced for the parts. The resistor is placed across the transformer leads, placing a load (I’ve seen it referred to as a “burden”) on the transformer enabling current flow. No instructions on how to use the sensor were anywhere to be found.

So I went with the methods I already know work with ACS712 sensors. I set up some feeders from the DCC system to my cleaning track, wrapping one of the feeders around the CT coil three times. I hooked leads from the sensor board to pin 0 and ground of my Mayhhew Labs Extended ADC Shield, then threw together a test sketch with the current reading utilities I’d previously used (adapted for the shield).

A Simple CT Sensor Test Rig

I “let her rip” and watched the readings go by on the serial monitor.

Oh My! Putting a DCC equipped locomotive on the track and turning track power on, I was rewarded with a clear reading even though the decoder was idling and all lights were off.  Bring up the lights and the readings jumped up appropriately. Then, taking the loco off the track, I tried shorting the track with a 10kΩ resistor: an immediate and clear response in the readings told me this was going to work.

Test readings in single ended mode using a Mayhew Labs 14 bit ADC Shield. Everything from a 10k resistor shorting the track, to different stages of locomotive operation are clearly shown by the sensor outputs.

I tried it again, this time connecting the CT sensor to A0 and ground on my UNO, and got different but workable results. Amazing!

The readings through the UNO’s built-in ADC are similar, especially at the low end. But notice that the scaling, which is spot-on correct in the first example, fails at higher current levels here. Evidently, the greater bit depth of the Mayhew Labs Shield (14 bits) vs the built-in ADC’s (10 bits) makes a difference where absolute accuracy is concerned. Our purposes are much cruder — we just want to know when a minimum current (1 mA) is flowing. For that purpose direct-to-UNO works fine.

Testing current sensing with and InterMountain EMD F3A

Wait! This Shouldn’t Work!

You see, I was using the ADC in “single-ended”, “uni-polar” mode so that it would read a single input in the range of 0 to +5 volts; this is the same way Arduino analog inputs are read by the built-in ADC. However, the signal from the CT is an A/C signal that the ADC (in single-ended, uni-polar mode) will not be able to read during the negative voltage part of the cycle. Nevertheless, it works with both the built-in and the external ADC, even though it shouldn’t.

I was puzzling as to why, apparently, I was able to cheat when it occurred to me that the difference from ordinary applications was probably the frequency of the signal. Ordinary A/C is a 60 Hz cycle (60 cycles per second).  That is slow compared to the rate at which an Arduino ADC can sample, so a lot of samples would be at 0 during negative phase of the cycle. This would throw calculations off.

DCC is an 8kHz  (8000 cycles per second) cycle which is very fast. My Mayhew Labs ADC can sample at a maximum rate of 3kHz, which is somewhat faster than the Arduino built-in ADC but still slower than the DCC cycle. So even at the fastest rate every ADC read will span multiple DCC cycles, resulting in the ADC returning a positive reading every time.

Or something along those lines is going on.

Mayhew Labs Extended ADC Shield on an UNO, reading the test CT sensor in differential mode.

For hard core accuracy, a CT should be read in differential, bi-polar (+/- voltage) mode, requiring a 14 or 16 bit external ADC with that capability. In differential mode, each CT lead is attached to an ADC port, then the two ports are compared and their difference reported. This effectively captures the entire A/C cycle. Read in differential mode, CT sensors produce stable, high accuracy readings. During testing with my 14 bit ADC I was able to sense down to near 400µA in differential mode, the draw of a 20kΩ resistor at 11 volts. Now that is sensitive!

Same test sequence using the Mayhew ADC to read the sensor in differential mode. Notice the increased accuracy, stability and sensitivity.

Next Steps

The Gikfun 5 sensor kit is a viable sensor package where you will be watching no more than a few (up to the 6 or 8 analog ports on your Arduino or Mayhew Labs ADC shield) blocks. For small installations, buy these sensors and use the ADC in your favorite Arduino board to read them.

For larger installations, the external ADC shield is worth the cost because it is fast and more accurate.

But what if you are watching more blocks than you have analog ports for? That is a bit of a problem, especially if you don’t want to have to use a new microcontroller for every 8 blocks. The module I’m working with has this problem, because it is a yard zone (with turntable and roundhouse) with 24 blocks, and I want just one Arduino board to listen to them all and run signals for this part of the layout.

In the next post, I’ll go into a solution to the problem and install the system.


Code

My experiment was complicated by the fact that I was testing different ADC’s and different connection methods. Below is some basic code for testing a CT sensor such as the Gikfun units discussed above by directly attaching one lead to an Arduino analog port, and the other lead to ground. This is the simplest way to use CT sensors.

The code is built around current sensing techniques originally developed for ACS712 sensors. For more in-depth discussion of that development process, see these posts – 1 , 2, 3 and 4. ADC’s (analog-to-digital converters) read the voltage produced by a sensor which, in the case both current transformers and ACS712 sensors, is proportional to the current it is measuring.

The challenge with sensing alternating current comes from the fact that at any given moment when a sensor is read, the current could be anywhere in its cycle with a voltage somewhere between -Vmax and +Vmax (the nominal A/C voltage). Since we are sampling a bipolar wave form, it is necessary to take multiple samples spanning multiple cycles, then calculate the current from the samples using the Root Mean Square algorithm. This is the generally accepted method for determining A/C voltage and current from digital samples.

In addition to solving the basic problems of sampling an A/C signal, the RMS algorithm cuts through signal and sampling noise, a significant problem with ACS712 sensors when measuring low current. Since CT sensors are far less noisy, we can get accurate readings with fewer samples; but we still need the RMS method to calculate current flow.

To make the process I’ve described accurate across an array of sensors, I developed a calibration routine. All ADC’s will produce some minimal reading for a sensor even when the sensor is not producing a signal. This is a form of noise that has to be filtered out. Also, some sensors (such as the ACS712) produce a signal in the absence of any current to measure — this too is noise that has to be filtered.

Each ADC port / sensor combination will produce a unique amount of noise. The purpose of the calibration routine is to measure and record the noise level — I call it ADC Zero — for each port/sensor. This is recorded both as raw mV, and as a calculated current in mA. Later, the measured noise is subtracted from each raw reading (in mV) to determine what current (if any) has been detected. Then, when current is detected, I use a multiple of the ADC Zero calculated current to establish an occupancy threshold. This method allows me to overcome sensor differences and manage the sensor system in a uniform way.

// CT Sensor test
// Using UNO built-in ADC to read the sensor
//////////////////////////////////////////////////////////////////

#define VERSION "1.006"
#define SYS_ID "CT Sensor Test - Direct to UNO ADC"
const int adcpin = 0;

// Sampling Parameters
const unsigned long sampleTime = 2000UL; 
const unsigned long numSamples = 100UL; 
const unsigned long sampleInterval = sampleTime/numSamples; 

#define SENSITIVITY 5000
#define DETECTION_MULTIPLIER 1.3
#define CALIBRATION_READS 300

// variables to hold sensor quiescent readings
float aqv;  // Average Quiescent Voltage; e.g. ADC Zero
float aqc;  // Average Quiescent Current; 

void setup()
{
  Serial.begin(9600);
  Serial.println(String(F(SYS_ID)) + String(F(" - SW:")) + String(F(VERSION)));
  Serial.print("\nCalibrating the sensor at pin ");
  Serial.println(adcpin);
  aqv = determineVQ(adcpin); 
  Serial.print("AQV: ");
  Serial.print(aqv * 1000, 4);
  Serial.print(" mV\t");
  aqc = determineCQ(adcpin, aqv);
  Serial.print("AQC: ");
  Serial.print(aqc * 1000, 4);
  Serial.print(" mA\t");
  float sense = (aqc * DETECTION_MULTIPLIER) - aqc;
  Serial.print("Detection Sensitivity: ");
  Serial.print(sense * 1000, 3);
  Serial.println(" mA\n\n");
  delay(7500);
}

void loop(){
  float current = readCurrent(adcpin, aqv);
  float delta = abs(aqc - current);
  bool occupied = delta > ((aqc * DETECTION_MULTIPLIER) - aqc);
  
  Serial.print("Current Sensed: ");
  Serial.print(current * 1000,3);
  Serial.print(" mA\t");
  
  if(occupied){
    Serial.println("Occupied");
  } else {
    Serial.println("Not occupied");
  }
  delay(3000);
}

//////////////////////////////////////////
// Current Sensor Functions
//////////////////////////////////////////
float readCurrent(int pin, float adc_zero)
{
  float currentAcc = 0;
  unsigned int count = 0;
  unsigned long prevMicros = micros() - sampleInterval ;
  while (count < numSamples)
  {
    if (micros() - prevMicros >= sampleInterval)
    {
      float adc_raw = (float) analogRead(pin) - adc_zero; // sensor reading in volts
      adc_raw /= SENSITIVITY; // convert to amperes
      currentAcc += (adc_raw * adc_raw); // sum the squares
      count++;
      prevMicros += sampleInterval;
    }
  }
  //https://en.wikipedia.org/wiki/Root_mean_square
  float rms = sqrt((float)currentAcc / (float)numSamples);
  return rms;
}

//////////////////////////////////////////
// Calibration
// Track Power must be OFF during calibration
//////////////////////////////////////////

float determineVQ(int pin) {
  float VQ = 0;
  //read a large number of samples to stabilize value
  for (int i = 0; i < CALIBRATION_READS; i++) {
    VQ += analogRead(pin);
    delayMicroseconds(sampleInterval);
  }
  VQ /= CALIBRATION_READS;
  return VQ;
}

float determineCQ(int pin, float aqv) {
  float CQ = 0;
  // set reps so the total actual analog reads == CALIBRATION_READS
  int reps = (CALIBRATION_READS / numSamples);
  for (int i = 0; i < reps; i++) {
    CQ += readCurrent(pin, aqv);
  }
  CQ /= reps;
  return CQ;
}

Adjusting the Sketch

The SENSITIVITY and DETECTION_MULTIPLIER constants are the main values you can manipulate to adjust the sketch.  The SENSITIVITY constant defines the conversion from raw mV to sensed current in mA; raise that number to reduce the current reading and decrease dynamic range of the sensor readings (lower it to do the reverse).  The DETECTION_MULTIPLIER determines when a current reading is high enough to mean that a block is occupied. Try different values for these items and see what happens. The interaction between these two constants is the primary determinant of how the sketch performs.

If you have having stability trouble with your readings, try increasing the number of samples taken on each pass (numSamples) and/or changing the sampleTime variable that controls the interval between samples.

Bear in mind that for low current sensing, the 10-bit ADC in an UNO is just barely sensitive enough to work with a CT. For low current sensing you’ll need to adjust the variables to get a minimum of false readings.  The values given in the sketch above are what worked for me with the Gikfun CT coil and a particular UNO to achieve stable detection.  Expect different readings and different variable values for different brands of CT coils, and different Arduino boards. Working with large numbers of CT sensors, I can say that a given Arduino board and a given brand/model CT, using the same value for the load resistor, will generally produce the same readings.

 

Basic Signaling for a Small Layout

Continuing with the theme of controlling a small layout with an UNO, I thought I’d accept my own challenge from the last post and talk about how one might implement signals on a small layout as I did on the Test Loop.

Signals on the Test Loop

While I was actively testing block occupancy detection on the test loop, I set up three sets of signals as part of that effort. I wanted to both test some off-the-shelf signals from Tomar and take a crack at building my own searchlight signals using BLMA unlit signal heads. The former turned out to work well, but because they are wired for common anode, they sent me on a quest to tame common anode wiring. The latter also came out well, once I learned how to reliably solder magnet wire to SMD micro LEDS!

Block and Signal layout on the Test Loop

Block and Signal layout on the Test Loop

I did this primarily to see the block detection system working (block detection is also shown on on my programmable control panel, but that is another story) as a train moves around the track.  You can see it in action in this video—note that only the locomotive is detectable by the block occupancy detection system; the rolling stock is not set up for detection.

Since the test loop is just an oval with a single turnout and siding, the system is fairly simple.  As you watch the train go around the oval you will see signals change state as the train moves in and out of blocks, and as the turnout changes state.  The logic is imperfect in a few cases but good enough to show the various parts of the system working as a whole under the control of an UNO, which was the point of the exercise.

A Framework for ABS

Automatic Block Signalling (ABS) is straight forward and prototypical for late nineteenth / early twentieth century railroads. ABS signals are autonomous and react to block occupancy and turnout status; the red (next block obstructed), yellow (a subsequent block is obstructed) and green (no obstruction) indicators are near universal. Typical implementations handle blocks in groups of three or four, depending on how far ahead the system sensors extend.

Because each signal is an autonomous object that responds to specific environmental conditions, ABS lends itself well to a data-oriented approach. As with turnouts in the previous post, the best starting point is to devise a data structure that will encapsulate and represent everything that needs to be known about each signal in your system. Here is what I came up with for the test loop (see this post for an explanation of my Duino nodes, and this post for the addressing system in use).

typedef struct SIGNAL_DEF {
  byte type; // bit mask indicating available signal aspects
             // bit 1 = Red; bit 2 = Green; bit 3 = Yellow)
             // 1=R; 2=G; 3=RG; 4=Y; 5=RY; 6=GY; 7=RGY
             // 3 and 7 are the two types on the test loop
  nodeAddress addr; // base address of the Duino node for this signal
  byte R_ID; // pin/bit id for red indication
  byte G_ID; // pin/bit id for green indication
  byte Y_ID; // pin/bit id for yellow indication
  
  // data elements for running the signal
  byte state; // current signal state
  T_ALIGN *turnouts; // these turnouts must be aligned as defined to get SIGNAL_GREEN
  byte numTurnouts;
  byte *following; // additional blocks ahead (depends on direction signal faces)
  // watched for occupancy resulting in SIGNAL_YELLOW caution   
  byte numFollowing; 
};

By now you should recognize that this is my preferred approach to dealing with complex, interactive objects in the system.  As always, I define a compound data structure (the structure contains other structures as elements, in this case the T_ALIGN and NODEADDRESS types)  to collect all relevant data for each signal. Feel free to reinvent any of this — the point is to collect all necessary data in one place for each signal.

“T_ALIGN *turnouts”  is an example of pointer notation which allows for an array of zero (empty array) or more of the respective types; the “numTurnouts” element indicates how many items the T_ALIGN array contains. The “byte *following” and “numFollowing” do the same thing for a list of subsequent block IDs that are watched for occupancy.

Here is the declaration of a signals array from the Test Loop encapsulating all the signals in use, using the data types discussed. Notice how structures and arrays within the SIGNAL_DEF structure are defined inside their own curly braces:

// Signals definitions and data
SIGNAL_DEF signals[NUM_SIGNALS] = {
  {3, {3, 0}, 0, 1, -1, SIGNAL_OFF,{0, ALIGN_DIVERGENT}, 1,{2}, 1 },
  {3, {3, 0}, 2, 3, -1, SIGNAL_OFF,{0, ALIGN_MAIN}, 1, {2}, 1 },
  {7, {1, 0}, 0, 1,  2, SIGNAL_OFF,{}, 0, {0}, 1},
  {7, {0, 0}, 0, 1,  2, SIGNAL_OFF,{0, ALIGN_DIVERGENT}, 1, {}, 0 },
  {3, {0, 0}, 3, 4, -1, SIGNAL_OFF,{0, ALIGN_MAIN}, 1,{2}, 1}
};

Signal logic is handled by one function that gets called at the end of each loop cycle on the UNO, after block occupancy has been established.

void refreshSignals() {
  // First pass, set Stop (RED) state; default is GREEN
  // from block occupancy or turnout states
  for(int i = 0; i < NUM_SIGNALS; i++){ // for each signal
    // default state
    int state = SIGNAL_GREEN;
    SIGNAL_DEF sig = signals[i];
    for(int j = 0; j < max(sig.numTurnouts, sig.numBlocks); j++){ 
      if(j < sig.numTurnouts){
        // if the turnout is in motion OR 
        // if turnout alignment does not equal the required alignment
        if(turnout[sig.turnouts[j].id].is_moving || 
            turnout[sig.turnouts[j].id].alignment != sig.turnouts[j].align){
         state = SIGNAL_RED;
        } 
      }
      if(j < sig.numBlocks){ // for each linked block in the SIGNAL_DEF
        if(blocks[sig.blocks[j]].occ){ // if occupied
          state = SIGNAL_RED;
        }
      }
    }
    setSignalBits(i, state);
  }
  
  // Second pass to set caution states on
  // signals that support it and are currently set to GREEN
  
  for(int i = 0; i < NUM_SIGNALS; i++){ // for each signal
    SIGNAL_DEF sig = signals[i];
    if(bitRead(sig.type, 2)){ // if the signal supports the caution state
      if(sig.numFollowing > 0 && sig.state == SIGNAL_GREEN){
        // check occupancy of following block(s) if any
        for(int j = 0; j < sig.numFollowing; j++){
          if(blocks[sig.following[j]].occ){
            setSignalBits(i, SIGNAL_YELLOW);
          }
        }
      }
    }
  }
  // Refresh the nodes to show signals in their updated state
  nodeRefresh();
}

void setSignalBits(int signalID, byte signalState) {
  SIGNAL_DEF sig = signals[signalID];
  if (sig.state != signalState) {
    signals[signalID].state = signalState;
    byte nodeBits = nodeGet(sig.addr);
    switch (signalState) {
      case SIGNAL_OFF:
        if(bitRead(sig.type, 0)) bitWrite(nodeBits, sig.R_ID, LOW);
        if(bitRead(sig.type, 1)) bitWrite(nodeBits, sig.G_ID, LOW);
        if(bitRead(sig.type, 2)) bitWrite(nodeBits, sig.Y_ID, LOW);
        break;
      case SIGNAL_RED:
        if(bitRead(sig.type, 0)) bitWrite(nodeBits, sig.R_ID, HIGH);
        if(bitRead(sig.type, 1)) bitWrite(nodeBits, sig.G_ID, LOW);
        if(bitRead(sig.type, 2)) bitWrite(nodeBits, sig.Y_ID, LOW);
        break;
      case SIGNAL_GREEN:
        if(bitRead(sig.type, 0)) bitWrite(nodeBits, sig.R_ID, LOW);
        if(bitRead(sig.type, 1)) bitWrite(nodeBits, sig.G_ID, HIGH);
        if(bitRead(sig.type, 2)) bitWrite(nodeBits, sig.Y_ID, LOW);
        break;
      case SIGNAL_YELLOW:
        if(bitRead(sig.type, 0)) bitWrite(nodeBits, sig.R_ID, LOW);
        if(bitRead(sig.type, 1)) bitWrite(nodeBits, sig.G_ID, LOW);
        if(bitRead(sig.type, 2)) bitWrite(nodeBits, sig.Y_ID, HIGH);
        break;
    }
    nodeSet(sig.addr, nodeBits);
  }
  return;
}

void setSignal(int signalID, byte signalState) {
  setSignalBits(signalID, signalState);
  nodeRefresh();
}

For more in-depth discussion of my Duino Node devices, node functions and how they are used,  see Adding Signals to the Test Loop and Adding Signals to the Test Loop Part 2.

The idea here is that the logic of the signal system is executed in the refreshSignals() function. That function, in turn, calls setSignalBits() to interface with the hardware, using the hardware specific Duino Node functions to  drive the hardware.

How to Integrate Signals into Your Small Layout

Adding signals to your small layout consists of two basic steps: 1) setup your signal hardware so that it can be turned on and off in some way; either by direct connection to your UNO or using a shift register chain along similar lines to what I do with Duino Nodes. The choice of common anode vs. common cathode wiring is yours to make, but will depend on how your signal gear is wired. 2) Integrate signal handling in the sketch using turnout state and (if you have it) block occupancy data. Call your signal logic function at the end of each iteration of the main loop, and let that function interact with the signal hardware. Your signal logic should be in one place, and should be written as an “abstraction” that doesn’t know how to change signal display, but relies on other hardware specific functions to do that job.

Tomar Signals

Tomar N Scale Signals

I consider signals to be the most basic form of animation you can add to your layout to bring it to life. Its a little bit of trouble, but I hope you can see its really not hard.  The advantage of the Arduino approach over a hardware/hardwired approach (eg, connecting signals to the outputs of a stand alone block occupancy device) is the flexibility you gain in implementing signals while keeping wiring to an absolute minimum. Adding Absolute Permissive Block signalling is just as matter of additional logic to the sketch for the stretch of track you are trying to protect. Even full CTC functionality can be readily supported by responding to messages from a CTC control panel or system.

You’ll be amazed that how much work a single UNO can actually do for you.

Running a Small Layout with an Uno

Arduino Uno R3

Arduino Uno R3

A reader on another thread had questions about running multiple turnouts on a small layout with an Uno. Primarily he wants to control turnouts with a control panel and feedback indicators.  I thought that his needs are pretty typical for small layouts, and would be a good example for others planning and building on a similar scale.

So this post is bottom-up exercise in planning and implementing single UNO control on a small layout. I hope it will be helpful in planning and building your layout.

The Requirements

The layout has 5 turnouts that will be controlled with push buttons. Control is toggle-style: each button push causes the turnout to toggle between positions. Turnouts are run by micro servos. The control panel will have a layout map with turnout push buttons and LED position indicators.

First Decisions

The UNO is the first board many of us encounter and is certainly up to the task of managing 5 turnouts and a control panel, with capacity to spare. There are other Arduino boards suitable for model railroading use; but the UNO is cost-effective and easy to work with.

That said, the issue you have to contend with eventually is the number of connections required to support all the devices and LEDS that will be attached to the microcontroller.

Arduino boards provide three different types of connections/pins:

  • Digital: digital pins are basic on/off binary connections. In output mode they are either fully on (emitting 5 volts, with a max current of 40 mA) or off. In input mode they read the incoming current as either HIGH (on) or LOW (off). When used as inputs, digital pins may need pull-up or pull-down resistors so that they will function correctly.
  • Digital PWM: some, but not all, digital pins are also capable of PWM – pulse width modulation.  The output of a PWM pin, if not fully on or off, is a series of pulses that cause the output to to on a percentage of the time.  The pulses are used to control servos and other PWM devices. PWM with LEDS lets you vary their brightness between 0 and 100%, supporting a variety of interesting lighting effects.
  • Analog: Analog pins are primarily used to read sensors that produce a variable output. Pin inputs are fed to the on-board ADC (analog-to-digital converter) for conversion to a number between 0 and 1024. Current and temperature sensors are example of analog devices read through analog pins / ADC. What you may not know about analog pins is that they can also be used as basic digital pins.

It is essential in early planning to determine all the devices to be connected to the microcontroller and the type of connection each requires to determine what connection issues you will need to resolve. In some cases, going to the UNO’s bigger cousin, the MEGA, will solve connection limits.  But even the MEGA has limits. In some cases it makes sense to use pin-multiplying techniques using external chips to drive banks of LEDS, servos or other devices, even when you otherwise have enough pins.

A Connection Plan

The UNO has 14 digital connections, numbered 0 to 13, of which 6 (pins 3, 5, 6, 9, 10, and 11 ) are PWM. It also has 6 analog connections that can be used as digital connections with digitalRead() and digitalWrite() when referenced in the sketch as A0 through A5. That is a grand total of 20 digital pins, of which 6 are PWM.

Of those, two pins are generally off limits on an UNO: digital pins 0 and 1. They are Serial RX and TX respectively, and should be avoided for other uses on any UNO where you expect to use the USB interface — that would be most of the time. On boards without the built-in USB, pins 0 and 1 are fair game.

For five servos we need five digital pins. Even though PWM is used to control servos, the Arduino servo library creates the necessary pulses on any digital pin. However, using the library disables PWM functionality on pins 9 & 10, so some of the UNO’s native PWM capacity is sacrificed in any case.

For the five turnout control buttons we need five basic digital pins. For the control panel indicators, assuming one LED for each leg of each turnout, we need 10 basic digital pins.

That means we need 20 connections; but with pins 0 & 1 reserved for the USB interface, we’re short 2 connections. Many would want to use bi-color red/green LEDS so the state of each leg is continuously shown. That would require 10 additional basic digital connections.

Need Connections? No Problem!

That’s OK. Its exactly what I expected to happen when I started in on this exercise. Both inputs and outputs can be multiplied with external chips and boards; multiplying the basic digital outputs is far and away the easiest to implement, because the circuits are simple and the compiler includes native software support without add-on libraries.

I would use shift registers to control all the control panel indicators. That reduces the connection load on the UNO to just 3 connections for all LED indicators, no matter how many you end up with. With that, and the 10 pins needed to read the buttons and control the servos, the total digital pins requirement is 13. That leaves 5 available pins, one analog and up to three digital PWM, for other uses.

There is a great tutorial on controlling LEDs with shift registers, and chaining multiple registers together, on the Arduino website. That tutorial is mandatory if you are unfamiliar with shift registers; the circuit(s) shown are what you will be using for control panel LED indicators. The balance of this post assumes the basic knowledge contained in that tutorial.

Build A Control Board

Use a shift register chain to run your panel indicators. I use a TI 74HC595 chips that I buy from DigiKey. At less than $.50 each in lots of 10, they are a bargain. $5.00 worth of chips supports 80 outputs. Its the cheapest way I know of to extend the capabilities of an Arduino and run a lot of low current devices.

Decide how many shift registers you need — the 74HC595 has 8 outputs — and create a board based on the tutorial circuits to hold the chips and provide connection points for your LEDS. A single LED for each leg of each turnout (to show which leg is open to traffic) will need 10 connections—use two shift registers (use the two-chip circuit in the tutorial). Add a third chip (after the tutorial you will know how to do that) and your connection count rises to 24, enough to support red/green bi-color LEDS on each turnout leg on the panel. With 4 connections left, who knows what else you might light up?

Each push button will need a 10kΩ pull-down resistor connecting its pin to ground.  The purpose of the pull-down resistor is to drain stray current and keep the pin LOW unless the button is pushed and full current flow is applied.  See the diagram below. The resistor should be placed as close to the pin as possible.

Servo Control by Button

Basic Servo Control by Button

Power Issues

While you can run one or two servos off the power supplied by the UNO’s +5 volt pin, more servos than that would exceed the board’s power handling capabilities. Accordingly, you will need to supply power to the servos from a separate power source. VERY IMPORTANT: the ground for the servo power source must be tied to UNO ground or the servos will not work correctly.

One Sketch To Run it All

Alright. The control panel is built and wired; the servos are installed (see this post for my latest methods) and connected to power and the UNO. What you need now is a sketch to run the control panel and the servos.

Here is a demonstration sketch to get you started:

//////////////////////////////////////////
//
// Small Layout turnout control
// Demonstration Sketch
//
//////////////////////////////////////////

#include <Servo.h> // include the Servo library

////////////////////////////////////////
//
// Definitions
//
////////////////////////////////////////

////////////////////////////////////////
// Basic parameters; adjust for your actual setup
#define NUMBER_OF_TURNOUTS 5
#define NUMBER_OF_SHIFT_REGISTERS 3
#define STEP_DELAY 70  // servo movement step delay, in milliseconds
///////////////////////////////////////

///////////////////////////////////////
// Data Structures
///////////////////////////////////////

//////////////////////////////////////
// TURNOUT_DEF holds all configuration
// information about turnouts and panel LEDS
//////////////////////////////////////
typedef struct TURNOUT_DEF {
  uint8_t button_pin; // Digital or analog pin for the button associated with this turnout
  uint8_t servo_pin; // Digital pin for the servo associated with this turnout
  int pos_main; // servo position for the MAIN leg, in degrees
  int pos_div; // servo position for the DIVERGENT leg, in degrees
  int panel_LED_main_green; // The position(s)of panel LEDS in the shift register chain
  int panel_LED_main_red; // Example assumes a bi-color (red/green) LED for each turnout leg
  int panel_LED_div_green; // modify these elements to reflect the actual LEDS you are using
  int panel_LED_div_red;
};

/////////////////////////////////////
// TURNOUT_DATA is wrapper structure holding
// both configuration and runtime data for turnout operation
/////////////////////////////////////
typedef struct TURNOUT_DATA {
  TURNOUT_DEF data; // configuration
  bool is_moving;
  byte alignment;
  int pos_now;
  int target_pos;
  unsigned long last_move;
};

// Alignment state values
#define ALIGN_NONE 0
#define ALIGN_MAIN  1
#define ALIGN_DIVERGENT 2


// pin ids for shift register chain controlling panel LEDS
#define LATCH_PIN 7
#define CLOCK_PIN 8
#define DATA_PIN 9

//////////////////////////////////////////
//
// Global variables
//
//////////////////////////////////////////

//////////////////////////////////////////
// TURNOUT_DATA Array
// * A0, A1, etc refer to analog pins which are used for buttons in this example
// * Replace pos_main (93) and pos_div (117) with real values for each turnout
// * LEDS are identified by their output position in the shift register chain;
// the identifier is a number between 0 and (NUMBER_OF_SHIFT_REGISTERS * 8) - 1. 
// Example assumes LEDS are connected to shift register outputs sequentially 
// from the first output of first register. You can connect LEDS to any output in
// any order; just set the identifiers accordingly.
//
// Only the TURNOUT_DEF part of the TURNOUT_DATA structure has to be initialized here; 
// The remaining elements are managed internally and are initialized automatically
//////////////////////////////////////////

TURNOUT_DATA turnouts[NUMBER_OF_TURNOUTS] = {
  {{A0, 2, 93, 117, 0, 1, 2, 3}},
  {{A1, 3, 93, 117, 4, 5, 6, 7}},
  {{A2, 4, 93, 117, 8, 9, 10, 11}},
  {{A3, 5, 93, 117, 12, 13, 14, 15}},
  {{A4, 6, 93, 117, 16, 17, 18, 19}}
};

// servo objects
Servo servos[NUMBER_OF_TURNOUTS];

// array to hold shift register state bytes
byte panel_LEDS[NUMBER_OF_SHIFT_REGISTERS];

void setup() 
{
  // Setup pins for shift register chain
  pinMode(LATCH_PIN, OUTPUT);
  pinMode(CLOCK_PIN, OUTPUT);
  pinMode(DATA_PIN, OUTPUT);
    
  // initialize each turnout 
  for(int i = 0; i < NUMBER_OF_TURNOUTS; i++){
    // attach the servo
    servos[i].attach(turnouts[i].data.servo_pin);
    // set the pin mode for the button pin
    pinMode(turnouts[i].data.button_pin, INPUT);
    // test and position the turnout by moving
    // to divergent then to main positions
    servos[i].write(turnouts[i].data.pos_div);
    turnouts[i].pos_now = turnouts[i].data.pos_div;
    setTurnout(i, ALIGN_MAIN);
    }
} // end of setup

void loop() 
{ 
  // get elapsed milliseconds at loop start
  unsigned long currentMillis = millis();

  // loop through the turnouts array
  for(int i = 0; i < NUMBER_OF_TURNOUTS; i++){
    if (turnouts[i].is_moving) {
      // if sufficient time has elapsed since the last move
      if ( (currentMillis - turnouts[i].last_move) >= STEP_DELAY ) {
        // move the turnout one degree
        turnouts[i].last_move = currentMillis;
        if (turnouts[i].pos_now < turnouts[i].target_pos) { // if the new angle is higher
          servos[i].write(++turnouts[i].pos_now);
        } else {  // otherwise the new angle is equal or lower
          if (turnouts[i].pos_now != turnouts[i].target_pos) { // not already at destination
            servos[i].write(--turnouts[i].pos_now);
          }
        }
      }
      // if target position reached, stop turnout motion
      if (turnouts[i].pos_now == turnouts[i].target_pos) {
        turnouts[i].is_moving = false;
        turnouts[i].last_move = 0;
        setIndicators(i);
      }
    } else {
      // if a turnout is NOT in motion, check to see if its button is pressed
      int button_state = digitalRead(turnouts[i].data.button_pin);
      if(button_state == HIGH){
        // toggle position
        if(turnouts[i].alignment == ALIGN_MAIN){
          setTurnout(i, ALIGN_DIVERGENT);
        } else {
          setTurnout(i, ALIGN_MAIN);
        }
      }
    }
  }
}// end of main loop

////////////////////////////////////////////////////////////////
// Supporting Functions
////////////////////////////////////////////////////////////////

void setTurnout(int id, int align){
    // Set indicators to show turnout in motion
    turnouts[id].alignment = ALIGN_NONE;
    setIndicators(id);
    // Set values to trigger motion on next loop iteration
    switch(align){
        case ALIGN_MAIN:
          turnouts[id].is_moving = true;
          turnouts[id].last_move = 0;
          turnouts[id].target_pos = turnouts[id].data.pos_main;
          turnouts[id].alignment = ALIGN_MAIN;
          break;
        case ALIGN_DIVERGENT:
          turnouts[id].is_moving = true;
          turnouts[id].last_move = 0;
          turnouts[id].target_pos = turnouts[id].data.pos_div;
          turnouts[id].alignment = ALIGN_DIVERGENT;
          break;
      }
}

void setIndicators(int id){
  switch(turnouts[id].alignment){
    case ALIGN_NONE: // means the turnout is in motion and not aligned
      panelWrite(turnouts[id].data.panel_LED_main_red, HIGH);
      panelWrite(turnouts[id].data.panel_LED_main_green, LOW);
      panelWrite(turnouts[id].data.panel_LED_div_red, HIGH);
      panelWrite(turnouts[id].data.panel_LED_div_green, LOW);
      break;
    case ALIGN_MAIN:
      panelWrite(turnouts[id].data.panel_LED_div_green, LOW);
      panelWrite(turnouts[id].data.panel_LED_div_red, HIGH);
      panelWrite(turnouts[id].data.panel_LED_main_green, HIGH);
      panelWrite(turnouts[id].data.panel_LED_main_red, LOW);
      break;
    case ALIGN_DIVERGENT:
      panelWrite(turnouts[id].data.panel_LED_div_green, HIGH);
      panelWrite(turnouts[id].data.panel_LED_div_red, LOW);
      panelWrite(turnouts[id].data.panel_LED_main_green, LOW);
      panelWrite(turnouts[id].data.panel_LED_main_red, HIGH);
      break;
  }
}
/////////////////////////////////////////////////
// Shift Register Functions
/////////////////////////////////////////////////
void panelWrite(int id, byte state) {
  int reg = floor(id / 8);
  int pos = id % 8;
  bitWrite(panel_LEDS[reg], pos, state);
  panelRefresh();
}

void panelRefresh(){
  // Prepare to shift by turning off the output
  digitalWrite(LATCH_PIN, LOW);
  // shift all bits out in MSB (most significant bit first) order
  for(int i = (NUMBER_OF_SHIFT_REGISTERS - 1); i>=0; i--) {
    // shift out the bits
    shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, panel_LEDS[i]);
  }
  // turn on the output to activate
  digitalWrite(LATCH_PIN, HIGH);
}

The sketch compiles into a compact package leaving plenty of memory and resources for additional functionality.

The main loop is built around a simple multitasking model, allowing you to control task timing and balance multiple competing tasks.  In this case, the main benefit of this methodology is control of the movement rate of turnout servos. On my test loop this methodology allows block occupancy detection to work in the background along with signal logic.

UP995 at Signal 34. Dual Searchlight Signals, Scratch Made with BLMA Signal Heads.

UP995 at Signals 3 & 4 on the Test Loop. Dual Searchlight Signals, Scratch Made with BLMA Signal Heads.

Troubleshooting

If you build the circuits accurately, everything should work out of the box (as it were). I am an obsessive tester — so I test things at critical stages as I build them.  Build and test your shift register circuits on a breadboard first, before soldering everything to a prototyping board.

To test the sketch I set up a simulation on a breadboard.

To test the sketch for this post I set up a 3 servo simulation on a breadboard.

Getting the connections to the UNO correct is key. If the LEDs don’t light at all, check all three connections, plus power connections; LEDS are polarized, so make sure the anodes are connected to incoming power and the cathodes to ground. Tracing power flow with a multi-tester should help you find problem areas quickly.

If your LEDs light but alternately flash odd/even, you probably reversed the LATCH_PIN and CLOCK_PIN connections.

For 3 or more servos, you must have an independent power supply to power them. BE SURE TO CONNECT GROUND FROM THE SERVO POWER SUPPLY TO ARDUINO GROUND.

Do Something with Unused Outputs

Old style hard wiring, without the help of a microcontroller, is a messy business at best, which tends to limit what you can do. DCC can help bridge the gap, but layout control is something of an afterthought for DCC (it was designed for locomotive control only) and is, frankly, awkward to use (my opinion; your mileage may vary).

To get the kind of functionality we have here without Arduinos — turnout control with a synchronous control panel — it would have to be hard wired. Or you could connect a full computer and run JMRI; that would require DCC (plus stationary decoders for the turnouts and a USB->DCC interface) to work . You would have to use stall-motor type turnout motors or add microcontrollers (Peco now sells a “smart” turnout system using servos and custom controllers) to interface with servo motors. So if you want to use servos instead of stall-motors, you can go Peco or do it the Arduino way from the start. As much as I appreciate Peco’s efforts here, the openness of the Arduino platform is a big part of what makes it useful and cost-effective.

Adding another function to a small layout — lets say a signal to protect a turnout — is a matter of connecting the signal leads to unused outlets and writing some code to run it.  If you’ve gotten this far, you’ll spend far more time thinking about exactly how the signal should run in conjunction with the turnout it is protecting, than you will connecting the device and adding code to the sketch.  Once you get into this way to doing things, I guarantee you’ll never look back.

So what will it be? Signals?  A fire simulation (using PWM and LEDS)? A lighted structure? Let your imagination run wild!


UPDATE

I starting working with shift registers a while back.  They are so effective my habit is to always use shift registers with LEDs, such as for signals, structure lighting and control panel applications.  The habit is so ingrained that I forget to mention the other important reason for using them: power management.

The power handling capability of an UNO is limited to 40 mA per pin and 400 mA for the entire board. LEDs typically draw between 20 and 30 mA.  So if you are directly powering 20 LEDs from an UNO, you are exceeding its power handling capacity and will burn out the board.

In the example given in the post, no more than half the LEDs well be lit at any one time so we are unlikely to exceed the 400 mA limit overall. A few more lit LEDS, though, would put it over the top.  Shift registers duck the problem altogether because each register is powered—and supplies power to connected devices—independently.  Other than the total load on your power supply, adding devices/lights to shift registers or even adding additional registers, puts no significant power load on the UNO.

Above I mentioned that an independent power supply is needed to run the servos.  From a “best practices” perspective, one should always power external devices with an independent power supply. Arduino’s current handling limits are tight; always using external power supplies avoids that problem.


UPDATE

Several readers asked if I have a fritzing image of the 3 servo demo.  I did not, but I put one together for those who might find this helpful.

3 Servo Demo

This circuit is intended to work with the above sketch, with the turnouts variable shortened to 3 elements instead of 5:

TURNOUT_DATA turnouts[NUMBER_OF_TURNOUTS] = {
  {{A0, 2, 93, 117, 0, 1, 2, 3}},
  {{A1, 3, 93, 117, 4, 5, 6, 7}},
  {{A2, 4, 93, 117, 8, 9, 10, 11}}
};

Always cross check all connections on a circuit like this against appropriate data sheets before powering it up for the first time! Be sure to connect the ground of your Arduino to the ground of your external power supply.

Adding Signals to the Test Loop, Part 2

Using shift register/darlington driver nodes for attaching lighting (and other devices) to the layout is easily justified by the conservation of precious Arduino pins. But that’s not necessarily the most important benefit of this approach.

Duino node for station lighting.

Duino nodes for station signal and lighting on the Test Loop.

The principal advantage of organizing lighting control into chains of shift registers—with or without Darlington Drivers to sink instead of source current (at this level it doesn’t matter what the shift register is attached to)—is to create an addressable architecture for lighting and animation.

What do I mean?

An addressable architecture is a system where individual objects can be identified by an “address” that provides sufficient information, in proper context, to allow the object to be accessed (my definition). An “address” should be a direct access mechanism and should not require conversion to a different form in order to use it. However, an address does not necessarily have to be complete if the context within which it is used has “knowledge” of the missing address information.

By that definition a pin number is a valid address. But, since we want to control more objects that we have pins, we need to think on a larger scale.

Arrays

Going beyond pin numbers, an address could be an offset (index) into an array. This works for objects attached to a shift register because you need to have memory set aside to represent state of the shift register; the values in memory are used to generate the stream of bits that sets the shift register state.

An 8-bit shift register could be represented in memory as an array of 8 bytes, either as plain 8-bit values or as boolean values (boolean is the better idea), occupying one byte for each bit on the register:

byte MyRegister[8];
or
bool MyRegister[8];

Having done that, every object on the shift register can be referred to by its position in the MyRegister array, a number from 0 to 7.  When you want to know or change the state of a particular bit on the shift register, you access the bit via its array index, making the index an address.

What about the Pins?

I am cheating in a sense. I’m relying on the fact that the first shift register in a chain has to be attached to three pins, you need to know which pin is which, and we can embed that information in the method that pushes bits out to the shift register. With that housekeeping squared away, all we need to do to turn shift register outputs on or off is maintain the array representing the shift register in memory, using the array indices as addresses for specific objects.

Here is an example method (derived from the Arduino Shift-Out tutorials, and used in the Roundhouse to run stall lamps) that writes to shift registers, using a static byte array to hold the register state information:

const int clockPIN = 3;
const int latchPIN = 4;
const int dataPIN = 5;
void registerWrite(int output, int state) {
  static byte outputStates[8];
  byte bitsToSend = 0;

  // update the outputStates array
  outputStates[output] = state;
  
 // Set bits according to the outputStates Array
 for(int i = 0; i < 8; i++){
     bitWrite(bitsToSend, i, outputStates[i]);
 } 
 // turn off the output while shifting bits
 digitalWrite(latchPIN, LOW);
 // shift the bits out
 shiftOut(dataPIN, clockPIN, MSBFIRST, bitsToSend);
 // turn on the output
 digitalWrite(latchPIN, HIGH);
}

This is what I mean by proper context. Here the context is a known “base address” for the shift register that never changes: the LatchPin, ClockPin and DataPin used to access it. Once context is established with a method that knows the “base address” and writes to the shift register, we no longer need to worry about that aspect of the problem and can supply the unique part of the address — the position of the output we wish to change — as an argument to the registerWrite method. In this instance, the address supplied to the method should be considered a relative address, because it is relative to the static portion of the address stored in the method code.

This simple approach works great for a shift register or two, but gets unwieldy when trying to manage the state of multiple shift register outputs with a single array. Further, memory is a precious resource and using a byte of memory to represent the state of a single bit on a shift register is inefficient at best. For a layout with hundreds of controllable lights for signals, crossings, street lights, building lighting and whatever else you can think of, something more sophisticated is called for.

The Data

So lets start with a better way to hold the values of the 8 bits of a shift register; I call shift registers “nodes”:

typedef struct nodeState {
  byte pins;
};

The 8 bits constituting a single byte of memory represent the 8 bits of a node. This is a big improvement in both memory efficiency and execution efficiency, since the pins byte can be sent directly to the target register without any processing.

Next we need a type to hold all information relevant to a chain of nodes:

typedef struct nodeGroup {
  byte clockPin;
  byte latchPin;
  byte dataPin;
  byte numNodes;
  nodeState *nodes;
};

Now we have all the data we need to handle a chain of shift register nodes in one place. Notice how the nodeState type is used. Once instantiated,  the *nodes element will point to an array of bytes, one for each node.

Notice something else: the door is now open to run multiple, distinct chains of nodes easily by creating a nodeGroup data instance for each chain. This confers even more freedom to structure resources for the convenience and logic of the sketch.

What’s The Address?

So that is how the nodes are attached and accessed. Now we’re ready for an address form that will work with this architecture:

typedef struct nodeAddress { 
  byte chain;
  byte node;
  byte pin;
};

With all node data organized into nodeGroup structures, the nodeAddress type provides what amounts to an absolute address to any resource defined in this way. An absolute address contains all necessary parts of an address, and is processed by methods without having any hidden address elements embedded in the method code.

Station and Signal 5

Practical Application

The test loop has one chain of four nodes.  For that sketch, a single node group is instantiated this way:

nodeState ndata0[] = {0,0,0,0};
nodeGroup nodeGroups[] = {{7, 6, 5, 4, ndata0}};

Here I’ve defined an array of nodeGroups with one member. The node group uses pins 5, 6 and 7 on the Uno, and has four member nodes. An array of nodeState structures, one for each node, is defined as ndata0[], and incorporated into the nodeGroup via the ndata0 pointer.

Signals on the test loop are defined by a different data structure that includes this element

nodeAddress addr;

capturing the base address of a particular signal. When the sketch is ready to set the state of a signal, it passes the nodeAddress to the node setting method, like this:

nodeSet(signal.addr, nodeBits);

All manipulation of the nodes can be accomplished with 4 methods:

void nodeWrite(struct nodeAddress addr, byte state) {
  // write the state to the pin bit defined by nodeAddress addr
  bitWrite(nodeGroups[addr.chain].nodes[addr.node].pins, addr.pin, state);
  nodeRefresh();
}
void nodeSet(struct nodeAddress addr, byte state) {
  // set the pins element with a byte value
  nodeGroups[addr.chain].nodes[addr.node].pins = state;
  nodeRefresh();
}
byte nodeGet(struct nodeAddress addr){
  // get the pins element for a node
  return nodeGroups[addr.chain].nodes[addr.node].pins;
}
void nodeRefresh(){
  // Shift out current node data, one node group at a time
  for(int i = (NODE_GROUPS - 1); i>=0; i--) {
    // shift all bits out in MSB order (last node first):
    // Prepare to shift by turning off the output
    digitalWrite(nodeGroups[i].latchPin, LOW);
    // for each node in the group
    for(int j = (nodeGroups[i].numNodes - 1); j>=0; j--) {
      shiftOut(nodeGroups[i].dataPin, nodeGroups[i].clockPin, MSBFIRST, nodeGroups[i].nodes[j].pins);
    }
    // turn on the output to activate
    digitalWrite(nodeGroups[i].latchPin, HIGH);
  }
}

The nodeSet method used by the signal system—primarily because a signal requires an output for each aspect (red, green, yellow)—sets the pins byte for a random node in one operation.

NodeWrite sets a single output bit on any random node.

NodeGet retrieves the current pin byte for a random node. The signal system uses this method to retrieve the bits on a signal node, before modifying the values and applying them with nodeSet.

NodeRefresh writes the pins bytes to the nodes.

There is, of course, more going on with the signals on the test loop. Deciding what aspect a signal should show at any given time is a matter of logic and data. In the next post in this series I’ll do a deep dive into my first take on making signals operational within an Arduino environment.

Until then, happy railroading!