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.
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
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.
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.
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.
I came across your site looking for block detection to trigger crossing signals. I am new to arduino trying to get this to work. I am dissatisfied with IR sensors. Could you send me the completed sketch? Also I have a double loop track. Could I wire 2 block detectors into one arduino mini to control one set of signals (so a train passing on either track will turn on lights)? Thanks for your help and insights. I am ordering the acs712s and have mostly sound locos in HO so don’t think sensing will be a problem.
Hi Dave,
I have uploaded a demo sketch to my new github repository. Go to https://github.com/rpsimonds/thenscaler/tree/Current-Sensing and download acs712.ino.
Can you do more than one sensor? Yes. You can run as many sensors as you have analog pins available to connect to, and any of them could actuate your signals.
I suggest you start with one sensor and this demonstration sketch. Observe the serial output and make sure it is behaving as you expect. Then, modify the sketch so that instead of outputting a serial message about detection have it actuate your signals. When you have it working satisfactorily, you can expand to an additional sensor fairly easily.
Best, Robin
Hi – how would this go with current sensing on a DC PWM layout ? How does it go with different polarity signals on DC ?
Hi Brant,
The ACS712 is a bi-polar sensing device, so it will produce a signal at either polarity. It will work with DC PWM, DC, AND DCC. Pretty much anything.
The big limitation of the ACS712 is that sensitivity is limited to 20 mA; that is not enough sensitivity to detect an idle locomotive or resistor-wheels.
It turns out that you can use CT Coils with DC PWM. CT Coils are generally used with fully alternating current, but they also work with single polarity PWM signals. See Current Sensing and Occupancy Detection for DCC for more info about CT Coils. I’ve achieved 100 microAmp detection thresholds with CT coils. That is seriously sensitive!
With DC PWM, you’ll get the best results using CT Coils instead of the ACS712.
Best,
Rob
Hi Rob
Have you tried using the ACS712 to control signals? I’m in the U.K. and want to build a sequencer to operate 3 aspect signals but I can’t use IR on my garden railway, therefore current sensing seems a logical solution. Unfortunately, I know nothing about electronics! Cheers Mike
Hi Mike,
Yes, you can use the ACS712 sensors to control signals. Once you master using the sensors to determine whether or not a block is occupied, you can implement signalling fairly easily. Here are some posts that may help: Basic Signaling for a Small Layout, Adding Signals to the Test Loop and Adding Signals to the Test Loop, Part 2.
Also, look at this post on CT sensors: Current Sensing and Occupancy Detection for DCC.
Best, Rob
Parts of the sketch are cut off on the right side. Can you send the entire sketch so that the cut off parts are visable
Does this help:
// 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;
}
This program is absolutely unbelievable! worked first try on my dc layout. The code is very clear and easy to understand. I’m so thankful that you spent your time sharing your expertise as it has really helped my layout progress into automated control.
I have a modular layout that connects the modules with dcc power and dc 15v wires. I am using nce switchkats to control switches because they are addressable over the dcc power wires. Is there an occupancy detector that will use the dcc power wires to indicate occupancy to an nce system or do all systems have to be hard wired into a device connected to the control buss?
There is no specific standard that I’m aware of for how block occupancy detection works, including how block occupancy is communicated to other parts of a layout — well, there is LCC, but its a standard that has not been widely adopted so its not very useful. If LCC were widely adopted, that would be a pathway to layout control using DCC messaging.
What vendors generally do is come up with a proprietary solution that doesn’t necessarily work with another vendor’s system. NCE equipment tends to be more generic and standards based — I seem to recall that they have a block detection solution, but I might be wrong about that. Digitrax has a proprietary system that includes a way to identify specific locomotives with a transponder system. Typically, regardless of the vendor, binding the parts together requires JRMI or some other system to interpret and act on block occupancy messages. Stationary DCC decoders are not smart devices so some other system has to intervene to do things like set signals or grade crossings based on occupancy changes.
If you are interested in exploring a more comprehensive layout control solution, take a look at the system I offer on my commercial site: A Guide to the Layout Control Operating System. LCOS creates smart layout objects that can listen for and act on messages from other layout objects. My solution runs on inexpensive micro-controller hardware that communicates via a wireless network with peer devices on your layout; a DCC interface device is available to join the systems and allow you to control layout objects with your throttle. No external computer is required.
Otherwise, you can use JRMI on a PC, Raspberry PI or other computing device with the Java VM installed to do the same thing, creating and binding together layout objects so they can respond to messages in an appropriate way.
R