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.

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!

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.

 

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.