Update

Happy Independence Day weekend to my US readers. Well its been several months where time for railroading has been limited. Top of the list, our nine-year-old beagle developed serious back/neck problems a few months ago that ultimately required surgery.

Lewis the beagle; 2 weeks after back surgery.

Lewis the beagle; 2 weeks after back surgery.

Little fellow is recovering and doing well with his rehabilitation program–exercises given to us by the specialty vet. Things like stairs are going to be a problem for him for a while, so he won’t be joining me in the layout room, as he usually does, in the near future.

Even so, I have some L&NC construction news along with updates on on-going projects.

 

ACS712 – How Low is Low Current

ACS712 Board

ACS712 Board

I’ve been exploring the limits of low current sensing with ACS712 sensors. Sparkfun markets a sensor board combining an ACS712 with an OP Amp that they characterize as a low current sensor. I had no luck calibrating and using that sensor; if someone reading this has used it successfully, please leave a comment explaining how you calibrate and use it.  Additional experiments with Op Amps, including creating a fairly stable instrumentation amplifier, have not been encouraging though I haven’t entirely given up and have a few possible circuit designs yet to try.

The fundamental constraint with ACS712 sensors is that their output noise inhibits current sensing below 20 mA (officially, Allegro says that you can’t detect below 20 mA without a specially designed version of the chip; which they will happily work on for a paying customer). At higher currents, the sensor works pretty much as advertised and can be used with or without filtering. At and below 20 mA it is difficult to tell the signal from the noise without some sort of mathematical filter; the RMS method I use can detect down to around 15 mA with an UNO’s 10 bit ADC.  With a Mayhew Labs 14 bit ADC, I seem to get a little more sensitivity, around 12 mA, using the RMS algorithm.

Below 12 mA noise takes over, at least so far as short term measurements go. So I’ve been looking at data collected over longer time periods and have a preliminary method that can discern a low current signal down to around 8 mA.  In essence, a low current change causes the long term output noise range to shift up or down depending on whether the current is increasing or decreasing. Capturing this range shift should improve practical detection sensitivity considerably. Its promising, but I still have kinks to work out of the system.

Is 1 mA sensitivity—the holy grail for detecting a single 10k resistor wheelset—possible with an off-the-shelf ACS712 sensor?  Probably not, and that may matter to people who want to use off-the-shelf 10k resistor wheelsets.

That does not make these inexpensive sensors useless. If you don’t mind making your own resistor wheelsets, then a single 1.5k resistor (assuming one resistor wheelset per car; two wheelsets at 3k—in parallel producing a net 1.5k resistance—one at each end of the car, would be a better idea for a bunch of reasons) will be detectable while still drawing under 1/10 watt (at 11.5 volts DCC n-scale standard; higher voltages allow for more resistance). 100 wheelsets would draw less than one Amp total current. More on that when I get the system operational.

Nevertheless, I’m curious whether a 16 bit ADC will help further resolve the low current signal. I’ll be giving that a try this summer so we will see.

Construction Progress

Laying Track-Bed at the Red Bluff Yard.

Laying Track-Bed at the Red Bluff Yard.

I finished drawing in the track plan, including some additional yard space, on the lower level modules and started in on laying roadbed and track.

Roughed In Turntable

Roughed In Turntable

Roughing in the turntable was fairly straight forward. After preliminary rough siting, I installed the roundhouse base and drew in the track center-lines. That located the turntable center (the intersection of the track center lines), enabling me to cut out an appropriately sized circle to accommodate a pit I’ve already created using a plywood base and styrene strips.

Building the Turntable Pit with Styrene Strips glued to a plywood base.

Building the Turntable Pit with Styrene Strips glued to a plywood base.

The turntable project is a major layout item in its own right and will be the subject of multiple future posts.

Laying Track for the Yard

Laying Track for the Yard

It took a few sessions, but I laid all the lower level track except for the turntable/roundhouse area (which I will do when I install the turntable) and joined it with the helix.

Tracklaying on Lower Level Done.

Track-laying on Lower Level Done.

Track Laid and Ready for Painting and Ballast.

Track Laid and Ready for Painting and Ballast.

For those interested in the nitty-gritty of track laying techniques, I used adhesives rather than mechanical fasteners for all roadbed and track. I use Liquid Nails for projects (water based) to adhere foam to wood, and anything else to foam. For the track I used DAP ALEX clear latex silicone caulk, deploying T-pins to hold the track in position while the caulk cures (just a few hours). What I like most about this method is that once cured the adhesive is completely invisible creating a nice base upon which to detail the track. Now that I’ve used clear caulk for track laying, I can’t imagine using a grey (and definitely not white) product unless the color closely matches the intended ballast color.

I don’t know if you can tell from the photograph of the whole lower level above, but the lighting system using Addressable LEDS that I’ve devised is working rather well (granted the wiring needs  tidying up; I’ll get to that eventually).


 

Block Occupancy Detection for DC and DCC, Part 4

Through the first three parts of this series (links to part 1, part 2 and part 3) I’ve experimented with and refined to a degree the use of ACS712 current sensors for block occupancy detection. The system works well in DC mode, lighting up whenever a running DC train enters or is powered up in a block; program logic “remembers” block states when power is off for control reasons. My DC locos typically start drawing 30 – 50  mA when the lamps come on.

In DCC mode, ultra low current detection becomes an issue and the off-the-shelf ACS712 sensors don’t meet every need. My BLI DCC/Sound Locomotive draws enough power to be detected with the current system in idle mode, all sound and lights off. However, some standard DCC decoders draw so little current in idle mode that they are not consistently detectable. Further, constant track power in DCC invites us to find a way to sense any object drawing some minimal increment of current — such as 1.1 mA for a 10KΩ resistor wheelset on an 11 volt (in N scale) feed. There are a number of obstacles to achieving 1 mA  sensitivity with ACS712 sensors.

The Challenges

First, there is a question of how low a current the ACS712 can resolve. Theoretically, because the chip responds in a ratiometric way to current input, any amount of current will provoke a proportional output response. In reality, the output of the chip is a little noisy, and the noise masks the low current response.

Allegro MicroSystems says that the best resolution with the optimum filter capacitor is 20 mA. There is some general agreement among internet cognoscenti that with some amplification of the signal from the ACS712, greater sensitivity should be possible.

Second, the 10 bit ADC built into most Arduino boards has a maximum resolution of 4.88 mV (5 volts / 1024 steps), limiting the minimum detectable current to 26 mA (4.88 mV resolution / 185 mV/A sensitivity). Even the 12 bit ADC found in the Due and Zero boards is not quite close enough, with a maximum resolution of 1.22 mV (5 volts / 4096 steps), resulting in minimum detectable current of 6.6 mA.

A Better Analog-to-Digital Converter

Of the two problems, this is by far the easier problem to solve by using an external ADC with a 14 or 16 bit resolution. A 14 bit ADC has a maximum resolution of .305 mV, which is a good resolution for consistently detecting current at the 1.6 mA level or higher; a 16 bit ADC would provide 76 µV resolution and current detection in the mid to high µA range.  For those wanting to detect a single resistor wheelset, 16-bit is the way to go.

For my purposes and goals, I’m going to see how it goes with 14 bits so I’m not pushing resolution past the point of usefulness at this stage. My thinking is that 1.6 mA should be sufficient to detect a pair of resistor wheels. Two 10kΩ resistors in parallel have a resulting resistance of 5kΩ, which at 11 volts will draw 2.2 mA (@16 volts, 3.2 mA). Assuming I can overcome the noise problem to the point where 14 bits of resolution provides accurate current detection, the final step would be to go to 16 bits.

I looked at several ADC products and settled on the Mayhew Labs Extended ADC Shield, which is also available at Amazon. I chose this for several reasons: it supports 8 inputs, comes in a shield format and (perhaps most importantly) offers enough bandwidth to be able to sample multiple sensors at a reasonable rate.

Mayhew provides a code library for reading a variety of sensor types through the shield. It is fairly straight forward to use, and the shield is a very good quality piece of gear. I have found other ADC boards, but none with the unique capabilities of this one.

Mayhew Labs Extended ADC Shield

Sparkfun Low Current Sensor

Sparkfun sells a breakout board as a low current sensor using the ACS712 sensor chip plus an operational amplifier to amplify the signal. This is the only commercial solution of its type I’m aware of, so I bought one to check out and test. The schematic for the board can be seen here.

SparkFun Low Current Sensor Breakout – ACS712. This image is from an older version; different trimpots are used in the current version.

Although the circuit is promising, this board is really an experimental item and not suitable for production use on a model railroad. Part of the problem is that it does not provide — and is not drilled for — standard screw terminals for attaching the input current source, adding additional challenges to deployment. On the input side (I+ / I-) it is drilled for a pair of header pins and two larger holes with contact pads. The control side (VCC, OUT, GND) is drilled for standard headers.

Secondly, because this is an experimental board, it has two trim-pots for configuring the Op-amp.  Sounds great, but in practice this setup doesn’t work well. Add sketchy instructions for setting and using the board and I think most will find this board impractical for large scale use, though an interesting experimental tool.

Learning By Failing

So when the board arrived, I soldered on some header pins and hooked up the sensor to an UNO with the Mayhew ADC Shield, then attempted to configure and use the board. Here are the setup instructions in their entirety:

“To calibrate, first set the output offset to the desired level (with zero current on the sense lines, read output with a DVM). Then with a known current input (a 100mA limited supply works well for this), set the output deflection with the gain pot. Sensitivity is then calculated as (Vref – Vdeflect)/(current input).”

The “output offset” is (presumably, though not literally stated) adjusted with the vref trim-pot.  But what “level” is “desirable” for this application?  The second instruction is even weirder, since you are not going to short out a power supply by directly attaching it to the sensor without a load, and its the load that determines the current flow. I’m sure they meant to say something like “supply a load with a known current draw.”

I tinkered fruitlessly with the trim-pots for hours, testing various theories about how it should work and getting nothing intelligible out of the sensor. The gain pot is straightforward enough, resulting obvious changes to readings as the trim-pot is adjusted. Its the vref adjustment that mystified me and seemed to have no logic. I  did notice that when setting it there was a point where the output would suddenly, but only briefly, drop to O. That zero point is so finely specific that it is essentially impossible to set the trim-pot at that level; it always ends up off one way or another. That was a clue, but I did not yet understand.

Back to School

I found a nice tutorial about Op-amp circuits and, after a while started to understand the circuit and comprehend the problem. If you don’t understand Op-amps (who does among those of us not trained in electrical engineering?), check out the tutorial before reading further here.

The Op-amp circuit Sparkfun uses is a “voltage subtractor,” the intent of which is to  subtract the input voltage from the reference voltage and amplify the difference. This is the right choice—I think—but the implementation is wrong, at least for my purposes.

Unique ACS712 Properties

Many sensors that one might use with an Arduino are straight forward linear output devices where the output voltage ranges from 0 to 5volts in proportion to an input (light, temperature, etc.).

What makes the ACS712 different is that it senses both polarity and current of the measured input.  The “quiescent voltage” we measure when calibrating a sensor is the sensor’s “zero” point: ideally VCC/2 — midway between 0 and VCC (nominally 5 volts).  When the polarity is one way the measured current is represented by a value above VCC/2; when the polarity is the opposite, the sensor produces a value under VCC/2.

That means that we are only interested in the offset between the sensor output and its quiescent value. The software calibration routine I’ve been using seeks to get the most accurate average mid-point value possible to enhance the accuracy of the offset measurement. Amplifying that offset should be the route to gaining sensitivity.

Shouldn’t vRef be VCC / 2?

It may well be that the vref adjustment has value when measuring DC current with a fixed polarity. However, I’ve seen no math or theory in support of that proposition. For measuring DCC current, which is an 8kHz alternating polarity wave form, there is only one correct reference voltage for comparison to the ACS712 output: VCC/2.  So, Sparkfun had the right idea using a trim-pot as a voltage divider circuit to generate the reference voltage, but the trim-pot inhibits setting vref to VCC/2 because it is too imprecise to set the two resistor legs to exactly the same value.

What Now?

The Sparkfun sensor experiment was a bust in that it did not produce a usable result.  But it did help me research and think about what it is I need an operational amplifier to do in order to successfully amplify the ACS712 signal. Sometimes we learn more from failure than we do from success.

The next step is the set up an Op-amp with a fixed reference voltage of VCC/2, then continue experimenting from there.  I have the parts. In a few weeks, if time permits (between work and our ailing Beagle, time is tight right now), I should have some sort of result.

Block Occupancy Detection for DC and DCC, Part 3

In Part 1 of this series I demonstrated the basics of current sensing with ACS712 sensors and an Arduino microcontroller. In Part 2, I demonstrated the test loop wired for four blocks, occupancy detection and signals.

In this post I’ll put on my programmer’s hat and talk about the code behind block occupancy detection on the test loop in Part 2. Some is an improved version of code used in Block Occupancy Detection for DC and DCC, Part 1.  The rest is new; and I’m scaling from a single to multiple detectors. I’ve also changed the way I calculate the occupancy threshold so that it is calibrated for each sensor.

ACS712 Basics

Current Sensors and Block Feeder Connections.

ACS712 Current Sensors and Block Feeder Connections on the Test Loop.

The basic concepts for working with an ACS712 sensor board are discussed in  Block Occupancy Detection for DC and DCC, Part 1, but lets review them here:

–The ACS712 sensor senses current flow regardless of polarity up to its rated maximum (5, 20 or 30 amps). That means that it can be used with both DC and AC current. DCC track power is an AC type wave form alternating polarity at about 8 kHz. Raw sensor readings are noisy, requiring some mathematical processing to be useful.

–For maximum accuracy sensors have to be calibrated at start up. This is a software process by which you determine the value produced by each sensor when there is no current present, the average quiescent voltage also called adc_zero. In addition, I now calculate the average quiescent current reading  (AQC) at adc_zero; this value is used to calculate the occupancy detection threshold as explained below.

–The current detected by a sensor is represented by the difference between the reading and adc_zero for that sensor, converted to milliamps by the sensitivity factor for the version of the chip in use. Here’s the formula:

CURRENT (in mA) = (RAW_VALUE  – ADC_ZERO) / SENSITIVITY

–Accurately measuring an AC waveform requires multiple readings over a defined sampling period, using a sampling interval that is shorter than the frequency of the current you are trying to measure in order to sample the entire cycle. The Root Mean Square of the data set is calculated to arrive at an rms current reading.

Determining Occupancy

The heart of the occupancy detection system is a function to read current from a sensor and return an rms current value. This function is intended to be called once for each sensor during the detection cycle, so the target sensor’s pin and adc_zero are passed as arguments.

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;
      // convert to amperes
      adc_raw /= SENSITIVITY;
      // accumulate the sum of the squares of the readings
      currentAcc += (adc_raw * adc_raw);
      ++count;
      prevMicros += sampleInterval;
    }
  }
  //https://en.wikipedia.org/wiki/Root_mean_square
  // square root of the sum of the squares / number of samples
  float rms = sqrt((float)currentAcc / (float)numSamples);
  return rms;
}

During the detection cycle, readCurrent() is called by another function that determines if the block is occupied by comparing the rms current reading to a threshold value, returning either true or false.

bool isOccupied(int block, float adc_zero, float threshold) {
  float current = readCurrent(block, adc_zero);
  return (current > threshold);
}

Sampling Parameters

Here are the key sampling parameters I’m currently using.

// constant variables for block occupancy detection

// sample over 58 ms; 100 times the nominal pulse width for binary 1 in DCC
// sample time is expressed in microseconds
const unsigned long sampleTime = 58000UL;

// number of samples taken during a read cycle 
const unsigned long numSamples = 200UL;

// ADC conversion time is about 50µs (+/-)
// the sampling interval-here 290µs-should be longer than then ADC conversion time 
const unsigned long sampleInterval = sampleTime / numSamples;

I originally started with 100 ms sample time, then later realized I could shorten it, which helped compensate for the time required for two step detection as described below. I’ve only just begun to play with the variables and see how time efficient I can make the detection process.

Managing Block Data

I should back up a bit and explain my data handling strategy.  I represent each block with this data structure:

typedef struct BLOCK_DEF {
  int pin;
  int aqv;
  float aqc;
  bool occ;
  bool chg_pending;
};

Each block is a sensor, so the first element is its pin assignment, and the next two elements (aqv & aqc) of the structure are the calibration data for the sensor calculated at startup.  The fourth element, occ is the current occupancy state of the block (true if occupied, otherwise false). The last element indicates whether a change in occupancy state has been detected but not finalized (true or false).

The test loop sketch defines an array of BLOCK_DEF structures for the four blocks, initializing the array with the pin numbers and starting values for the remaining elements; An additional BLOCK_DEF structure is defined and initialized for for the master current sensor:

BLOCK_DEF blocks[NUM_BLOCKS] = {
    {0, 0, 0, false, false},
    {1, 0,0, false, false},
    {2, 0, 0, false, false},
    {3, 0, 0, false, false}}; 
    
BLOCK_DEF master = {4, 0, 0, false, false};

Calibration

My calibration routine has evolved to require two functions. The first is the same as in the original demonstration; the second function is new. Remember that track power must be OFF during calibration.

int 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(100);
  }
  VQ /= CALIBRATION_READS;
  return int(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;
}

The first function determines the quiescent voltage, adc_zero, which is saved in the aqv element of the BLOCK_DEF structure. With adc_zero calculated, the second function determines the average quiescent current reading at adc_zer0, which is saved in the aqc element of the BLOCK_DEF data structure.

Here’s the calibration procedure from setup():

  master.aqv = determineVQ(master.pin);
  master.aqc = determineCQ(master.pin, master.aqv);
  
  for (i = 0; i < NUM_BLOCKS; i++) { // for each block 
    blocks[i].aqv = determineVQ(blocks[i].pin);
    blocks[i].aqc = determineCQ(blocks[i].pin, blocks[i].aqv);
  }

Why the second step? Because even though there is no current to be sensed, the sensor still produces a small reading. The sensors on the test loop produce an AQC reading from 9 to 24 mA. By capturing the average quiescent current reading, the detection threshold can be automatically adjusted for each sensor.

Instead of using a hard current threshold (like .0259, about 26 mA, as I did in the first demo), I now use a multiplier that I apply to the AQC reading for each sensor to set the detection threshold for that sensor.

Detection sensitivity is controlled by the multiplier. The multiplier I used during the demo was 1.5, which produces satisfactory results on the test loop, but is insensitive to low power devices.

Set the multiplier too low, and block states will flicker because of the variance in sensor readings. On the test loop I’m inhibited because of the Atlas bumper light and its draw on track power on block 3. I’m able to bring the multiplier down to 1.095; below that block 3 flickers mercilessly.  But the other blocks do not, so I think even lower multipliers and higher sensitivities are possible. I’ll find out on the L&NC.

The Detection Cycle

A detection cycle occurs on every iteration of the main loop. The first step is to check the master power. If the master power is on, then each block is checked in turn. If a block’s occupancy state is changed during the cycle, a notification is sent out to “subscribers,” other devices that have asked to receive block occupancy data. That is how my control panel gets updated.

  power_state = isOccupied(master.pin, master.aqv, 
           master.aqc * DETECTION_MULTIPLIER, master.chg_pending);
  if(power_state != master.occ) {
    if(master.chg_pending) {
      master.occ = power_state;
      master.chg_pending = false;
    } else {
      master.chg_pending = true;
    }
  } else {
    master.chg_pending = false;
  }
  if(master.occ){
    for (int i = 0; i < NUM_BLOCKS; i++) {
      block_state = isOccupied(blocks[i].pin, blocks[i].aqv, 
               blocks[i].aqc * DETECTION_MULTIPLIER, false);
      if (block_state != blocks[i].occ) { // if occupancy state has changed
      // if a pending change has been previously detected
      if(blocks[i].chg_pending && !master.chg_pending) {
           blocks[i].occ = block_state;
           blocks[i].chg_pending = false;
           String id = String(i);
           notifySubscribers(1, id, String(block_state));
         } else { // initial change detection
           blocks[i].chg_pending = true;
         }
       } else {
         blocks[i].chg_pending = false;       
       }
    } 
  }

As stable as the current sensing system is now, I have found that when a locomotive is crossing a block boundary and drawing from two blocks you can get some jitter causing the newly occupied block’s state to flicker.

My current (pardon the pun) solution is to implement a system rule requiring 2 consecutive readings to confirm a block state change.  So, the first time a state change is detected, the block (or the master) is marked pending. On the next cycle, if the state change is detected again, the state change is accepted. Otherwise the pending flag is reset. The additional time lag in detection this creates is unnoticeable, and it completely eliminates detection jitter.

What’s Next?

Dual Searchlight Signals, Scratch Made with BLMA Signal Heads.

Dual Searchlight Signals, Scratch Made with BLMA Signal Heads.

The whole point of block occupancy detection is to put that data to use in some way. The most basic use for it is to run signals, which I used on the test loop as a way of showing occupancy detection in action. That can be done through JMRI, or as part of the layout’s intrinsic Arduino processes which is they way I’m approaching it.

I think of signals as the simplest form of animation a layout can and should have. As you can see from the demo, signals done the Arduino way are equally functional in a DC or DCC environment. It simply doesn’t matter how you run your trains: if you want signals then you should have them!

But, as always, there are unique complications that arise when doing things the Arduino way, not the least of which is the finite number of pins available to run lights or other external devices.

Here’s a hint: running signals the Arduino way works best when the signals are tied together in a physical and logical chain.

That, fellow railroaders, is the subject for another post.

 

Block Occupancy Detection for DC and DCC, Part 2

In Block Occupancy Detection for DC and DCC I demonstrated the use of an ACS712 sensor and an Arduino Uno to sense current draw across the rails, the basis for most block occupancy detection systems. Assuming that one has also equipped some rolling stock with resistor wheel-sets, lighting or sound decoders, so that the entire length of a train is detectable, the theory is that current draw indicates that a block is occupied — and lack of current draw indicates that a block is clear.

In reality, it may not be that simple if you have devices drawing from track power other than locomotives and rolling stock. Any background current draw on rail power can interfere with the current sensing model.

Rail Powered Accessories

The Test Loop

The Test Loop

Keen eyes will notice that the test loop has a rail powered light on the Atlas track bumper at the end of the siding. I laid the track on the test loop long before I understood block occupancy detection.  I won’t do that again as I build the L&NC.

The problem is that the bumper light draws from the track itself and thus presents a constant load on that block. The draw of the lamp forces a slightly higher threshold for determining occupancy than would otherwise be appropriate.  Static loads on monitored blocks greatly complicate current sensing.

The best practice where current sensing is used to detect block occupancy is to feed accessories from a separate power bus. To use track power (as you might with a stationary decoder running a turnout motor), draw from outside monitored blocks—from a separate branch or sub-bus—to avoid confusing the occupancy detection system.

Solving the Continuity Problem.

While current sensing works well on a DC layout, as shown in the first video demonstration, the use of current as a control mechanism means that block occupancy is undetectable at times because of normal operation. Similarly, any transient loss of DCC track power (as with a short) will throw the occupancy detection system off for the same reasons. I think of this as a continuity problem.

The solution in both cases is to monitor the track bus with an extra sensor and lock the state of the system whenever the track bus is off. The simplest algorithm, puts it a slightly different way:

if track current is flowing then check blocks and update else maintain current state

E.g. ( in C++, where “master.on” represents the master current status):

if(master.on == true) {
  for ( i = 0, i < NUMBER_BLOCKS; i++) {
    // check block sensor i and update block status
etc......

For the cost of a sensor and a few lines of code the block occupancy system will now maintain its state correctly whenever there is a total loss of track power.

Wiring for Detection

Preparing the test loop for the Arduino & ACS712 sensor based occupancy detection system was a bit of a chore, primarily because current sensing requires some rethinking of the standard approach to layout wiring.

Wiring. Block sensors were added in Phase 2. Phase 3 signals and lighting in progress.

Test Loop Wiring. Block sensors were added in Phase 2. Phase 3 signals and lighting were in progress when this picture was taken.

The test loop represents my first crack at it. Needless to say, there is a list of things that will be done differently on the L&NC.

Routing Buses

The loop has a bus structure that, except for the lack of the DCC command bus (I connect my throttle directly to the booster when it is attached to the test loop), is the prototype for the L&NC.

Test Loop Connection Panel

Test Loop Connection Panel

The bus structure begins with the connection points on one end of the layout, mounted on a connection panel made from .080 styrene sheet. Every module on the L&NC will have one of these, with matching cables at the opposite side where it adjoins the next module.

Starting from the top the first two connection groups are polarized power connections made with color coded Anderson Powerpole connectors. A “Recommended Practice” per the Ntrak Manual, these things are a reliable solution for creating polarized power connections.  Some parts are available from Amazon; I find that Powerwerx.com has the best selection of PowerPole parts and supplies. The 15 amp gear should be sufficient for most N Scale layouts (an N Scale layout requiring more than that would be something to see!). Kudos to Ntrak for finding and adopting these connectors. BTW: the crimper is expensive but indispensable if one is going to make a bunch of Powerpole connectors.

The top group is the main DC power bus in three voltages: 12 (yellow), 5 (red) and 3.3 (blue) plus ground (black). This was also where I attached a label with the loop’s assigned IP address.

The next group down is the track bus (red & black); my DC power pack and my DCC booster can connect to the loop here with a matching plug.

The next two connections are an RJ45 port for Ethernet and a USB connection to the UNO. Both are  assemblies I got from Adafruit Industries. On the L&NC, the USB port(s) for attached Arduino boards will be located in an accessible place, away from the other connection groups (which will generally be hidden).

Connection Panel Bus Connections

Panel Bus Connections

Color coded power buses are routed along the edge to a central barrier strip where feeders take off to supply power where needed. On the L&NC I’ll choose different, unique colors for the track bus and feeders to reserve black for system ground and red for system Vcc (+5 volts DC).

Suitcase connectors are really popular these days for connecting wires without soldering. If you are not going to do occupancy detection then directly connecting feeders to bus wires with suitcase connectors is a clean solution. However, once you start down the road I’m traveling–whether your solution is off-the-shelf or homemade–suitcase connectors are not helpful. Instead, I find that PC board mounted screw terminals make easy wiring connections that are reliable and easily changed when necessary. When dealing with magnet wire, which you have to use in N scale if you want to run wires through scale light poles, conduits, etc., top-of-the-line PC board terminal systems are the ONLY reliable solution (the cheap versions will drive you crazy with intermittent connectivity).

ACS712 Board

ACS712 Board

The ACS712 sensor boards come with screw terminals for the monitored load, plus 3 pin headers that take standard 3 wire servo connectors for connecting to system power and the microcontroller. Wires that connect to the central barrier strip have crimped terminals.  I mostly solder at the PC board level and, less often, to make special cables (I solder track connections too, but that is a different issue).

Power Distribution on the Test Loop

Power Distribution on the Test Loop. The board in the upper right corner is for distributing 5 volt power to the signal system via PC board terminals.

On an L&NC module, the bus will continue from the central distributions point out to the edge configured to connect to the next module. One big change in module construction on the L&NC will make wiring easier: the cross frame members will be pre-drilled with large holes. That “little” gouge you see where wires cross the frame member at the bottom of the picture taught me that lesson.

On the test loop the 12 volt bus supplies the attached UNO with its Ethernet shield; the 5 volt bus supplies all sensors and actuators attached to the UNO and is generally considered system Vcc. The 3.3 volt bus is not currently being used on the test loop (but would be great for low current lighting or animations). All share a common ground (really important!). NB: I use modified Computer power supplies that  produce all three voltages at once with a common ground. Multiple DC power supplies can be combined to the same effect; just tie their grounds together to create the common ground.

Even on the test loop, the 5 volt bus is used so much that I need to create a distribution system to get power where needed. The lesson for the L&NC is  I will need to have distribution nodes for the whole power bundle in a few strategic locations on each module. Obviously, I need to do some more detailed wiring planning in advance so I know where everything has to go.

Routing Feeders

Current sensing encourages a nodal distribution system for track power. Instead of attaching feeders to the nearest track bus, the feeders are routed to an area where the sensors have been mounted. You could mount individual sensors at the points where feeders descend from the tracks above. In some situations that may work better; in other situations, aggregating the sensors in groups is the easier and more effective method.

Current Sensors and Block Feeder Connections.

Current Sensors and Block Feeder Connections.

ACS712 sensors read only one of the two rail conductors. On the test loop sensors are attached to the red rail.

I tried to complete the rail feeder connection with a board (with terminals) across from the sensors for the black rail connection points. I also ended up using the same board to centralize the power and data connections for the sensors and the servo for the turnout. In the future I will not mix track / control system connection points in this way.

Block Occupancy Detection in Action

Here is demonstration of the progress so far, with block occupancy detection active on all 4 blocks of the test loop. The signals are programmed to respond to block occupancy and the state of the turnout (the turnout actually has three unique states: “aligned main”, “aligned divergent”, and “in motion”), allowing you to see the system responding as trains move along the track. You’ll see it work in both DC and DCC — the layout works whether a power pack or a DCC booster is plugged into the track bus port. I have yet to see a single commercial occupancy detection solution that moves as easily between DC and DCC as this one does. I added a station with internal lights (connected to the same nodal system that supports the signals) to give just a little hint of the capabilities that will be unlocked on the L&NC.

In future installments, I’ll dig into the occupancy detect code again to give more insight into how it works. I’ll also go into the details of how I’ve implemented signalling on the test loop, including some special (and inexpensive) gear I’ve designed to allow a single microcontroller to run up to 255 nodes of signals and other kinds of lighting or animation.

Block Occupancy Detection for DC and DCC, Part 1

Block occupancy detection is central to advanced layout control. Without it systems like control panels, signals or animation that depend on knowing the position of your trains can’t function. If my concept of building a layout around a network of Arduino boards and off-the-shelf peripherals is going to work, it has to be able to do block occupancy detection.

A Control Panel made with JMRI

A Control Panel made with JMRI PanelPro

As it turns out, block occupancy detection is possible and economical (in terms of equipment cost) with an Arduino and some sensors. In this post I’ll talk about and demonstrate what I’ve learned so far about the electronics of block occupancy detection.


Block occupancy detection can be accomplished a number of ways. The simplest methods involve installing trigger points that detect a passing train using optical or magnetic sensors. These methods generally only work with moving trains, and can’t detect stationary objects located on the tracks between trigger points.

Detecting Current Flow

Enter current sensing, the block detection method most closely tied to the electronic function of the layout. The idea behind sensing current for block occupancy detection is simple enough: current should only flow across the rails in a block that has a locomotive or other device (such as rolling stock with lights or a sound car decoder) on it.  If current flows, the block is occupied; if not, it is (or should be) clear.

How Current is Sensed

Digitrax BDL168 Detection Unit

Digitrax BDL168 Detection Unit

 

Current sensing is generally done indirectly, to avoid drawing on the current (more of a problem in high voltage applications than for a model railroader) and to enable sensors to operate at a different voltage/current from that being sensed. Early commercial block detection systems used  induction coils to sense current passing through wires wrapped around the coil. Newer systems tend to be hybrid or solid-state, reducing size and power consumption. The majority are optimized for DCC and few systems claim to work in DC.

RRCirKits Detection Coils

RRCirKits Detection Coils

To put it another way, there is no block occupancy sensor device on the market with a generic analog or digital output that could be directly read by a general purpose computing device, such as an Arduino. For that matter, I could not identify a single BO system that could easily interconnect with a system from another manufacturer. There aren’t any real standards in this area.

Current Sensing, the Arduino Way

The Allegro ACS712 chip is a leading solid state current sensing solution for power feeds up to 30 amps.  Its works using a phenomena called the “hall effect” to indirectly detect current flow through a silicon chip. The output of the ACS712 connects to an analog port on an Arduino board (or any other microcontroller/microcomputer that supports analog sensing). Connected to an Arduino, a call to analogRead() gives you the output of the sensor.

ACS712 Board

ACS712 Sensor Board

As is usually the case, the chip requires a few external components to function properly; primarily connectors and a filter capacitor/resistor combination to fine tune the sensitivity of the device.  As it happens there is a robust market for simple ACS712 sensing boards, resulting in prices under $2 per sensor if you buy directly from a Chinese source on Ebay, or Alibaba. Even on Amazon.com, sensors can be purchased for about $7 each with Prime 2-day shipping, which makes them slightly cheaper per block than sensors for the most cost-effective systems specifically marketed for DCC.

The only issue I’ve seen with these open market sensors is that there is no specific standard for the filter capacitor. Accordingly, different batches of these boards may have a slightly different sensitivity which would have to be accounted for in the software. As you’ll see, that is not nearly as big a deal as it sounds.

For now I think the optimal strategy for anyone trying to install a block detection system with these components, is to buy all the ones you will need (plus a few extra) in a single batch from a single source. At $2 a pop from China, it makes sense to buy in bulk. In my experience that will guarantee they are all tuned the same way.

DC and DCC Ready

It’s more important for model railroaders that the sensor functions equally well with alternating and direct current. The only difference in the way the sensors work as a system in DC versus DCC comes from the fact that in DC track power is used to control trains; an occupied block may have no power at all for control reasons. That could throw a block occupancy detection system off.

Any current sensing system for DC has to account this; as should any DCC friendly system that wants to preserve block occupancy information between track power outages (such as caused by a short circuit on the track).  We’ll get to solving that problem in the next post.

Plug and Read . . . Not!

I should tell you that if you plug in a sensor and try to determine current state with a single reading…. well, you’re going to be disappointed.  Individual readings seem to jump around a lot although you can discern trends in either direction.

Alternating current presents the additional problem of sensing a current flow that reverses polarity; at any given moment the power could be anywhere in its cycle, including 0 volts at polarity transition. For AC, multiple samples taken at a frequency that is greater than the frequency of the power cycle is required. Something like 8 – 12 samples per cycle will allow proper detection of AC current. Multi-sampling helps with DC too.

First, Calibration

Different sensor chips will produce slightly different analog readings under the same conditions. Any differences in filter capacitors between boards will also produce different readings, as will any +/- variation from 5 volts supplied as Vcc to the chip. To deal with these differences and actual power conditions, we have to calibrate each sensor on startup.

What we need to know is the output of each sensor when there is no current flow (no load); we’ll call this condition adc-zero. The difference between sensor output and adc-zero represents the current sensed by the device.

Getting an accurate number for adc-zero requires averaging a large number of quick readings — 5000 seems to be optimal — like this (we call adc-zero the “quiescent voltage” output):

long VQ = 0;
//read 5000 samples to stabilize value
for (int i=0; i<5000; i++) {
   VQ += analogRead(PIN);
   delay(1);
 }
 VQ /= 5000;
 return int(VQ);

With that number calculated for each sensor, we can get  consistent readings across multiple sensors.

Sampling Current

Reading current consistently requires a multi-sample approach over a defined time span. For this demonstration we’ll accumulate 250 samples taken over a 100 ms span on each read cycle.

Alternating current drives you to calculate the Root Mean Square of the data to filter polarity shifts and get a very stable mean current value you can work with.  Not only does this work well with A/C, it also is equally helpful in getting stable readings of DC current because the output of the sensor varies over time [for the hardcore math and theory, see the Wikipedia page].

Its an easy formula to translate into a computer algorithm, using a function like this :

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 = analogRead(PIN) - adc_zero; // Electical offset voltage
      adc_raw /= SENSITIVITY; // convert to amperes
      // accumulate sum of the squares of converted raw data
      currentAcc += (adc_raw * adc_raw); 
      ++count;
      prevMicros += sampleInterval;
    }
  }
  // calculate the root mean square of the data set
  float rms = sqrt((float)currentAcc / (float)numSamples);
  return rms;
}

Finally, we have to use the rms values to determine whether or not a block is occupied. For the purposes of this demonstration, I’m using a hard rms threshold of .0259 (the adc-zero rms is about .021 on the sensor used for the demonstration); I’ll adopt a different approach when I scale up to a multi-block arrangement.

To further smooth the sketch response, it averages each reading with the previous one. Here is the key code that determines if a block is occupied:

  prev_current = current; // save last reading
  current = readCurrent(currentPin, adc_zero);
  avg_current = (current + prev_current)/2;
  occupied = avg_current > occupancy_threshold;

So lets put the whole journey to this point together in a video demonstration. This demonstrates current sensing in a DC context:

Here’s the sketch in its entirety:

// Advanced Sensing with ASC712
// Using Calibration to find sensor 0
// then sensing by compiling multiple readings to obtain an RMS (Root Mean Square).
// Derived from these forum posts: https://forum.arduino.cc/index.php?topic=179541.0
// NOTE: Forum posters did not account for device sensitivity
///////////////////////////////////////////////////////////////////////////////////
const int currentPin = 0;
// Sampling Parameters
const unsigned long sampleTime = 100000UL; // sample over 100ms
const unsigned long numSamples = 250UL; // the number of samples divides sampleTime exactly, 
                                        // but low enough for the ADC to keep up
const unsigned long sampleInterval = sampleTime/numSamples;  // the sampling interval
                                         //  must be longer than then ADC conversion time
#define SENSITIVITY 185  // from ACS712 data sheet for 5A version, in mV/A
int adc_zero;  // variable to hold calibrated sensor quiescent voltage

boolean occupied = false;
float occupancy_threshold = .0259;

float current = 0;
float prev_current;
void setup()
{
  Serial.begin(9600);
  Serial.println("\nACS712 Current Sensing Basic Demonstration\nMultiple Readings converted to RMS at 1 second intervals\nValues for quiescent output are determined by Calibration.\n\nCalibrating the sensor:\n");
  adc_zero = determineVQ(currentPin); //Quiescent output voltage - the average voltage ACS712 shows with no load (0 A)
  delay(1000);
}

void loop(){
  float avg_current;
  prev_current = current;
  current = readCurrent(currentPin, adc_zero);
  avg_current = (current + prev_current)/2;
  occupied = avg_current > occupancy_threshold;
  
  Serial.print("Current Sensed:");
  Serial.print(current * 1000 ,1);
  Serial.print(" mA\t\t");
  Serial.print("The block is ");
  if(occupied){
    Serial.println("occupied");
  } else {
    Serial.println("not occupied");
  }
  
  delay(1000);
}

int determineVQ(int PIN) {
  Serial.print("estimating avg. quiscent voltage:");
  long VQ = 0;
  //read 5000 samples to stabilize value
  for (int i=0; i<5000; i++) {
    VQ += analogRead(PIN);
    delay(1);//depends on sampling (on filter capacitor), can be 1/80000 (80kHz) max.
  }
  VQ /= 5000;
  Serial.print(map(VQ, 0, 1023, 0, 5000));
  Serial.println(" mV");
  return int(VQ);
}
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 = analogRead(PIN) - adc_zero; // Electical offset voltage
      adc_raw /= SENSITIVITY; // convert to amperes
      currentAcc += (adc_raw * adc_raw);
      ++count;
      prevMicros += sampleInterval;
    }
  }
  //https://en.wikipedia.org/wiki/Root_mean_square
  float rms = sqrt((float)currentAcc / (float)numSamples);
  return rms;
}

Next installment, we go to the next level: multiple blocks and adding signals to the mix. And I’ll demonstrate everything in both DC and DCC.