{"id":604,"date":"2016-01-16T16:48:17","date_gmt":"2016-01-17T00:48:17","guid":{"rendered":"http:\/\/thenscaler.com\/?p=604"},"modified":"2016-02-09T15:59:12","modified_gmt":"2016-02-09T23:59:12","slug":"block-occupancy-detection-for-dc-and-dcc-part-3","status":"publish","type":"post","link":"https:\/\/thenscaler.com\/?p=604","title":{"rendered":"Block Occupancy Detection for DC and DCC, Part 3"},"content":{"rendered":"<p>In <a href=\"https:\/\/thenscaler.com\/?p=514\">Part 1<\/a> of this series I demonstrated the <a href=\"https:\/\/thenscaler.com\/?p=514\">basics of current sensing with ACS712 sensors<\/a> and an Arduino microcontroller. In <a href=\"https:\/\/thenscaler.com\/?p=529\">Part 2<\/a>, I demonstrated the<a href=\"https:\/\/thenscaler.com\/?p=529\"> test loop wired for four blocks, occupancy detection and signals<\/a>.<\/p>\n<p>In this post I&#8217;ll put on my programmer&#8217;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 <a href=\"https:\/\/thenscaler.com\/?p=514\">Block Occupancy Detection for DC and DCC, Part 1.<\/a>\u00a0 The rest is new; and I&#8217;m scaling from a single to multiple detectors. I&#8217;ve also changed the way I calculate the occupancy threshold so that it is calibrated for each sensor.<\/p>\n<h2>ACS712 Basics<\/h2>\n<div id=\"attachment_592\" style=\"width: 310px\" class=\"wp-caption alignleft\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-592\" class=\"size-medium wp-image-592\" src=\"https:\/\/thenscaler.com\/wp-content\/uploads\/2015\/12\/Track-power-and-block-detection-connections-1-300x199.jpg\" alt=\"Current Sensors and Block Feeder Connections.\" width=\"300\" height=\"199\" srcset=\"https:\/\/thenscaler.com\/wp-content\/uploads\/2015\/12\/Track-power-and-block-detection-connections-1-300x199.jpg 300w, https:\/\/thenscaler.com\/wp-content\/uploads\/2015\/12\/Track-power-and-block-detection-connections-1.jpg 576w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><p id=\"caption-attachment-592\" class=\"wp-caption-text\">ACS712 Current Sensors and Block Feeder Connections on the Test Loop.<\/p><\/div>\n<p>The basic concepts for working with an ACS712 sensor board are discussed in\u00a0 <a href=\"https:\/\/thenscaler.com\/?p=514\">Block Occupancy Detection for DC and DCC, Part 1<\/a>, but lets review them here:<\/p>\n<p>&#8211;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.<\/p>\n<p>&#8211;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 <strong>adc_zero<\/strong>. In addition, I now calculate the average quiescent current reading\u00a0 (<strong>AQC<\/strong>) at adc_zero; this value is used to calculate the occupancy detection threshold as explained below.<\/p>\n<p>&#8211;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&#8217;s the formula:<\/p>\n<p style=\"text-align: center;\">CURRENT (in mA) = (RAW_VALUE\u00a0 &#8211; ADC_ZERO) \/ SENSITIVITY<\/p>\n<p>&#8211;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 <a href=\"https:\/\/en.wikipedia.org\/wiki\/Root_mean_square\" target=\"_blank\">Root Mean Square<\/a> of the data set is calculated to arrive at an rms current reading.<\/p>\n<h2>Determining Occupancy<\/h2>\n<p>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&#8217;s pin and adc_zero are passed as arguments.<\/p>\n<pre>float readCurrent(int PIN, float adc_zero)\r\n{\r\n\u00a0 float currentAcc = 0;\r\n\u00a0 unsigned int count = 0;\r\n\u00a0 unsigned long prevMicros = micros() - sampleInterval;\r\n\u00a0 while (count &lt; numSamples)\r\n\u00a0 {\r\n\u00a0\u00a0\u00a0 if (micros() - prevMicros &gt;= sampleInterval)\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 float adc_raw = (float) analogRead(PIN) - adc_zero;\r\n      \/\/ convert to amperes\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 adc_raw \/= SENSITIVITY;\r\n      \/\/ accumulate the sum of the squares of the readings\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 currentAcc += (adc_raw * adc_raw);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 ++count;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 prevMicros += sampleInterval;\r\n\u00a0\u00a0\u00a0 }\r\n\u00a0 }\r\n\u00a0 \/\/https:\/\/en.wikipedia.org\/wiki\/Root_mean_square\r\n  \/\/ square root of the sum of the squares \/ number of samples\r\n\u00a0 float rms = sqrt((float)currentAcc \/ (float)numSamples);\r\n\u00a0 return rms;\r\n}<\/pre>\n<p>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.<\/p>\n<pre>bool isOccupied(int block, float adc_zero, float threshold) {\r\n\u00a0 float current = readCurrent(block, adc_zero);\r\n\u00a0 return (current &gt; threshold);\r\n}<\/pre>\n<h2>Sampling Parameters<\/h2>\n<p>Here are the key sampling parameters I&#8217;m currently using.<\/p>\n<pre>\/\/ constant variables for block occupancy detection\r\n\r\n\/\/ sample over 58 ms; 100 times the nominal pulse width for binary 1 in DCC\r\n\/\/ sample time is expressed in microseconds\r\nconst unsigned long sampleTime = 58000UL;\r\n\r\n\/\/ number of samples taken during a read cycle \r\nconst unsigned long numSamples = 200UL;\r\n\r\n\/\/ ADC conversion time is about 50\u00b5s (+\/-)\r\n\/\/ the sampling interval-here 290\u00b5s-should be longer than then ADC conversion time \r\nconst unsigned long sampleInterval = sampleTime \/ numSamples;<\/pre>\n<p>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&#8217;ve only just begun to play with the variables and see how time efficient I can make the detection process.<\/p>\n<h2>Managing Block Data<\/h2>\n<p>I should back up a bit and explain my data handling strategy.\u00a0 I represent each block with this data structure:<\/p>\n<pre>typedef struct BLOCK_DEF {\r\n  int pin;\r\n\u00a0 int aqv;\r\n\u00a0 float aqc;\r\n\u00a0 bool occ;\r\n\u00a0 bool chg_pending;\r\n};<\/pre>\n<p>Each block is a sensor, so the first element is its pin assignment, and the next two elements (aqv &amp; aqc) of the structure are the calibration data for the sensor calculated at startup.\u00a0 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).<\/p>\n<p>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:<\/p>\n<pre>BLOCK_DEF blocks[NUM_BLOCKS] = {\r\n\u00a0\u00a0\u00a0 {0, 0, 0, false, false},\r\n\u00a0\u00a0\u00a0 {1, 0,0, false, false},\r\n\u00a0\u00a0\u00a0 {2, 0, 0, false, false},\r\n\u00a0\u00a0\u00a0 {3, 0, 0, false, false}}; \r\n\u00a0\u00a0 \u00a0\r\nBLOCK_DEF master = {4, 0, 0, false, false};<\/pre>\n<h2>Calibration<\/h2>\n<p>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.<\/p>\n<pre>int determineVQ(int PIN) {\r\n\u00a0 float VQ = 0;\r\n\u00a0 \/\/read a large number of samples to stabilize value\r\n\u00a0 for (int i = 0; i &lt; CALIBRATION_READS; i++) {\r\n\u00a0\u00a0\u00a0 VQ += analogRead(PIN);\r\n\u00a0\u00a0\u00a0 delayMicroseconds(100);\r\n\u00a0 }\r\n\u00a0 VQ \/= CALIBRATION_READS;\r\n\u00a0 return int(VQ);\r\n}\r\n\r\nfloat determineCQ(int pin, float aqv) {\r\n\u00a0 float CQ = 0;\r\n\u00a0 \/\/ set reps so the total actual analog reads == CALIBRATION_READS\r\n\u00a0 int reps = (CALIBRATION_READS \/ numSamples);\r\n\u00a0 for (int i = 0; i &lt; reps; i++) {\r\n\u00a0\u00a0\u00a0 CQ += readCurrent(pin, aqv);\r\n\u00a0 }\r\n\u00a0 CQ \/= reps;\r\n\u00a0 return CQ;\r\n}<\/pre>\n<p>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.<\/p>\n<p>Here&#8217;s the calibration procedure from setup():<\/p>\n<pre>  master.aqv = determineVQ(master.pin);\r\n\u00a0 master.aqc = determineCQ(master.pin, master.aqv);\r\n\u00a0 \r\n\u00a0 for (i = 0; i &lt; NUM_BLOCKS; i++) { \/\/ for each block \r\n\u00a0\u00a0\u00a0 blocks[i].aqv = determineVQ(blocks[i].pin);\r\n\u00a0\u00a0\u00a0 blocks[i].aqc = determineCQ(blocks[i].pin, blocks[i].aqv);\r\n\u00a0 }\r\n<\/pre>\n<p>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.<\/p>\n<p>Instead of using a hard current threshold (<a href=\"https:\/\/thenscaler.com\/?p=514\">like .0259, about 26 mA, as I did in the first demo<\/a>), I now use a multiplier that I apply to the <strong>AQC<\/strong> reading for each sensor to set the detection threshold for that sensor.<\/p>\n<p>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.<\/p>\n<p>Set the multiplier too low, and block states will flicker because of the variance in sensor readings. On the test loop I&#8217;m inhibited because of the Atlas bumper light and its draw on track power on block 3. I&#8217;m able to bring the multiplier down to 1.095; below that block 3 flickers mercilessly.\u00a0 But the other blocks do not, so I think even lower multipliers and higher sensitivities are possible. I&#8217;ll find out on the L&amp;NC.<\/p>\n<h2>The Detection Cycle<\/h2>\n<p>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&#8217;s occupancy state is changed during the cycle, a notification is sent out to &#8220;subscribers,&#8221; other devices that have asked to receive block occupancy data. That is how my <a href=\"https:\/\/thenscaler.com\/?p=414\">control panel<\/a> gets updated.<\/p>\n<pre>\u00a0 power_state = isOccupied(master.pin, master.aqv, \r\n           master.aqc * DETECTION_MULTIPLIER, master.chg_pending);\r\n\u00a0 if(power_state != master.occ) {\r\n\u00a0\u00a0\u00a0 if(master.chg_pending) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 master.occ = power_state;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 master.chg_pending = false;\r\n\u00a0\u00a0\u00a0 } else {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 master.chg_pending = true;\r\n\u00a0\u00a0\u00a0 }\r\n\u00a0 } else {\r\n\u00a0\u00a0\u00a0 master.chg_pending = false;\r\n\u00a0 }\r\n\u00a0 if(master.occ){\r\n\u00a0\u00a0\u00a0 for (int i = 0; i &lt; NUM_BLOCKS; i++) {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 block_state = isOccupied(blocks[i].pin, blocks[i].aqv, \r\n               blocks[i].aqc * DETECTION_MULTIPLIER, false);\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 if (block_state != blocks[i].occ) { \/\/ if occupancy state has changed\r\n      \/\/ if a pending change has been previously detected\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 if(blocks[i].chg_pending &amp;&amp; !master.chg_pending) {\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 blocks[i].occ = block_state;\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 blocks[i].chg_pending = false;\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 String id = String(i);\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 notifySubscribers(1, id, String(block_state));\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 } else { \/\/ initial change detection\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 blocks[i].chg_pending = true;\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0 \u00a0 \u00a0} else {\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 blocks[i].chg_pending = false; \u00a0\u00a0\u00a0\u00a0\u00a0 \r\n       }\r\n \u00a0\u00a0 } \r\n\u00a0 }<\/pre>\n<p>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&#8217;s state to flicker.<\/p>\n<p>My current (pardon the pun) solution is to implement a system rule requiring 2 consecutive readings to confirm a block state change.\u00a0 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.<\/p>\n<h2>What&#8217;s Next?<\/h2>\n<div id=\"attachment_658\" style=\"width: 233px\" class=\"wp-caption alignleft\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-658\" class=\"size-medium wp-image-658\" src=\"https:\/\/thenscaler.com\/wp-content\/uploads\/2016\/01\/UP995-at-Signal-34-223x300.jpg\" alt=\"Dual Searchlight Signals, Scratch Made with BLMA Signal Heads.\" width=\"223\" height=\"300\" srcset=\"https:\/\/thenscaler.com\/wp-content\/uploads\/2016\/01\/UP995-at-Signal-34-223x300.jpg 223w, https:\/\/thenscaler.com\/wp-content\/uploads\/2016\/01\/UP995-at-Signal-34-768x1033.jpg 768w, https:\/\/thenscaler.com\/wp-content\/uploads\/2016\/01\/UP995-at-Signal-34-762x1024.jpg 762w, https:\/\/thenscaler.com\/wp-content\/uploads\/2016\/01\/UP995-at-Signal-34.jpg 946w\" sizes=\"auto, (max-width: 223px) 100vw, 223px\" \/><p id=\"caption-attachment-658\" class=\"wp-caption-text\">Dual Searchlight Signals, Scratch Made with BLMA Signal Heads.<\/p><\/div>\n<p>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<a href=\"https:\/\/thenscaler.com\/?p=529\"> I used on the test loop as a way of showing occupancy detection in action<\/a>. That can be done through JMRI, or as part of the layout&#8217;s intrinsic Arduino processes which is they way I&#8217;m approaching it.<\/p>\n<p>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&#8217;t matter how you run your trains: if you want signals then you should have them!<\/p>\n<p>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.<\/p>\n<p>Here&#8217;s a hint: running signals the Arduino way works best when the signals are tied together in a physical and logical chain.<\/p>\n<p>That, fellow railroaders, is the subject for another post.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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&#8217;ll put on my programmer&#8217;s hat and talk about the code behind block occupancy detection on [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":592,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[35,21,14],"tags":[46,22,47,39],"class_list":["post-604","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-electronics","category-layout-control","category-test-loop","tag-acs712-sensor","tag-arduino","tag-block-occupancy-detection","tag-programming"],"_links":{"self":[{"href":"https:\/\/thenscaler.com\/index.php?rest_route=\/wp\/v2\/posts\/604","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/thenscaler.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/thenscaler.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/thenscaler.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/thenscaler.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=604"}],"version-history":[{"count":25,"href":"https:\/\/thenscaler.com\/index.php?rest_route=\/wp\/v2\/posts\/604\/revisions"}],"predecessor-version":[{"id":683,"href":"https:\/\/thenscaler.com\/index.php?rest_route=\/wp\/v2\/posts\/604\/revisions\/683"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/thenscaler.com\/index.php?rest_route=\/wp\/v2\/media\/592"}],"wp:attachment":[{"href":"https:\/\/thenscaler.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=604"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/thenscaler.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=604"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/thenscaler.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=604"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}