tag:blogger.com,1999:blog-40921934179286900792024-03-19T11:37:45.385+08:00On the Drawing Boardadventures in electronic circuit designerebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.comBlogger134125tag:blogger.com,1999:blog-4092193417928690079.post-27195190702562675502016-03-25T21:23:00.001+08:002016-04-07T19:35:10.781+08:00Review of some Android scientific calculator appsJust a cursory review of scientific calculators apps that have caught my fancy thus far<br />
<br />
<h3>
<a href="http://www.quartic-software.co.uk/">RealCalc</a></h3>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWR3SbgrzfqUARqf26f3zKyHGqLMO7LJ2cT0TlmHa7ICh-5y1yP-xz01j_NSlEnGG6fs0pCbkCuVj_sxunbo65P8a1TiyO36_KrjdHgVeshi0lpGrjc6_sVOnuebBdJDomkyOnzXD_xuY/s1600/realcalc+plus.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img alt="" border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWR3SbgrzfqUARqf26f3zKyHGqLMO7LJ2cT0TlmHa7ICh-5y1yP-xz01j_NSlEnGG6fs0pCbkCuVj_sxunbo65P8a1TiyO36_KrjdHgVeshi0lpGrjc6_sVOnuebBdJDomkyOnzXD_xuY/s320/realcalc+plus.png" title="RealCalc Plus" width="179" /></a></div>
(Been using this for a few years and so I'm biased toward it and tend to use it as my benchmark)<br />
<br />
Pros:<br />
<ul>
<li>Has RPN mode </li>
<li>In RPN mode, number of registers shown is adjustable just by sliding up/down. Max of 4 on my phone. </li>
<li>binary, hex, octal functions included in free version</li>
<li>customizable unit conversions and constants</li>
</ul>
Cons:<br />
<ul>
<li>No random number generator</li>
<li>Only one RPN style available in free verison, two other options only in paid version</li>
<li>Fractions capability only in paid version</li>
<li>12-digit display is only in the paid version</li>
<li>Doesn't keep a history of numerical operations, only the numerical results</li>
<li>Rectangular / polar conversion not available</li>
<li>one default theme/skin (but it's sufficient with clear and crisp labels on the calculator keys)</li>
</ul>
<br />
<h3>
<a href="http://calctastic.com/">CalcTastic</a></h3>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXlJhoWdWDZbdec00DDlnBVhRr-KXV0CJiBVnGfTNKQKr0rpEl91SsMZLuASE8NgkKYQcW2sWqtlekLKnivPRKbLj73mXQfbozgHnjBKySVCjdb61bEFopPOmmPZIsHxbyqqZF-aY3xFA/s1600/calctastic.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img alt="" border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXlJhoWdWDZbdec00DDlnBVhRr-KXV0CJiBVnGfTNKQKr0rpEl91SsMZLuASE8NgkKYQcW2sWqtlekLKnivPRKbLj73mXQfbozgHnjBKySVCjdb61bEFopPOmmPZIsHxbyqqZF-aY3xFA/s320/calctastic.png" title="CalcTastic" width="179" /></a></div>
Pros:<br />
<ul>
<li>Has RPN mode.</li>
<li>Has random number generator</li>
<li>Can generate random numbers (integers) from 0 to x, where x = any integer</li>
<li>Conversion function (eg., convert from m/s to mph, temp from F to K) has far more units than RealCalc</li>
<li>The numbers and operations being keyed in (before pressing the equals key) is shown on an upper line with the intermediate and final result shown on the lower line.</li>
<li>Keeps a history of the operations (eg. multiplication, log, square root) performed, not just the numerical results of those operations</li>
<li>Stack is accessible even in Algebraic mode</li>
<li>Has rudimentary statistics package</li>
<li>Stack doubles as data storage for statistics package (which includes min, max, mean, median, variance, std deviation)</li>
<li>Can handle complex numbers</li>
<li>User-selectable theme/skin</li>
</ul>
Cons:<br />
<ul>
<li>"CalcTastic"?! Seriously?</li>
<li>Number of lines displayed is fixed. On my phone it's 3 lines of registers in RPN mode and two lines in Algebraic mode </li>
<li>Binary, hex, octal functions only in paid version</li>
<li>Rectangular / polar conversion only in paid version</li>
<li>Fractions capability only in paid version</li>
</ul>
<br />
<h3>
<a href="http://hiperdevelopment.wix.com/hipercalc">Hiper Calculator</a></h3>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg95T5JYeHeGe80YRWvOf5IM02P0dQvVpPVGm4V1JnGj9DDCbhnSbbn2qYUkFUqSqGLas_s8ohnUh1xFHX_ExCNWQZmp7Lr4v26Dhbp9JzCxGEtbHbCbO9EzTR2p8tAUiBGuoM5ngooT1k/s1600/hiper+calculator.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img alt="" border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg95T5JYeHeGe80YRWvOf5IM02P0dQvVpPVGm4V1JnGj9DDCbhnSbbn2qYUkFUqSqGLas_s8ohnUh1xFHX_ExCNWQZmp7Lr4v26Dhbp9JzCxGEtbHbCbO9EzTR2p8tAUiBGuoM5ngooT1k/s320/hiper+calculator.png" title="Hiper Calculator" width="179" /></a></div>
Pros:<br />
<ul>
<li>The number of significant figures makes me giddy: 15 digits on a phone in portrait mode and 32 in landscape mode, with the paid version extending that to 100, and the exponent's from three to nine digits. </li>
<li>Free version already has fractions capabilities</li>
<li>Has random number generator</li>
<li>Keeps a history of the operations (eg. multiplication, log, square root) performed, not just the numerical results of those operations</li>
<li>The numbers and operations being keyed in (before pressing the equals key) is shown on an upper line with the intermediate and final result shown on the lower line.</li>
<li>Has an Engineering SI mode! It will, for example, show 10,000 as 10 k, 5.7 million as 5.7 M, 0.000764 as 764 u [symbol for micro]. Quite useful for electronics designers who have to deal with capacitances in picofarads, nanofarads, microfarads, resistances in kilohms and megaohms, currents in microamps and milliamperes. It just isn't natural for them to say, "I need a 2.2 million ohm resistor" or "1 x 10<sup>-9</sup> ceramic capacitor."</li>
<li>Binary, hex, octal functions included in free version</li>
<li>User-selectable theme/skin </li>
</ul>
Cons:<br />
<ul>
<li>No RPN mode (heartbreaking!)</li>
<li>For trigonometric functions and logarithms, among others, Hiper uses infix notation, ie., you have to press, for instance the "cos" key before entering the angle value. Am more used to postfix notation for these functions even in non-RPN calculators. </li>
<li>Rectangular / polar conversion not available</li>
</ul>
<div>
<br /></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<h3>
<a href="http://www.stultusstudios.com/">Natural Scientific Calculator</a><br />
<a href="https://play.google.com/store/apps/details?id=org.solovyev.android.calculator&hl=en">Calculator++</a><div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcF6m5O5AEjxNvki4cgauLVUqt-kT10U9JYRPcX1XAktsRwwZVsfgSGRGhjuK_taAC4RImgwPfg4OEj-LKkG96bcTiLM83qheUCrTqadccTP8r11B-UYEipLz3ICzWtu4Ysv-sUTf07RE/s1600/natural+scientific+calculator.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="" border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcF6m5O5AEjxNvki4cgauLVUqt-kT10U9JYRPcX1XAktsRwwZVsfgSGRGhjuK_taAC4RImgwPfg4OEj-LKkG96bcTiLM83qheUCrTqadccTP8r11B-UYEipLz3ICzWtu4Ysv-sUTf07RE/s200/natural+scientific+calculator.png" title="Natural Scientific Calculator" width="112" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAdQ2YsBDqMDpc4M8zgAdWhU3lrh8JfmAlCKDVQ4hyphenhyphenZcnSFQLOpusXaL4aBr0w0MoThJdA6vORu0YESsEWw3xjrMBojypR-gg7r4zPIILKnKjoSQq1ewn0dlUwV6MqYGaNrUBSBMMSe8g/s1600/calculator%252B%252B.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="" border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAdQ2YsBDqMDpc4M8zgAdWhU3lrh8JfmAlCKDVQ4hyphenhyphenZcnSFQLOpusXaL4aBr0w0MoThJdA6vORu0YESsEWw3xjrMBojypR-gg7r4zPIILKnKjoSQq1ewn0dlUwV6MqYGaNrUBSBMMSe8g/s200/calculator%252B%252B.png" title="Calculator++" width="112" /></a></div>
</h3>
These two are aesthetically pleasing, but using them is driving me up the wall. Still trying to get on the learning curve. Neither has RPN mode, as far as I can tell.<br />
<h3>
</h3>
<h3>
<a href="https://play.google.com/store/apps/details?id=b4a.configulator_calculator&hl=en">Configulator</a></h3>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrV1WKRBrNHjjBC9clXvBjfvahN81IN4MqN7OVi3GAxOfGCRFM7E1ZD0UiTQARemIwIM2IBH6nB-aUDoV5LmdgTwmVeC7grzKElIynPuuVn0XXUCXFKNqgQHJ6WAOYBP6Bxzrn8mKR4_c/s1600/configulator.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="" border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrV1WKRBrNHjjBC9clXvBjfvahN81IN4MqN7OVi3GAxOfGCRFM7E1ZD0UiTQARemIwIM2IBH6nB-aUDoV5LmdgTwmVeC7grzKElIynPuuVn0XXUCXFKNqgQHJ6WAOYBP6Bxzrn8mKR4_c/s200/configulator.png" title="Configulator" width="112" /></a></div>
Looks absolutely terrible; it's hideous. And I still have to learn how to program those 15 function keys. Those user definable keys are the only reason I even bothered looking into this calculator.<br />
<br />
<h3>
Conclusion ... for now</h3>
Between RealCalc and CalcTastic, the latter wins in terms of number of functions. But I'd rather have the look, fonts, key location, layout of RealCalc (must be because of my years of familiarity with it). Now if only CalcTastic could add an Engineering SI mode and extend its signficant figures to something like 50, it would be the winner by a light year.erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com10tag:blogger.com,1999:blog-4092193417928690079.post-25802006839572964892013-04-14T00:29:00.003+08:002013-04-14T00:32:06.919+08:00Made by monkeys: female headers2-mm pitch female headers from--where else but--China. The gold-colored pins have been unwittingly engineered such that they wick up solder so efficiently that solder goes all the way up the header and effectively clogs the portion into which male header pins plug into.<br />
<br />
I had to desolder the female headers on that red USB adapter board because of this problem. And just my luck the female headers I bought are the very same type installed by the manufacturer on their board! So soldering the new headers was anxiety-provokingly fiddly.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOOacApFIaS8LHUG8sWCfE10t-pXPrPzm3aOkHXPnmAi1gl-W66RR0xcaKCtA43pwAXiMBLJ8RVG6R2umUeSVg9Yf_BMccZbIHmFMUwjLs-IZTAX6OaPCOCRM92nFVIeb7St0-sCKaqKw/s1600/IMG_2858.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="266" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOOacApFIaS8LHUG8sWCfE10t-pXPrPzm3aOkHXPnmAi1gl-W66RR0xcaKCtA43pwAXiMBLJ8RVG6R2umUeSVg9Yf_BMccZbIHmFMUwjLs-IZTAX6OaPCOCRM92nFVIeb7St0-sCKaqKw/s400/IMG_2858.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The gaps within each pin of the female header allow solder to wick into the inner recesses and clog the socket, thus rendering the header unusable--boards with male headers cannot be plugged in</td></tr>
</tbody></table>
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgumIIVeo_FUYsb65zhLwylVLQ7Rvp6QSHgNRRA-4AzIYfRmOEQwgs44Ig7W23qDz9tz3-Su4AIcWe6eMIvsCLErBuNFy-zlZnt349GMSVQZt8B7y5nl6SFu8NTiwU-k-fbl4GH6UwXGSc/s1600/IMG_2866.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="266" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgumIIVeo_FUYsb65zhLwylVLQ7Rvp6QSHgNRRA-4AzIYfRmOEQwgs44Ig7W23qDz9tz3-Su4AIcWe6eMIvsCLErBuNFy-zlZnt349GMSVQZt8B7y5nl6SFu8NTiwU-k-fbl4GH6UwXGSc/s400/IMG_2866.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">A new 2-mm female header just soldered in place</td></tr>
</tbody></table>
<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_QljKvVKqzlqLAYMnMTpc6EU093d-oaI8xXcJ5OMo0IAeyt0_Dmbr3xiuC0DxMs_llDeb7bKqyfo8Mb_zoVH44T3ldVfKshw47nPHXqGlZRgnKPfomDPzfX02HlATFpvCM2-N5H_enWI/s1600/IMG_2870.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_QljKvVKqzlqLAYMnMTpc6EU093d-oaI8xXcJ5OMo0IAeyt0_Dmbr3xiuC0DxMs_llDeb7bKqyfo8Mb_zoVH44T3ldVfKshw47nPHXqGlZRgnKPfomDPzfX02HlATFpvCM2-N5H_enWI/s400/IMG_2870.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">In contrast is this 2.54-mm (0.1") female header</td></tr>
</tbody></table>
<br />erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com1tag:blogger.com,1999:blog-4092193417928690079.post-16456521752600834012012-07-01T19:02:00.000+08:002012-07-01T19:11:58.747+08:00Melody door chime with "dead time"Here's a very similar circuit and firmware to the <a href="http://electromotiveforces.blogspot.com/2012/07/door-opened-closed-and-left-open-too.html" target="_blank">door open/closed detector</a>. However, instead of a magnetic contact (magnetic proximity switch) triggering the MCU to play the melody, it's an ordinary bell push for doorbells. <br />
<br />
The "dead time" in the title refers to the time period after the melody is played when button presses are ignored. This discourages (but unfortunately does not prevent) pranksters (usually kids) from playing with the door chime. Also prevents impatient visitors/mailmen/couriers/extraterrestrials from making the chime go off continuously to put you into panic mode. Moreover, the circuit cannot be fooled by taping down the button in the hopes of forcing the circuit to keep playing the tune since the microcontroller only responds to edge triggering. <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi7Iu3jywq3K-CMCuPPxpL9W8R3cOSRa8K3GhOmn_GqnDkUXjfdVxwVvPlJ9seivOY1ccscnsZKbfxNrer8YYYT3ZIXAvdbf3pld3dTbXY_1oAYtMefw6uD6wcbfXG3zCzcjnoA9YrUx2M/s1600/door-chime-schem.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi7Iu3jywq3K-CMCuPPxpL9W8R3cOSRa8K3GhOmn_GqnDkUXjfdVxwVvPlJ9seivOY1ccscnsZKbfxNrer8YYYT3ZIXAvdbf3pld3dTbXY_1oAYtMefw6uD6wcbfXG3zCzcjnoA9YrUx2M/s320/door-chime-schem.png" width="320" /></a></div>
VR1 = L7812 +12V voltage regulator 1000mA<br />
VR2 = 78L05ACZ +5V voltage regulator 100mA<br />
MCU = Microchip <a href="http://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en020030" target="_blank">PIC10F202</a> microcontroller<br />
PB = bell push<br />
OC1 = <a href="http://www.avagotech.com/pages/en/optocouplers_plastic/plastic_general_purpose_phototransistor_optocoupler/hcpl-817-000e/" target="_blank">Avago HCPL-817</a> optoisolator<br />
A1 = S9013 NPN transistor <br />
BR = W01M 100V 1.5A bridge rectifier<br />
XF = 220VAC to 6VAC transformer<br />
SPKR = 8-ohm, 250mW, 3-inch speaker<br />
<br />
The speaker is powered with 12VDC to significantly increase the output volume. As with the door status detector, I used an old computer speaker. PB is located several meters away from the circuit board. The internal weak pull-ups of the MCU are used so no external resistors are necessary on the collector of the optotransistor. I installed this circuit over three years ago and thus far I've not had any problems. Still working like a charm. <br />
<br />
See the <a href="http://electromotiveforces.blogspot.com/2012/07/door-opened-closed-and-left-open-too.html" target="_blank">door open/closed detector</a> for the principle of operation of the melody playing routines. Debounce routine works by storing the last eight readings in one 8-bit variable. Pushbutton
is deemed not bouncing if the the variable is equal to 0x00 or 0xFF. Any
other value means the switch is bouncing. Upon detection of a
falling edge MCU plays the melody just once. To implement the dead time a simple one-second delay routine is called several times (depending on how many seconds is specified in the constant <code>secdelay</code>) after playing the tune and before handing back control to the pushbutton polling routine. When the melody is being played and during the dead time PB is not polled therefore button pushes are ignored. Because there's only one timer in the PC10F202, timer 0 is used for both playing the
notes and for polling of the switches. The prescale values are different
for the two applications and so <code>OPTION</code> register is edited accordingly when switching between the two.<br />
<br />
<a href="http://electromotiveforces.blogspot.com/2010/06/macros-and-defines-for-pic-baseline-and.html">et.inc</a><span id="goog_782904966"></span><span id="goog_782904967"></span> contains various defines and macro. <br />
<br />
<br />
<pre>; ********************************************************************************
;
; DOOR CHIME
; February 2009
;
; When bell push is momentarily pressed a melody is played.
; After the melody has finished there is a delay of 15 seconds when PB presses are ignored.
; Because PB is not polled when melody is playing and during the delay,
; repeatedly pressing PB during this time does not result in any action.
; Since the circuit responds only to edge triggering keeping the bell push depressed
; will not result in any further action.
;
; MCU has debounce firmware that monitors bell push switch readings. Upon detection of a falling edge
; MCU plays a sequence of musical notes. Firmware does not react to a rising edge.
;
; MCU weak pull-ups are enabled in firmware.
; Watchdog timer is activated. WDT nominal time-out according to datasheet is 18ms (without prescaler)
;
; ********************************************************************************
processor 10F202
#include p10F202.inc
#include et.inc
__config _MCLRE_OFF & _CP_OFF & _WDT_ON
cblock 0x08
CYCLES ; determines how long the note sounds; value of 4 makes the note sound for approx 0.3sec
LENGTH:2 ; time the note will play
NOTE ; note's T/2 in terms of TMR0 initial value
PB_VAL ; each bit is a push button switch reading; bit0 has the latest reading; bits are shifted left during each TMR0 rollover
FEN ; assortment of flag and enable bits for various purposes
COUNTER:3 ; loop counters
COUNTER2:2 ; loop counters
endc
; ********************************************************************
; comment out when not debugging
; #define debug
; ********************************************************************
ifdef debug
#define secdelay .1
else
#define secdelay .15 ; number of seconds after melody has ended before bell push can be pressed again for the chime to play again
endif
#define spkr GPIO,2 ; output speaker via NPN transistor
#define pb GPIO,0 ; input from bell push
; macro for playing the note
; _note is the note's T/2 (half period)
; _length is the number of times the note is played
key macro _note, _length
movlf _note, NOTE
movlf _length, LENGTH
call PlayNote
endm
; see wikipedia article "scientific pitch notation" for table of frequency values of notes
; B4 = ti = 493.88 Hz, period = 2.0248 ms, T/2 = 1.0124 us
; C5 = do = 523.25 Hz, period = 1.9111 ms, T/2 = 955.57 us
; D5 = re = 587.33 Hz, period = 1.7026 ms, T/2 = 851.31 us
; E5 = mi = 659.26 Hz, period = 1.5168 ms, T/2 = 758.43 us
; F5 = fa = 698.46 Hz, period = 1.4317 ms, T/2 = 715.86 us
; G5 = sol = 783.99 Hz, period = 1.2755 ms, T/2 = 637.76 us
; A5 = la = 880.00 Hz, period = 1.1364 ms, T/2 = 568.18 us
; B5 = ti = 987.77 Hz, period = 1.0124 ms, T/2 = 506.19 us
; C6 = do = 1046.5 Hz, period = 955.57 us, T/2 = 477.78 us
; D6 = re = 1174.7 Hz, period = 851.28 us, T/2 = 425.64 us
; E6 = mi = 1318.5 Hz, period = 758.44 us, T/2 = 379.22 us
; given internal clock = 4Mhz and timer0 prescaler = 1:4
; timer zero rolls over to zero every 1microsecond x 4 x TMR0 steps
; TMR0 steps = (T/2) / (1 us x 4)
; TMR0 initial value = 256 - TMR0 steps
; the following values should be loaded into TMR0 in order to produce the above notes, respectively
#define B4 .256 - .253
#define C5 .256 - .239
#define D5 .256 - .213
#define E5 .256 - .190
#define F5 .256 - .179
#define G5 .256 - .159
#define A5 .256 - .142
#define B5 .256 - .127
#define C6 .256 - .119
#define D6 .256 - .106
#define E6 .256 - .95
; in the PlayNote subroutine LENGTH determines how many times the note will play.
; since T/2 for every note is different LENGTH must be different in order for each note to play for the same length of time
; LENGTH is computed as follows: L1 x T1 = L2 x T2
; where L1 = length note 1, T1 = period of note 1, L2 = length of note 2, T2 = period of note2
; arbitrarily set LENGTH for E6 = 200 (leave room for a few more higher notes)
#define LB4 .75
#define LC5 .79
#define LD5 .89
#define LE5 .100
#define LF5 .106
#define LG5 .119
#define LA5 .133
#define LB5 .150
#define LC6 .159
#define LD6 .178
#define LE6 .200
#define pb_level FEN,0 ; voltage level: 1 = hi, 0 = low
#define option_poll_sw b'10000011' ; value to copy to OPTION register when polling switches
; pullups enabled, prescaler to timer0, prescaler = 1:16
#define option_chime b'10000001' ; value to copy to OPTION register in chime routine
; pullups enabled, prescaler to timer0, prescaler = 1:4
#define option_no_wpu b'11000011' ; value to copy to OPTION register when polling switches but with weak pull-ups off
#define tmr_init_val .11 ; initial value for timer0, given 4Mhz clock and prescaler 1:16, TMR0 will rollover every 3.92ms
org 0x0
movwf OSCCAL ; calibrate the chip
goto initialize
; =======================================================================================
; subroutines
;
; 10F202 can only call subroutines if they are located in first 256 memory locations
; =======================================================================================
; ---------------------------------------------------------------------------------
; Notes are played by switching the speaker on and off at the frequency of the note.
; the following routine also plays the note for a specified time as determined by LENGTH and CYCLES
PlayNote:
movff NOTE, TMR0
movff LENGTH, LENGTH+1
movff CYCLES, COUNTER
bsf spkr
loop_note
dnxnotzero TMR0
goto loop_note
movff NOTE, TMR0
toggle spkr
clrwdt
decfsz LENGTH+1,f
goto loop_note
movff LENGTH, LENGTH+1
decfsz COUNTER,f
goto loop_note
bcf spkr
return
; ---------------------------------------------------------------------------------
; ---------------------------------------------------------------------------------
OptionChime:
movlw option_chime
option
return
; ---------------------------------------------------------------------------------
; ---------------------------------------------------------------------------------
OptionPollSw:
movlw option_poll_sw
option
return
; ---------------------------------------------------------------------------------
; ---------------------------------------------------------------------------------
; approximately one second delay
Delay_onesec:
clrf COUNTER
clrf COUNTER+1
movlf .5, COUNTER+2
delay1sec_loop
decfsz COUNTER,f
goto delay1sec_loop
clrwdt
decfsz COUNTER+1,f
goto delay1sec_loop
decfsz COUNTER+2,f
goto delay1sec_loop
return
; ---------------------------------------------------------------------------------
; ---------------------------------------------------------------------------------
; the following melody is played when bell push is pressed (falling edge detected)
SoundChime:
call OptionChime
movlf .4, CYCLES
; melody from the movie Close Encounters of the Third Kind
; --------------------------------------------------------
key D6, LD6
key E6, LE6
key C6, LC6
key E5, LE5
key G5, .255
; time interval when chime has already finished playing
; and button presses are ignored
movlf secdelay, COUNTER2
call Delay_onesec
decfsz COUNTER2,f
goto $-2
call OptionPollSw
return
; ---------------------------------------------------------------------------------
; =======================================================================================
; main program
; =======================================================================================
; ---------------------------------------------------------------------------------
initialize:
movlw b'1'
tris GPIO
clrwdt
call OptionPollSw
clrf GPIO ; speaker off
movlf 0xFF, PB_VAL ; all bits hi, pb contacts considered open
bsf pb_level ; level = hi, pb contacts considered open
movlf tmr_init_val, TMR0
; ---------------------------------------------------------------------------------
main:
poll_timer0:
dnxnotzero TMR0 ; poll TMR0 until it rolls over
goto poll_timer0
movlf tmr_init_val, TMR0
clrwdt
poll_timer0_end:
; poll and check bell push; store value in 8-bit record
; if PB_VAL = 0xFF AND pb_level = 0 then let pb_level = 1, this is a rising edge detect, do nothing
; if PB_VAL = 0x0 AND pb_level = 1 then let pb_level = 0, this is a falling edge detect and chime is sounded
; if PB_VAL > 0 AND PB_VAL < 0xFF then switch is bouncing, so do nothing
; if PB_VAL = 0xFF AND pb_level = 1 then there is no level change, so do nothing
; if PB_VAL = 0 AND pb_level = 0 then there is no level change, so do nothing
check_pb:
rlf PB_VAL,f ; discard oldest switch reading
bcf PB_VAL,0 ; carry bit will be moved into bit zero so clear bit0
dnxbit1 pb
bsf PB_VAL,0
dnxeqlit PB_VAL, 0xFF
goto all_bits_hi
dnxzero PB_VAL
goto all_bits_lo
goto check_pb_end
all_bits_hi:
dnxbit1 pb_level
goto check_pb_end
bsf pb_level ; rising edge detected
goto check_pb_end
all_bits_lo:
dnxbit0 pb_level
goto check_pb_end
bcf pb_level ; falling edge detected
call SoundChime
check_pb_end:
goto main
; ---------------------------------------------------------------------------------
end
</pre>
<br />erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com0tag:blogger.com,1999:blog-4092193417928690079.post-44393771597289694422012-07-01T04:07:00.000+08:002012-07-01T12:14:08.442+08:00Door opened, closed, and left open too long detector with corresponding melodies for each conditionThe musical candle brought to mind a couple of circuits I designed and built back in 2008 that play a melody when the room door is opened, closed, and left open for too long. They were installed in my sister's home and dad's office. The two circuits vary slightly in that one of them is tamper-proof--cutting the wires coming out of the magnetic contact (switch) will activate the music.<br />
<br />
To sense whether the door is open or closed the circuits rely on a common type of magnetic proximity switch called magnetic contact. It's widely used in home security systems. They come in pairs with one block containing a magnet and the other housing a reed switch. The switch may be normally open or normally closed or may be a single-pole double-throw type. In the circuits below I use an NO and an NC type. Check out <a href="http://www.security.honeywell.com/hsc/documents/OmincatIN.pdf" target="_blank">Honeywell's magnetic contacts catalog</a> to see a wide variety of models. The ones I used had no brand and may have been made in Taiwan.<br />
<br />
The circuits that were installed some four years ago: <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEifkIvYv3vKgFkPHsxioQwfKJE5muHW-46bC_B1GgxShr4KZx7znzRhCrXbNDCXOlnuMSPw59wrqr2IT6ppJWixquRL19mBrVt2I83TJ2S1aQPFISJY4Ec08wvRVnH2qPCWRlCAqCNO4iw/s1600/door-open-home-schem.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEifkIvYv3vKgFkPHsxioQwfKJE5muHW-46bC_B1GgxShr4KZx7znzRhCrXbNDCXOlnuMSPw59wrqr2IT6ppJWixquRL19mBrVt2I83TJ2S1aQPFISJY4Ec08wvRVnH2qPCWRlCAqCNO4iw/s320/door-open-home-schem.png" width="320" /></a></div>
VR = L7805 +5V voltage regulator 1000mA<br />
MCU = Microchip PIC10F202 microcontroller<br />
OPTO1, OPTO2 = Avago HCPL-817 optoisolator<br />
RS = reed switch (magnetic contact)<br />
Q1 = S9013 NPN transistor<br />
SPKR = 8-ohm. 2-watt, 3-inch speaker <br />
<br />
RS and magnet are fastened to the door jamb and door, respectively. When door is closed, magnet actuates RS and its contacts close. This shunts current to ground. When door is opened, RS
contacts open, and current passes through LED of OPTO1 causing optotransistor to turn on. MCU then senses a low on its input pin. With this setup cutting either of the wires to RS will cause OPTO1 to be energized leading to the MCU playing the melody that indicates the door has just been opened. The drawback of this configuration is that it's always dissipating power. To minimize consumption R1 should be as large as possible but should still be able to supply sufficient current to OPTO1's LED to turn the optotransistor on. <br />
<br />
MCU weak pull-ups are enabled in firmware so no external pull-up
resistors are necessary on the optotransistors. GP3 is also the MCLR pin and is very sensitive
to transients. So pcb track length to the optoisolator should be kept as
short as possible.<br />
<br />
R3 potentiometer allows adjustment of speaker volume to desired level.<br />
<br />
When the door has been left open for a certain amount of time, MCU will
start playing a different melody. The tune will be played over and over with a predetermined interval between plays until the door
is closed, or until PB is momentarily pressed. PB prevents/stops the melody from
playing (closing the door resets the variables and the "door has been left open" melody will play again if the door is left open once more). Firmware is such that RS switching
and PB presses are detected and processed only when no melody is playing. <br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0nb9bh9mr1TnOyYLAxlo6PF8X4NW4vsA4kAfDwWKyAO8-hcwe8IFDga1Q2FbHHqozyCP38gQ_9E5BK3Qol8gRhnsh2e-s29n41WG4lExkqqu8IzX1WWT2BR3gKHjCmJDO4FmWdmGZqMU/s1600/door-open-office-schem.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0nb9bh9mr1TnOyYLAxlo6PF8X4NW4vsA4kAfDwWKyAO8-hcwe8IFDga1Q2FbHHqozyCP38gQ_9E5BK3Qol8gRhnsh2e-s29n41WG4lExkqqu8IzX1WWT2BR3gKHjCmJDO4FmWdmGZqMU/s320/door-open-office-schem.png" width="320" /></a></div>
VR = L7805 +5V voltage regulator 1000mA<br />
MCU = Microchip PIC10F202 microcontroller<br />
OPTO1 = Avago HCPL-817 optoisolator<br />
RS = reed switch (magnetic contact)<br />
Q1 = 2N7000 MOSFET transistor<br />
SPKR = 8-ohm. 250-mW, 2-inch speaker<br />
<br />
In the above circuit (labeled "office circuit"), RS is open when magnet is in close proximity (door closed). This cuts off current to the optoisolator and therefore optotransistor is off. RS contacts close when the door is opened. <br />
<br />
As for PB you may be wondering why I chose to use a normally closed momentary contact pushbutton. I actually can't recall exactly. It now strikes me as an odd (and bad) choice. It could've been due to nonavailability at that time of a normally open button. As you will see in the firmware I ran into problems with this set-up. The 5.1K pull-down resistor was too high and the internal pull-ups have to be disabled while GP3 is being read. Rather than having a pull-down resistor I should've just used the internal pull-up and connected the normally-closed PB to ground (instead of V<sub>DD</sub>). This would of course have necessitated changes in firmware--PB would now be deemed pressed if a high was sensed.<br />
<br />
<h4 style="margin: 1em 0;">
Playing melodies</h4>
To make music we need notes and to produce notes we need different frequencies. To create frequencies using a microcontroller and a speaker we can feed the speaker a square wave with a 50% duty cycle and a frequency corresponding to the note we wish to play. The following table summarizes the notes that the firmware below uses. Other <a href="http://en.wikipedia.org/wiki/Scientific_pitch_notation" target="_blank">frequencies from C0 to B10</a> can of course be utilized. <br />
<br />
<table border="1" cellpadding="5" cellspacing="0"><tbody>
<tr><th>Note </th>
<th>Pitch</th>
<th>Frequency f (Hz)</th>
<th>Period T = 1/f (microseconds)</th>
<th>T/2</th></tr>
<tr>
<td>ti</td>
<td>B4</td>
<td>493.88</td>
<td>2024.8</td>
<td>1012.4</td>
</tr>
<tr>
<td>do</td>
<td>C5</td>
<td>523.25</td>
<td>1911.1</td>
<td>955.57</td>
</tr>
<tr>
<td>re</td>
<td>D5</td>
<td>587.33</td>
<td>1702.6</td>
<td>851.31</td>
</tr>
<tr>
<td>mi</td>
<td>E5</td>
<td>659.26</td>
<td>1516.8</td>
<td>758.43</td>
</tr>
<tr>
<td>fa</td>
<td>F5</td>
<td>698.46</td>
<td>1431.7</td>
<td>715.86</td>
</tr>
<tr>
<td>sol</td>
<td>G5</td>
<td>783.99</td>
<td>1275.5</td>
<td>637.76</td>
</tr>
<tr>
<td>la</td>
<td>A5</td>
<td>880.00</td>
<td>1136.4</td>
<td>568.18</td>
</tr>
<tr>
<td>ti</td>
<td>B5</td>
<td>987.77</td>
<td>1012.4</td>
<td>506.19</td>
</tr>
<tr>
<td>do</td>
<td>C6</td>
<td>1046.5</td>
<td>955.57</td>
<td>477.78</td>
</tr>
<tr>
<td>re</td>
<td>D6</td>
<td>1174.7</td>
<td>851.28</td>
<td>425.64</td>
</tr>
<tr>
<td>mi</td>
<td>E6</td>
<td>1318.5</td>
<td>758.44</td>
<td>379.22</td></tr>
</tbody></table>
<br />
In the firmware implementation the sole timer that exists in the PIC10F202 is used to determine when to switch the speaker on and off to produce the desired note. The 10F202 has a fixed internal clock frequency of 4MHz. With timer 0 prescaler set to 1:4 <code>TMR0</code> rolls over to zero every 1 µs x 4 x <code>TMR0</code> counts. Shifting the variables around we have: <code>TMR0</code> counts = (T/2) / (1 µs x 4). Computed values are given in the following table. <br />
<br />
<table border="1" cellpadding="5" cellspacing="0">
<tbody>
<tr>
<th>Pitch</th>
<th>number of timer 0 counts given clock = 4MHz and prescale = 1:4</th>
</tr>
<tr>
<td>B4
</td>
<td>253
</td>
</tr>
<tr>
<td>C5
</td>
<td>239
</td>
</tr>
<tr>
<td>D5
</td>
<td>213
</td>
</tr>
<tr>
<td>E5
</td>
<td>190
</td>
</tr>
<tr>
<td>F5
</td>
<td>179
</td>
</tr>
<tr>
<td>G5
</td>
<td>159
</td>
</tr>
<tr>
<td>A5
</td>
<td>142
</td>
</tr>
<tr>
<td>B5
</td>
<td>127
</td>
</tr>
<tr>
<td>C6
</td>
<td>119
</td>
</tr>
<tr>
<td>D6
</td>
<td>106
</td>
</tr>
<tr>
<td>E6
</td>
<td>95
</td>
</tr>
</tbody>
</table>
<br />
In order to obtain the above counts <code>TMR0</code> is loaded with an initial value = 256 - <code>TMR0</code> counts.<br />
<br />
By energizing the speaker for T/2 and de-energizing it for T/2 the note
of the desired frequency can be played. In creating a melody, however,
we also need to control how long each note is played. Thus in the
firmware this time duration of play is specified with each note. Since T/2 varies for every note the duration of play must vary accordingly as well in order for each note to play for the same length of time. This is computed as follows: <br />
<br />
L1 x T1 = L2 x T2 <br />
<br />
where <br />
L1 = play duration of note 1<br />
T1 = period of note 1<br />
L2 = play duration of note 2<br />
T2 = period of note2<br />
<br />
In the firmware E6 is arbitrarily set to 200. This leaves some room for a few more higher notes that may be necessary for other tunes. Table below lists the computed play durations.<br />
<br />
<table border="1" cellpadding="5" cellspacing="0">
<tbody>
<tr>
<th>Pitch
</th>
<th>Play duration of each note (E6 used as standard and arbitrarily set to 200)
</th>
</tr>
<tr>
<td>B4
</td>
<td>75
</td>
</tr>
<tr>
<td>C5
</td>
<td>79
</td>
</tr>
<tr>
<td>D5
</td>
<td>89
</td>
</tr>
<tr>
<td>E5
</td>
<td>100
</td>
</tr>
<tr>
<td>F5
</td>
<td>106
</td>
</tr>
<tr>
<td>G5
</td>
<td>119
</td>
</tr>
<tr>
<td>A5
</td>
<td>133
</td>
</tr>
<tr>
<td>B5
</td>
<td>150
</td>
</tr>
<tr>
<td>C6
</td>
<td>159
</td>
</tr>
<tr>
<td>D6
</td>
<td>178
</td>
</tr>
<tr>
<td>E6
</td>
<td>200
</td>
</tr>
</tbody>
</table>
<br />
In the <code>PlayNote</code> subroutine variable <code>LENGTH</code> determines how many times a note plays.<br />
<br />
<h4 style="margin: 1em 0;">
Firmware</h4>
Since the firmware for the two circuits above are almost the same except for the melodies they play, I just had one program and had preprocessor defines to determine which part of the code would be assembled. By commenting out either <code>#define home</code> or <code>#define office</code> the desired firmware can be assembled. <br />
<br />
The time duration that a note is played is implemented through the variable <code>LENGTH</code>, or in lieu of that by using a literal, and through the variable <code>CYCLES</code>. Pauses (silence) between notes when necessary are implemented using a 100ms delay routine. <br />
<br />
Because there's only one timer, timer 0 is used for both playing the notes and for polling of the switches. The prescale values are different for the two applications and so <code>OPTION</code> register is edited accordingly when switching between the two. For the office circuit <code>OPTION</code> is also updated when weak pull-ups have to be temporarily disabled when the pushbutton is being polled.<br />
<br />
Firmware debounces all switch readings. The last eight readings are stored in one 8-bit variable and the switch is deemed not bouncing if the the variable is equal to 0x00 or 0xFF. Any other value means the switch is bouncing. Upon detection of a
falling edge MCU plays one sequence of musical notes, and on detection
of rising edge a different melody, to distinguish between opening and
closing of the door. <br />
<br />
I coded the firmware in PIC assembly using Microchip's MPLAB IDE. You'll probably be perplexed by the code since it uses a lot of <a href="http://electromotiveforces.blogspot.com/2010/06/macros-and-defines-for-pic-baseline-and.html" target="_blank">homebrew defines and macros</a>. On my computer they're in the include file "et.inc" and thus the reason for the <code>#include et.inc</code>.
<br />
<br />
<br />
<pre>
; ********************************************************************************
;
; DOOR OPENED, CLOSED, AND LEFT OPEN TOO LONG DETECTOR
; WITH CORRESPONDING MELODIES FOR EACH CONDITION
; October 2008
;
; MPLAB IDE
;
; ********************************************************************************
processor 10F202
#include p10F202.inc
#include et.inc
__config _MCLRE_OFF & _CP_OFF & _WDT_OFF
cblock 0x08
COUNTER:3 ; loop counters
CYCLES ; determines how long the note sounds; value of 4 makes the note sound for approx 0.3sec
LENGTH:2 ; number of times a frequency (note) will play so that all frequencies will play with the same time duration.
NOTE ; T/2 in terms of TMR0 initial value
SW_VAL ; each bit is a reed switch reading; bit0 contains the latest reading,
; bit7 the oldest; bits are shifted left during each TMR0 rollover
PB_VAL ; each bit is a push button switch reading; bit0 has the latest reading;
; bits are shifted left during each TMR0 rollover
FEN ; assortment of flag and enable bits for various purposes
DOOR_OPEN_COUNT:2 ; counts number of elapsed 4.08ms, when count has exceeded preset level,
; chime sounds alerting door open condition
endc
; **********************************************
; !! SET THE CORRECT DEFINES !!
; is firmware for office or for home?
#define office
; #define home
; **********************************************
; **********************************************
; change the following constants if need be
; number of seconds door is open before door-open-too-long chime plays
#define open_time .30
; time interval in seconds between door-open-too-long chime
; make sure this value is less than open_time since it will be deducted from it
#define alert_interval .20
; **********************************************
#define spkr GPIO,2 ; output speaker
#define reedsw GPIO,0 ; input from reed switch / optoisolator
#define pb GPIO,3 ; momentary N.O. push button;
; pressing pb prevents/stops door-open-too-long melody from playing
; macro for playing the note
; _note is the note's T/2 (half period)
; _length is the number of times the note is played
key macro _note, _length
movlf _note, NOTE
movlf _length, LENGTH
call PlayNote
endm
; see wikipedia article "scientific pitch notation" for table of frequency values of notes
; B4 = ti = 493.88 Hz, period = 2.0248 ms, T/2 = 1.0124 us
; C5 = do = 523.25 Hz, period = 1.9111 ms, T/2 = 955.57 us
; D5 = re = 587.33 Hz, period = 1.7026 ms, T/2 = 851.31 us
; E5 = mi = 659.26 Hz, period = 1.5168 ms, T/2 = 758.43 us
; F5 = fa = 698.46 Hz, period = 1.4317 ms, T/2 = 715.86 us
; G5 = sol = 783.99 Hz, period = 1.2755 ms, T/2 = 637.76 us
; A5 = la = 880.00 Hz, period = 1.1364 ms, T/2 = 568.18 us
; B5 = ti = 987.77 Hz, period = 1.0124 ms, T/2 = 506.19 us
; C6 = do = 1046.5 Hz, period = 955.57 us, T/2 = 477.78 us
; D6 = re = 1174.7 Hz, period = 851.28 us, T/2 = 425.64 us
; E6 = mi = 1318.5 Hz, period = 758.44 us, T/2 = 379.22 us
; given internal clock = 4Mhz and timer0 prescaler = 1:4
; timer zero rolls over to zero every 1microsecond x 4 x TMR0 steps
; TMR0 steps = (T/2) / (1 us x 4)
; TMR0 initial value = 256 - TMR0 steps
; the following values should be loaded into TMR0 in order to produce the above notes, respectively
#define B4 .256 - .253
#define C5 .256 - .239
#define D5 .256 - .213
#define E5 .256 - .190
#define F5 .256 - .179
#define G5 .256 - .159
#define A5 .256 - .142
#define B5 .256 - .127
#define C6 .256 - .119
#define D6 .256 - .106
#define E6 .256 - .95
; in the PlayNote subroutine LENGTH determines how many times the note will play.
; since T/2 for every note is different LENGTH must be different in order for each note to play for the same length of time
; LENGTH is computed as follows: L1 x T1 = L2 x T2
; where L1 = length note 1, T1 = period of note 1, L2 = length of note 2, T2 = period of note2
; arbitrarily set LENGTH for E6 = 200 (leave room for a few more higher notes)
#define LB4 .75
#define LC5 .79
#define LD5 .89
#define LE5 .100
#define LF5 .106
#define LG5 .119
#define LA5 .133
#define LB5 .150
#define LC6 .159
#define LD6 .178
#define LE6 .200
#define sw_level FEN,0 ; voltage level: 1 = hi, 0 = low
#define override FEN,1 ; DoorOpenTooLongChime override flag; 1 = override active, 0 = no override
#define option_poll_sw b'10000011' ; value to copy to OPTION register when polling switches
; pullups enabled, prescaler to timer0, prescaler = 1:16
#define option_chime b'10000001' ; value to copy to OPTION register in chime routine
; pullups enabled, prescaler to timer0, prescaler = 1:4
#define option_no_wpu b'11000011' ; value to copy to OPTION register when polling switches but with weak pull-ups off
#define tmr_init_val .11 ; initial value for timer0, given 4Mhz clock and prescaler 1:16, TMR0 will rollover every 3.92ms
; 3.92ms was chosen so that in the check_level routine open_time and alert_interval are both
; equivalent to seconds; every time DOOR_OPEN_COUNT rolls over, time elapsed = 3.92ms x 256 = 1.004sec
; =======================================================================================
; =======================================================================================
org 0x0
movwf OSCCAL ; calibrate the chip
goto initialize
; =======================================================================================
; subroutines
;
; 10F202 can only call subroutines if they are located in first 256 memory locations
; =======================================================================================
; ---------------------------------------------------------------------------------
; Notes are played by switching the speaker on and off at the frequency of the note.
; the note is played for a specified time as determined by LENGTH and CYCLES
PlayNote:
movff NOTE, TMR0
movff LENGTH, LENGTH+1
movff CYCLES, COUNTER
bsf spkr
loop_note
dnxnotzero TMR0
goto loop_note
movff NOTE, TMR0
toggle spkr
decfsz LENGTH+1,f
goto loop_note
movff LENGTH, LENGTH+1
decfsz COUNTER,f
goto loop_note
bcf spkr
return
; ---------------------------------------------------------------------------------
; ---------------------------------------------------------------------------------
OptionChime:
movlw option_chime
option
return
; ---------------------------------------------------------------------------------
; ---------------------------------------------------------------------------------
OptionPollSw:
movlw option_poll_sw
option
return
; ---------------------------------------------------------------------------------
; ---------------------------------------------------------------------------------
OptionNoWPU:
movlw option_no_wpu
option
return
; ---------------------------------------------------------------------------------
; ************************************************************
; following subroutines are for office circuit melodies
#ifdef office
; ---------------------------------------------------------------------------------
; the following melody is played when door is opened (falling edge detected)
DoorJustOpenedChime:
call OptionChime
movlf .4, CYCLES
key C5, LC5
key E5, LE5
key B5, .255
call OptionPollSw
return
; ---------------------------------------------------------------------------------
; ---------------------------------------------------------------------------------
; the following melody is played when door is closed (rising edge detected)
DoorClosed:
call OptionChime
movlf .2, CYCLES
key A5, LA5
key F5, LF5
movlf .5, CYCLES
key D5, LD5
key C5, LC5
call OptionPollSw
return
; ---------------------------------------------------------------------------------
; ---------------------------------------------------------------------------------
; the following melody is played when door has been left open for too long
DoorOpenTooLongChime:
dnxbit1 override ; don't play chime if override button had been pressed
return
call OptionChime
movlf .2, CYCLES
movlf .3, COUNTER+2
door_open_loop1
key C5, LC5
key E5, LE5
key B5, LB5
decfsz COUNTER+2,f
goto door_open_loop1
movlf .3, COUNTER+2
door_open_loop2
key C5, LC5
key E5, LE5
key A5, LA5
decfsz COUNTER+2,f
goto door_open_loop2
key A5, LA5
call OptionPollSw
return
; ---------------------------------------------------------------------------------
#endif ; ifdef office
; ************************************************************
; ************************************************************
; following subroutines are for home circuit melodies
#ifdef home
; ---------------------------------------------------------------------------------
; approximately 100 millisecond delay
D100ms
clrf COUNTER
movlf .130, COUNTER+1
d100ms_loop
decfsz COUNTER,f
goto d100ms_loop
decfsz COUNTER+1,f
goto d100ms_loop
return
; ---------------------------------------------------------------------------------
; ---------------------------------------------------------------------------------
; the following melody is played when door is opened (falling edge detected)
; "mary had a little lamb (partial)" melody
DoorJustOpenedChime:
call OptionChime
movlf .3, CYCLES
key B5, .255
key A5, LA5
key G5, LG5
key A5, LA5
call D100ms
key B5, LB5
call D100ms
key B5, LB5
call D100ms
key B5, .255
call OptionPollSw
return
; ---------------------------------------------------------------------------------
; ---------------------------------------------------------------------------------
; the following melody is played when door is closed (rising edge detected)
DoorClosed:
; none to be played
return
; ---------------------------------------------------------------------------------
; ---------------------------------------------------------------------------------
; the following melody is played when door has been left open for too long
; "mary had a little lamb (complete)" melody
DoorOpenTooLongChime:
dnxbit1 override ; don't play chime if override button had been pressed
return
call OptionChime
movlf .3, CYCLES
key B5, .255
key A5, LA5
key G5, LG5
key A5, LA5
call D100ms
key B5, LB5
call D100ms
key B5, LB5
call D100ms
key B5, .255
key A5, LA5
call D100ms
key A5, LA5
call D100ms
key A5, .255
key B5, LB5
key D6, LD6
call D100ms
key D6, .255
key B5, .255
key A5, LA5
key G5, LG5
key A5, LA5
call D100ms
key B5, LB5
call D100ms
key B5, LB5
call D100ms
key B5, LB5
call D100ms
call D100ms
key B5, LB5
key A5, LA5
call D100ms
key A5, LA5
key B5, LB5
key A5, LA5
key G5, .255
call OptionPollSw
return
; ---------------------------------------------------------------------------------
#endif ; ifdef home
; ************************************************************
; =======================================================================================
; main program
; =======================================================================================
; ---------------------------------------------------------------------------------
initialize:
movlw b'1001'
tris GPIO
call OptionPollSw
clrf GPIO ; speaker off
clrf DOOR_OPEN_COUNT
clrf DOOR_OPEN_COUNT+1
movlf 0xFF, SW_VAL ; all bits hi, door considered closed
bsf sw_level ; level = hi, door considered closed
movlf 0xFF, PB_VAL ; all bits hi, push button considered unpressed
bcf override ; manual override flag initially off
movlf tmr_init_val, TMR0
; ---------------------------------------------------------------------------------
main:
poll_timer0:
dnxnotzero TMR0 ; poll TMR0 until it rolls over
goto poll_timer0
movlf tmr_init_val, TMR0
poll_timer0_end:
; check switch level if zero -- if it is this means that door is open
; if door has been left open for amount of time = open_time then sound the door-open-too-long chime
; and keep sounding the melody every alert_interval amount of time
check_level:
dnxbit1 sw_level
goto check_level_end
incfsz DOOR_OPEN_COUNT,f
goto check_level_end
incf DOOR_OPEN_COUNT+1,f
dnxltlit DOOR_OPEN_COUNT+1, open_time
goto check_level_end
call DoorOpenTooLongChime
movlf open_time - alert_interval, DOOR_OPEN_COUNT+1
check_level_end:
; poll and check reed switch; store value in 8-bit record
; if SW_VAL = 0xFF AND sw_level = 0 then let sw_level = 1, this is a rising edge detect and chime for door-just-closed is played
; if SW_VAL = 0x0 AND sw_level = 1 then let sw_level = 0, this is a falling edge detect and chime for door-just-opened is played
; if SW_VAL > 0 AND SW_VAL < 0xFF then switch is bouncing, so do nothing
; if SW_VAL = 0xFF AND sw_level = 1 then there is no level change, so do nothing
; if SW_VAL = 0 AND sw_level = 0 then there is no level change, so do nothing
check_reedsw:
rlf SW_VAL,f ; discard oldest switch reading
bcf SW_VAL,0 ; carry bit will be moved into bit zero so clear bit0
dnxbit1 reedsw
bsf SW_VAL,0
dnxeqlit SW_VAL, 0xFF
goto all_bits_hi
dnxzero SW_VAL
goto all_bits_lo
goto check_reedsw_end
all_bits_hi:
dnxbit1 sw_level
goto check_reedsw_end
bsf sw_level ; rising edge detected - door has just been closed
clrf DOOR_OPEN_COUNT
clrf DOOR_OPEN_COUNT+1
bcf override
call DoorClosed
goto check_reedsw_end
all_bits_lo:
dnxbit0 sw_level
goto check_reedsw_end
bcf sw_level ; falling edge detected - door has just been opened
call DoorJustOpenedChime
check_reedsw_end:
; check the override button if it's been pressed
; if door is open (i.e., sw_level = 0) AND pb is pressed (i.e., PB_VAL = 0)
; then set override flag -- this will prevent DoorOpenTooLongChime from playing
check_pb:
rlf PB_VAL,f ; discard oldest switch reading
bcf PB_VAL,0 ; carry bit will be moved into bit zero so clear bit0
; ************************************************************
; the following compensates for a flaw in the circuit:
; the 5.1K pulldown resistor for PB is too high, given the 16k to 20Kohm weak pullup of the 10F202;
; When N.C. PB is pressed, 5.1K resistor cannot pull close enough to ground for GP3 to detect a low
; According to datasheet input low = 0.2Vdd max
; Firmware fix of hardware problem:
; Turn off the weak pull-up just before PB is read and restore right after the read
; solves the hardware problem without affecting anything else.
#ifdef office
call OptionNoWPU ; turn off weak pull ups
#endif
; ************************************************************
dnxbit1 pb
bsf PB_VAL,0
; ************************************************************
#ifdef office
call OptionPollSw ; restore weak pull ups
#endif
; ************************************************************
dnxnotzero PB_VAL
goto check_pb_end
dnxbit0 sw_level
bsf override
check_pb_end:
goto main
; ---------------------------------------------------------------------------------
end
; ==============================================================================
; alternative melodies for chime
; ==============================================================================
; melody from the movie Close Encounters of the Third Kind
; --------------------------------------------------------
; key D6, LD6
; key E6, LE6
; key C6, LC6
; key E5, LE5
; key G5, .255
;
; melody akin to music box dancer
; -------------------------------
; key C5, LC5
; key E5, LE5
; key G5, LG5
; key C6, LC6
; key B5, LB5
; key A5, LA5
; key G5, .255
; edwardson-composed melody (part of the melodies used in the door opened/closed routines)
; -------------------------------
; movlf .2, CYCLES
; movlf .3, COUNTER+2
;door_open_loop1
; key A5, LA5
; key F5, LF5
; key D5, LD5
; decfsz COUNTER+2,f
; goto door_open_loop1
;
; movlf .3, COUNTER+2
;door_open_loop2
; key A5, LA5
; key E5, LE5
; key C5, LC5
; decfsz COUNTER+2,f
; goto door_open_loop2
;
; movlf .4, CYCLES
; key A5, LA5
</pre>
<br />erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com0tag:blogger.com,1999:blog-4092193417928690079.post-21200732992342681232012-06-27T01:19:00.000+08:002012-06-27T01:19:50.534+08:00Musical candle teardownWhile in an office supplies store I saw a display rack full of "Singing Candles" and thought it would be interesting to take one apart to see how it works. <br />
<br />
I've already taken off the white end cap when I took the picture. <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0VQCZyXb-99MCOMWuHU7XcR0D4wcpBBO_6lgN9-JLIS5HauDpzf6tba3DHrUfb3T-hSz5PUyjdbyzpu0mVY3dxuGtlOaz4tzZZli24nv6-cnQydoxGj8LRBWjDDxvKNe-jI2Qdx1RjYA/s1600/music-candle-1960.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0VQCZyXb-99MCOMWuHU7XcR0D4wcpBBO_6lgN9-JLIS5HauDpzf6tba3DHrUfb3T-hSz5PUyjdbyzpu0mVY3dxuGtlOaz4tzZZli24nv6-cnQydoxGj8LRBWjDDxvKNe-jI2Qdx1RjYA/s320/music-candle-1960.png" width="320" /></a></div>
<br />
There's a pre-made hole in the candle through which the thin silver strip threads and out the top of the candle. <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhShBuSZahQIuyTX01b5eNCia3iIrTqSy8EJecq6A3uTF516NhyphenhyphengJm_9oB0i5bd5Uw1kdi1e3b3L-6tLIhOObmctBghYWZVzA2Gce_BjlDRxDsKkTwedea5-iaJ1TVdMWq7aT9OgkBLmfc/s1600/music-candle-1959.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhShBuSZahQIuyTX01b5eNCia3iIrTqSy8EJecq6A3uTF516NhyphenhyphengJm_9oB0i5bd5Uw1kdi1e3b3L-6tLIhOObmctBghYWZVzA2Gce_BjlDRxDsKkTwedea5-iaJ1TVdMWq7aT9OgkBLmfc/s320/music-candle-1959.png" width="320" /></a></div>
<br />
The MCU is in the black glob of course. The red ceramic capacitor is 10nF. The negative terminal of the button battery is soldered to the pcb. <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghquYgiYImykyf5Tzpra35zczHBoF8nOBa1pLUw8UmBKb8UI16FOuUi54lWIXI52DxU-5cs6rBw581u9HU9mON51dJ4zYr8c8YxinhomQ4ITbHmf4bKxhj7kEabqyWN3qLDDeeZifCFNI/s1600/music-candle-1963.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="165" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghquYgiYImykyf5Tzpra35zczHBoF8nOBa1pLUw8UmBKb8UI16FOuUi54lWIXI52DxU-5cs6rBw581u9HU9mON51dJ4zYr8c8YxinhomQ4ITbHmf4bKxhj7kEabqyWN3qLDDeeZifCFNI/s320/music-candle-1963.png" width="320" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiK514uBbE2LI0NoVBNPtNS56E0rw0f-mPPdCw9ZpWaXubx9i0inXeieaa0csazmFyurHabh1iEubG-jYl6a_e3Yxh8rpiKjeifalXr6aXlW2CjcwInagK6OqGTyratLOBWiUwkhQZAovE/s1600/music-candle-1965.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="174" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiK514uBbE2LI0NoVBNPtNS56E0rw0f-mPPdCw9ZpWaXubx9i0inXeieaa0csazmFyurHabh1iEubG-jYl6a_e3Yxh8rpiKjeifalXr6aXlW2CjcwInagK6OqGTyratLOBWiUwkhQZAovE/s320/music-candle-1965.png" width="320" /></a></div>
<br />
An S9014 NPN transistor drives the speaker. Base current limiting resistor is 4.7K 1/4-watt.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpG_bN4bOMaU90VKM-EMv3O-ZlPy9TwFKFyXMZHx5SszbBbjreDqZmDVqFB4XGd5M4SwfyBMVIaDqE00DxCxs9ETFLClfw3O5NazIuYY0YzmoalLOJlevjFuDUGnPEQ316Efo10s0R7rk/s1600/music-candle-1964.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="188" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpG_bN4bOMaU90VKM-EMv3O-ZlPy9TwFKFyXMZHx5SszbBbjreDqZmDVqFB4XGd5M4SwfyBMVIaDqE00DxCxs9ETFLClfw3O5NazIuYY0YzmoalLOJlevjFuDUGnPEQ316Efo10s0R7rk/s320/music-candle-1964.png" width="320" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPw2wWrgiLEHgLyXmq60iZe_LG7zp_7rL_4qGMC8bPvIRwH-sLOKxV4mx87P8LADGFPLdESQdxonddQPS8rXK8idKMOFUBi1PK75_C2DZLCgXRlF99ngiWUro1lbJugRX2Rx4q-17eX_0/s1600/music-candle-1966.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="170" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPw2wWrgiLEHgLyXmq60iZe_LG7zp_7rL_4qGMC8bPvIRwH-sLOKxV4mx87P8LADGFPLdESQdxonddQPS8rXK8idKMOFUBi1PK75_C2DZLCgXRlF99ngiWUro1lbJugRX2Rx4q-17eX_0/s320/music-candle-1966.png" width="320" /></a></div>
<br />
This is the speaker.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWU445DQGvEPEdgXx3dYJOsbd87TSt9FtSHH3H4r4rc3ziYFD1VeRx3mvXNUHQYa5O-pVyyZoLG2NXgE9-ltcWvTn_E6nuI3WR6u2qHK8H6qwvH_kUCP1oAfC6JQQMbCDzUfZL50jk38c/s1600/music-candle-1952.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWU445DQGvEPEdgXx3dYJOsbd87TSt9FtSHH3H4r4rc3ziYFD1VeRx3mvXNUHQYa5O-pVyyZoLG2NXgE9-ltcWvTn_E6nuI3WR6u2qHK8H6qwvH_kUCP1oAfC6JQQMbCDzUfZL50jk38c/s320/music-candle-1952.png" width="320" /></a></div>
<br />
Other end of the speaker. It doesn't show in the pics but there's a coil of enamel wire if you peer into the holes. <br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgpni-IcoqFMdX8E1lRXMpqEjKNbEDrNt_TG6sT4FKyQ9d9IpLde5zCKDRD0HPHhA1SEyxxLE605Dil5-AVdHS0nnZ3SaeK5BdRqk5yp8Y5kvRgB-uS-KUSz479ovRownDBHDGi2rODd4/s1600/music-candle-1961.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgpni-IcoqFMdX8E1lRXMpqEjKNbEDrNt_TG6sT4FKyQ9d9IpLde5zCKDRD0HPHhA1SEyxxLE605Dil5-AVdHS0nnZ3SaeK5BdRqk5yp8Y5kvRgB-uS-KUSz479ovRownDBHDGi2rODd4/s320/music-candle-1961.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhPgqQNnLB1LewT-5_BKb9OwxtlgjGS3Z2Hu2846oqOkjhEd0tVb62oJ_fEo08MAiZ66dwCUp95BDc3xZBD8-WdE4tYDDw_Z4XIw6XGC3ICUxX08aJQqQTmQ1PIq1R1Od9SJeGd0SdZFI/s1600/music-candle-1962.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhPgqQNnLB1LewT-5_BKb9OwxtlgjGS3Z2Hu2846oqOkjhEd0tVb62oJ_fEo08MAiZ66dwCUp95BDc3xZBD8-WdE4tYDDw_Z4XIw6XGC3ICUxX08aJQqQTmQ1PIq1R1Od9SJeGd0SdZFI/s320/music-candle-1962.png" width="320" /></a></div>
<br />
Tinned pcb tracks after the retaining clip, rectangular metal bridge and metalized strip have been removed<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwWjF0jLc81_o7ydCGoyn81DQJX6l4LFART5GXUlBYBcIsd_VVBvNsXKAM6GA8cyG0HGwjbzdYsE7LDjsg983UWi29GqCpmM0taFIhLQE0bvfngSomQS_c9aVvNaRB4aLMrDJX8x1ka3E/s1600/music-candle-1955.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwWjF0jLc81_o7ydCGoyn81DQJX6l4LFART5GXUlBYBcIsd_VVBvNsXKAM6GA8cyG0HGwjbzdYsE7LDjsg983UWi29GqCpmM0taFIhLQE0bvfngSomQS_c9aVvNaRB4aLMrDJX8x1ka3E/s320/music-candle-1955.png" width="320" /></a></div>
<br />
From left to right: retaining clip, rectangular metal bridge, metalized strip, pcb<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjayNk9j-quuJbPTRC8uWqKhcYtDhYjPB3BOuNCqNCmjLmrBdQT0du4cv8Bk2pJd84ta3R-K7-XkFUgdM9yywkT_XovGkqtyAG8VtVTXsS5meI9yG3OJXEbqsVf5qzXvQYBPNiEvlg2UDk/s1600/music-candle-1957.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjayNk9j-quuJbPTRC8uWqKhcYtDhYjPB3BOuNCqNCmjLmrBdQT0du4cv8Bk2pJd84ta3R-K7-XkFUgdM9yywkT_XovGkqtyAG8VtVTXsS5meI9yG3OJXEbqsVf5qzXvQYBPNiEvlg2UDk/s320/music-candle-1957.png" width="320" /></a></div>
<br />
<br />
The narrow silver strip consists of two electrically conductive
metalized outer layers and a nonconducting layer sandwiched in between. Measured resistance at DMM probe-to-probe distance of 1cm is 9 ohms, and ~60 ohms at 7 cm.<br />
<br />
The following is a stylized diagram showing how the strip is positioned on the pcb tracks. Thickness of the strip and tracks are exaggerated of course just for illustration purposes. <br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGvYhsAyEwmxhroP2QEwrs8xhl_Cbzwwq9GttSm46A4nmaWMcXU92KKbdvW10V3YTzQbkpZCzZn5sbfR1e33HQjdYxDZH568AUPJYSN4cXt1rOHTfp-bnvVL8CY4kkkaahGvpPQFQWsrY/s1600/music-candle-pcb-tracks.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="97" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGvYhsAyEwmxhroP2QEwrs8xhl_Cbzwwq9GttSm46A4nmaWMcXU92KKbdvW10V3YTzQbkpZCzZn5sbfR1e33HQjdYxDZH568AUPJYSN4cXt1rOHTfp-bnvVL8CY4kkkaahGvpPQFQWsrY/s320/music-candle-pcb-tracks.png" width="320" /></a></div>
The rectangular nickel-plated bridge sits on top of the strip and one of the tinned pcb tracks, providing an electrical pathway from the top layer of the strip to the other track. However, because of the insulating layer, the track on which the strip rests is isolated from the other track. A retaining clip secures the bridge and strip to the pcb. <br />
<br />
As you might have seen in the first couple of images above the blister pack has a "press here"
sticker that allows the would-be buyer to test the candle before
purchasing. After opening the package I took a look at what they had
under the sticker and found there's a 1-cm diameter aluminum foil
glued beneath the plastic film and another on the cardboard. Ohmmeter check confirms they're zero ohm. There's one or
two millimeters of airspace between the top and bottom foil. When the user pushes down
on the plastic film the two foils make contact. At the same time they pinch the exposed silver
strip. This causes electrical contact between the two outer layers of
the strip and immediately wakes
up the MCU thus causing the music to start playing. Only a momentary contact is necessary and the MCU will play the song three times. <br />
<br />
Here's the musical candle in action.<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="360" src="http://www.youtube.com/embed/8nDtkw4jJPg" width="480"></iframe>
<br />
<br />
Here's what I think happens: When the candle is lit and the flame begins to burn the strip, carbon forms and provides a relatively low resistance between the metalized layers, thus causing a switching action which wakes the MCU up (via an interrupt-on-change of sorts). As long as the candle is lit the MCU plays the song over and over. <br />
<br />
Interestingly when the flame is snuffed out the MCU stops playing (not immediately but only after playing the song three in a row after the last check of the pin monitoring the "strip switch"). This implies that the resistance of the carbon and other substances in the burnt strip markedly increases when it cools down. Reigniting the wick quickly brings the temperature back up and causes the resistance to decrease once again thus triggering the MCU.<br />
<br />
Pics of strip after candle had been lit<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-g62rNxBvhsytaImFVj-OCtUxUR2o9teVP7MvdVxQWMRLJoSqg99nAS1WzpNeXZ6mcR2XE2IEOyuQIoIR7LrfLuCkvJtm36ZEu-rpqZZyDcoMB2e8bPcD2Rg92YGfFYc4fcT_IkQ_LGs/s1600/music-candle-1969.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-g62rNxBvhsytaImFVj-OCtUxUR2o9teVP7MvdVxQWMRLJoSqg99nAS1WzpNeXZ6mcR2XE2IEOyuQIoIR7LrfLuCkvJtm36ZEu-rpqZZyDcoMB2e8bPcD2Rg92YGfFYc4fcT_IkQ_LGs/s320/music-candle-1969.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZjHDIQIIbyCR_0RfO82TY-SSZqunTSpnK2R-JkJi_yFLshkDgJ5V7QX4qEcHRHiJW4ctEXoEb6w1XKXdVwMr_USz5a_y5vHyiIQshDnt-uWyusofJv_75JfpUZCHJE2pmFGoEA2DNX54/s1600/music-candle-1972.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZjHDIQIIbyCR_0RfO82TY-SSZqunTSpnK2R-JkJi_yFLshkDgJ5V7QX4qEcHRHiJW4ctEXoEb6w1XKXdVwMr_USz5a_y5vHyiIQshDnt-uWyusofJv_75JfpUZCHJE2pmFGoEA2DNX54/s320/music-candle-1972.png" width="320" /></a></div>
<br />
<br />erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com0tag:blogger.com,1999:blog-4092193417928690079.post-15510571129036086472012-06-25T01:15:00.000+08:002012-06-25T01:26:52.061+08:00Measuring liquified petroleum gas (LPG) levelsBefore anything else, I forgot to mention in <a href="http://electromotiveforces.blogspot.com/2012/06/detecting-liquified-petroleum-gas-lpg.html" target="_blank">in the LPG detection test</a> that I took temperature measurements of the TGS2610 sensor of the <a href="http://www.figaro.co.jp/en/data/pdf/20091110164017_59.pdf" target="_blank">LPM2610</a> module using a Fluke 87V and its thermocouple. After the module had been powered up for many minutes I held the thermocouple junction to the case for a couple of minutes and obtained a maximum reading of 52.8°C. Ambient temperature was 32.7°C. I am not confident with this case temperature measurement. Contact area between thermocouple and case is small and manually holding the thermocouple did not of course properly secure it to the sensor. Taping or even gluing it in place may be a more reliable way of taking a measurement.<br />
<br />
As I mentioned in <a href="http://electromotiveforces.blogspot.com/2012/06/detecting-liquified-petroleum-gas-lpg.html" target="_blank">Detecting LPG</a>, it occurred to me that we can use the Figaro <a href="http://www.figaro.co.jp/en/data/pdf/20091110164017_59.pdf" target="_blank">LPM2610</a> module to not just detect the presence of LPG when it's >= 10% of its lower explosive limit (LEL), we may also be able to use its analog outputs to measure the amount of gas in the air.<br />
<br />
Before we jump in, a couple of caveats relevant to measuring gas levels. The <a href="http://www.figaro.co.jp/en/data/pdf/20091110164925_54.pdf" target="_blank">TGS2610 datasheet</a> says the sensor's typical detection range is between 500 to 10,000 ppm. Given butane LEL = 18,000 ppm, the sensor's detection range translates to 2.8% to 55.5% LEL. I want to be able to measure all the way down to 1% but it looks like this may be asking too much from this sensor. Then again we can take our chances. <br />
<br />
Secondly, the sensor is hardly accurate as can be seen in this Figaro diagram of the module's expected performance:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBis7_yA-2vXRNQM6RLTx72LD8vCRX509wt7_fr3y5-3CP7KZwIfdBoKvV-LOrEFVTSOI2onHEPqRSADnuTc0Yp48BRXPnwuDUNFrZAkQoZk8KC10dC9-9WZi9DAHIa3-tXGJ02tbwvkA/s1600/LPM2610+expected+performance+graph.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="154" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBis7_yA-2vXRNQM6RLTx72LD8vCRX509wt7_fr3y5-3CP7KZwIfdBoKvV-LOrEFVTSOI2onHEPqRSADnuTc0Yp48BRXPnwuDUNFrZAkQoZk8KC10dC9-9WZi9DAHIa3-tXGJ02tbwvkA/s320/LPM2610+expected+performance+graph.png" width="320" /></a></div>
<br />
The note below the chart says <br />
<blockquote class="tr_bq">
When using LPM2610, typical alarm tolerances for 10% LEL of LP gas such as those shown in the figure above can be expected. However, in actual usage, alarm thresholds may vary since the threshold is also affected by such factors as the tolerances of test conditions and heat generation inside the gas detection enclosure. </blockquote>
The tolerances are pretty wide: 5 to 20% LEL. Not at all encouraging. <br />
<br />
Given these characteristics, using the LPM2610 to measure gas levels seems almost foolhardy. But in quixotic fashion we shall nevertheless proceed--if only as a mathematical exercise. <br />
<br />
<br />
To start off here's a list of definitions of terms and variables we'll be using:<br />
<ul>
<li><i>Standard</i> values are sensor resistance and output voltage at an ambient temperature of 20°C. To <i>standardize</i> a voltage is to apply a corrective factor provided by V<sub>REF</sub> so that the voltage value becomes that which would be expected if ambient = 20°C. </li>
</ul>
<ul>
<li><i>Actual </i>values are LPM2610 resistance and output voltage values without consideration to ambient temperature.</li>
</ul>
<ul>
<li>V<sub>REF</sub> is the output of the LPM2610 voltage divider consisting of a thermistor and various fixed-value resistors. V<sub>REF</sub> is theoretically = V<sub>DD</sub>/2 when ambient temperature = 20°C. </li>
</ul>
<ul>
<li>V<sub>OUT(act)</sub> = actual output of the LPM2610 voltage divider consisting of (1) the TGS2610 sensor whose resistance varies with amount of LPG present and (2) a fixed value load resistor R<sub>L</sub>.
</li>
</ul>
<ul>
<li>R<sub>S(act)</sub> = actual sensor resistance in the LPM2610</li>
</ul>
<ul>
<li>R<sub>S(std)</sub> = standard sensor resistance </li>
</ul>
<ul>
<li>V<sub>OUT(std)</sub> = standard voltage output given R<sub>S(std) </sub> </li>
</ul>
<ul>
<li>R<sub>L</sub> = load resistance in series with R<sub>S</sub> </li>
</ul>
<br />
<h4 style="margin: 1em 0;">
Preliminary equations</h4>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTaWzWzRmQJvZI2ajHgFmOskTWO4MfsfUsppB0VogF0KHij3gqtx36SYZpVE2RyFFeolwtBPBXCzhdv3XvCVDJv0GTomY3xn_l0ha6MV_gOz4nv4edUESy2w6PJgdopxf_GbgNxKTCLEs/s1600/LPM2610-test-circuit.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTaWzWzRmQJvZI2ajHgFmOskTWO4MfsfUsppB0VogF0KHij3gqtx36SYZpVE2RyFFeolwtBPBXCzhdv3XvCVDJv0GTomY3xn_l0ha6MV_gOz4nv4edUESy2w6PJgdopxf_GbgNxKTCLEs/s320/LPM2610-test-circuit.png" width="320" /></a></div>
Take a look at the LPM2610 circuit diagram above. Given the voltage divider R<sub>S</sub> and R<sub>L</sub>, output voltage V<sub>OUT</sub> is given by the equation below. V<sub>OUT</sub> in this case is the actual V<sub>OUT</sub> and R<sub>S</sub> is the actual R<sub>S</sub>. <br />
<br />
<a href="http://www.codecogs.com/eqnedit.php?latex=V_%7BOUT%28act%29%7D%20=%20%5Cleft%20[%5Cfrac%7BR_%7BL%7D%7D%7BR_%7BL%7D@plus;R_%7BS%28act%29%7D%7D%5Cright%20]%20V_%7BDD%7D" target="_blank"><img src="http://latex.codecogs.com/gif.latex?V_%7BOUT%28act%29%7D%20=%20%5Cleft%20[%5Cfrac%7BR_%7BL%7D%7D%7BR_%7BL%7D+R_%7BS%28act%29%7D%7D%5Cright%20]%20V_%7BDD%7D" title="V_{OUT(act)} = \left [\frac{R_{L}}{R_{L}+R_{S(act)}}\right ] V_{DD}" /></a>
<br />
<br />
Given our definitions of standard sensor resistance and output voltage it follows that:<br />
<br />
<a href="http://www.codecogs.com/eqnedit.php?latex=V_%7BOUT%28std%29%7D%20=%20%5Cleft%20[%5Cfrac%7BR_%7BL%7D%7D%7BR_%7BL%7D@plus;R_%7BS%28std%29%7D%7D%5Cright%20]%20V_%7BDD%7D" target="_blank"><img src="http://latex.codecogs.com/gif.latex?V_%7BOUT%28std%29%7D%20=%20%5Cleft%20[%5Cfrac%7BR_%7BL%7D%7D%7BR_%7BL%7D+R_%7BS%28std%29%7D%7D%5Cright%20]%20V_%7BDD%7D" title="V_{OUT(std)} = \left [\frac{R_{L}}{R_{L}+R_{S(std)}}\right ] V_{DD}" /></a><br />
<br />
The LPM2610 module is calibrated at an ambient temperature of 20°C where V<sub>REF</sub> = V<sub>DD</sub>/2. As ambient temperature deviates from 20°C, V<sub>REF</sub> is adjusted by the module's thermistor. In our equations we will need to determine V<sub>OUT(std)</sub>. And so we need to adjust V<sub>OUT(act)</sub> by a certain factor. This correction factor is as follows:
<br />
<br />
<a href="http://www.codecogs.com/eqnedit.php?latex=%5Cfrac%7BV_%7BREF%7D%7D%7B%5Cfrac%7BV_%7BDD%7D%7D%7B2%7D%7D=%20%5Cfrac%7B2V_%7BREF%7D%7D%7BV_%7BDD%7D%7D" target="_blank"><img src="http://latex.codecogs.com/gif.latex?%5Cfrac%7BV_%7BREF%7D%7D%7B%5Cfrac%7BV_%7BDD%7D%7D%7B2%7D%7D=%20%5Cfrac%7B2V_%7BREF%7D%7D%7BV_%7BDD%7D%7D" title="\frac{V_{REF}}{\frac{V_{DD}}{2}}= \frac{2V_{REF}}{V_{DD}}" /></a>
<br />
<br />
<h4 style="margin: 1em 0">
Derivation of R<sub>S(std)</sub> Equation </h4>
We need to find an equation that describes sensor resistance in terms of % LEL. Since Figaro provides no equation we need to derive it from sensor data. Fortunately, <a href="http://www.figarosensor.com/products/2610app.pdf" target="_blank">Application Note for LP Gas Detectors using TGS2610</a>
lists R<sub>L</sub> = R<sub>S</sub> for various %LEL and TGS2610 ID numbers (ID# corresponds to
specific sensor resistances). For example the Figaro LPM2610 modules that I got
have sensors with ID#15. From the AN here are its characteristics:<br />
<br />
<br />
<table border="1" cellpadding="5" cellspacing="0">
<caption>Specification of TGS2610 with ID#15</caption>
<tbody>
<tr>
<th>% LEL</th>
<th>R<sub>S(std)</sub> in ohms</th>
</tr>
<tr>
<td>5</td>
<td>3830</td>
</tr>
<tr>
<td>10</td>
<td>2740</td>
</tr>
<tr>
<td>15</td>
<td>2260</td>
</tr>
<tr>
<td>20</td>
<td>2000</td>
</tr>
</tbody></table>
<br />
To transform that table of data into a formula we perform regression analysis. If we plot the above we end up with a nonlinear curve, one which shows the characteristics of a <a href="http://en.wikipedia.org/wiki/Power_law" target="_blank">power law curve</a> where y = ax<sup>b</sup>.
Unfortunately, the spreadsheet I'm using can only do linear regression.
Fortunately, we can work around this limitation. If we plot the values
in the above table on a log-log paper (where x and y axes are graduated
in a logarithmic scale), we get a nearly straight line and not a curve.
Therefore, we can take the log(x) and log(y) and plug those in the
linear regression equations. Solving for the log of both x and y, i.e.,
of %LEL and R<sub>S(std)</sub>, we obtain the following:<br />
<br />
<table border="1" cellpadding="5" cellspacing="0">
<tbody>
<tr>
<th>log(%LEL)</th>
<th>log(R<sub>S(std)</sub>)</th>
</tr>
<tr>
<td>0.698970004336019
</td>
<td>3.58319877396862</td>
</tr>
<tr>
<td>1</td>
<td>3.43775056282039</td>
</tr>
<tr>
<td>1.17609125905568</td>
<td>3.3541084391474</td>
</tr>
<tr>
<td>1.30102999566398</td>
<td>3.30102999566398</td>
</tr>
</tbody></table>
<br />
We let the spreadsheet perform a linear regression using the above logarithmic data and it magically spews out the following:<br />
<br />
b = x coefficient = -0.471269891979091<br />
c = constant = 3.91103846203761<br />
r squared = 0.999483957661255<br />
<br />
r<sup>2</sup>
is the square of the correlation coefficient. Since it's practically = 1
it implies that the correlation between data and the equation is
extremely high. Keeping in mind that we had used the logarithms of x and
y, the regression equation is:<br />
<br />
log(y) = b*log(x) + c <br />
<br />
Here's a plot of both the datasheet values (red points) and computed line using an <a href="http://science.kennesaw.edu/%7Eplaval/applets/LRegression.html" target="_blank">online linear regression calculator</a>:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0Gx7PTqCW4en0ZEb-UP8XRNCDeCKqPoKnBrRnzRzlnry_JA-kUea8zsKFQvfqfo7bb93YaRDoiOTl4yaYTMoTSlRMqU-puob95x0OTVTkkiPiRWjR3G2IsSgOYKK8CfkFtCRa3lsNHtY/s1600/TGS2610+id%2315+LEL+vs+resistance+linear+plot.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="256" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0Gx7PTqCW4en0ZEb-UP8XRNCDeCKqPoKnBrRnzRzlnry_JA-kUea8zsKFQvfqfo7bb93YaRDoiOTl4yaYTMoTSlRMqU-puob95x0OTVTkkiPiRWjR3G2IsSgOYKK8CfkFtCRa3lsNHtY/s320/TGS2610+id%2315+LEL+vs+resistance+linear+plot.png" width="320" /></a></div>
<br />
<br />
Taking the antilog of both sides of the equation above gives us:<br />
<br />
y = 10<sup>[b*log(x) + c]</sup><br />
<br />
Table below shows the computed R<sub>S(std)</sub> and the %error from values given in the datasheet. <br />
<br />
<table border="1" cellpadding="5" cellspacing="0">
<caption>R<sub>S(std)</sub> using regression equation</caption>
<tbody>
<tr>
<th>% LEL</th>
<th>R<sub>S(std)</sub> in ohms</th>
<th>error in %</th>
</tr>
<tr>
<td>5</td>
<td>3816</td>
<td>-0.36</td>
</tr>
<tr>
<td>10</td>
<td>2753</td>
<td>0.47</td>
</tr>
<tr>
<td>15</td>
<td>2274</td>
<td>0.62</td>
</tr>
<tr>
<td>20</td>
<td>1986</td>
<td>-0.72</td>
</tr>
</tbody></table>
<br />
<br />
Recall that I performed a
linear regression because my spreadsheet does not have the capability of
doing a power regression. However, there are various online resources
that we can use, obviating the need to get the logarithms of x and y.
Plugging the data from the very first table above into a <a href="http://science.kennesaw.edu/%7Eplaval/applets/PRegression.html" target="_blank">power regression calculator</a> gives us the following results:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbrzBTKpY3f7r2E64-_uuRIYb_qYtQ1IfP8awdUS9IatEXfOX54cAAuAwEal-8Rn18vghfU4Z3TmHbXuNcU2A2DXKEeTqPRrHabe_vivnW-_yZBVCOmTUVfSauofbfB9AfF6Or_OPzsu8/s1600/TGS2610+id%2315+LEL+vs+resistance+power+plot.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="256" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbrzBTKpY3f7r2E64-_uuRIYb_qYtQ1IfP8awdUS9IatEXfOX54cAAuAwEal-8Rn18vghfU4Z3TmHbXuNcU2A2DXKEeTqPRrHabe_vivnW-_yZBVCOmTUVfSauofbfB9AfF6Or_OPzsu8/s320/TGS2610+id%2315+LEL+vs+resistance+power+plot.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6SLZHAyHmrvzrztvNNIP105g1g_vcPej40AWkMWCElxBX4pWqUu_1gL7yJs-mWiEl-LCHw6QHXZHTcw1u3HZzvm_R8HMM0Dhll852kAlkslO8gIXVwcmZm3xGUcaSt1CpQ0kV-rlMXF8/s1600/TGS2610+id%2315+LEL+vs+resistance+power+plot.png" style="margin-left: 1em; margin-right: 1em;"><br /></a></div>
a = 8147.7643914991<br />
b = -0.471269892<br />
correlation coefficient = 0.9998059883<br />
<br />
So the power equation is<br />
<br />
y = 8147.7643914991x<sup>-0.471269892</sup><br />
<br />
Now, since both the linear and power equations yield the same y we can equate the two:<br />
<br />
ax<sup>b</sup> = 10<sup>[b*log(x) + c]</sup><br />
<br />
Taking the log of both sides we obtain:<br />
<br />
log(a) + b*log(x) = b*log(x) + c<br />
<br />
log(a) = c<br />
<br />
Indeed, if we take the log of 8147.76 we get 3.911<br />
<br />
Bear
in mind that the above equations apply only to TGS2610 ID#15.
Nevertheless, upon subjecting ID#10 and #20 to power regression and computing for V<sub>OUT(std)</sub> as a percentage of V<sub>DD</sub> I obtained results which confirmed the guess that they'd all
show practically the same characteristic voltage outputs given their
respective voltage divider resistor values. Click the "Vout as %Vdd" tab at the bottom of the following <a href="https://docs.google.com/spreadsheet/ccc?key=0AuWFzMAGPO9vdElBMGFDYVlrOHU4TThtWmZFS2s3Mnc" target="_blank">spreadsheet</a> to see the computed values. Take a look at the columns with blue headers. The percentages for each of the three ID# are practically the same for every %LEL.<br />
<br />
<h3>
Determining % LEL</h3>
<br />
There's at least two ways--when using a microcontroller--to determine the % LEL: direct computation and use of a look-up table.<br />
<br />
<h4 style="margin: 1em 0">
I. Direct Computation</h4>
The direct way is to derive a formula which the microcontroller can use to calculate the amount of gas detected (as % LEL). <br />
<br />
Given the voltage divider in the TGS2610 sensor and from our definition of standard values we infer that:<br />
<br />
<a href="http://www.codecogs.com/eqnedit.php?latex=V_%7BOUT%28std%29%7D%20=%20%5Cleft%20[%5Cfrac%7BR_%7BL%7D%7D%7BR_%7BL%7D@plus;R_%7BS%28std%29%7D%7D%5Cright%20]%20V_%7BDD%7D" target="_blank"><img src="http://latex.codecogs.com/gif.latex?V_%7BOUT%28std%29%7D%20=%20%5Cleft%20[%5Cfrac%7BR_%7BL%7D%7D%7BR_%7BL%7D+R_%7BS%28std%29%7D%7D%5Cright%20]%20V_%7BDD%7D" title="V_{OUT(std)} = \left [\frac{R_{L}}{R_{L}+R_{S(std)}}\right ] V_{DD}" /></a>
<br />
<br />
From the above power regression we have:<br />
<br />
<a href="http://www.codecogs.com/eqnedit.php?latex=R_%7BS%28std%29%7D=ax%5E%7Bb%7D" target="_blank"><img src="http://latex.codecogs.com/gif.latex?R_%7BS%28std%29%7D=ax%5E%7Bb%7D" title="R_{S(std)}=ax^{b}" /></a>
<br />
<br />
Therefore:<br />
<a href="http://www.codecogs.com/eqnedit.php?latex=V_%7BOUT%28std%29%7D%20=%20%5Cleft%20[%5Cfrac%7BR_%7BL%7D%7D%7BR_%7BL%7D@plus;ax%5E%7Bb%7D%7D%5Cright%20]%20V_%7BDD%7D" target="_blank"><img src="http://latex.codecogs.com/gif.latex?V_%7BOUT%28std%29%7D%20=%20%5Cleft%20[%5Cfrac%7BR_%7BL%7D%7D%7BR_%7BL%7D+ax%5E%7Bb%7D%7D%5Cright%20]%20V_%7BDD%7D" title="V_{OUT(std)} = \left [\frac{R_{L}}{R_{L}+ax^{b}}\right ] V_{DD}" /></a>
<br />
<br />
Given V<sub>OUT(act)</sub> we can obtain V<sub>OUT(std)</sub> by applying the corrective factor:<br />
<br />
<a href="http://www.codecogs.com/eqnedit.php?latex=V_%7BOUT%28std%29%7D%20=%20%5Cfrac%7BV_%7BOUT%28act%29%7D%7D%7B%5Cleft%20%28%5Cfrac%20%7B2V_%7BREF%7D%7D%7BV_%7BDD%7D%7D%20%5Cright%20%29%7D%20=%20V_%7BOUT%28act%29%7D%5Cfrac%20%7BV_%7BDD%7D%7D%7B2V_%7BREF%7D%7D" target="_blank"><img src="http://latex.codecogs.com/gif.latex?V_%7BOUT%28std%29%7D%20=%20%5Cfrac%7BV_%7BOUT%28act%29%7D%7D%7B%5Cleft%20%28%5Cfrac%20%7B2V_%7BREF%7D%7D%7BV_%7BDD%7D%7D%20%5Cright%20%29%7D%20=%20V_%7BOUT%28act%29%7D%5Cfrac%20%7BV_%7BDD%7D%7D%7B2V_%7BREF%7D%7D" title="V_{OUT(std)} = \frac{V_{OUT(act)}}{\left (\frac {2V_{REF}}{V_{DD}} \right )} = V_{OUT(act)}\frac {V_{DD}}{2V_{REF}}" /></a> <br />
<a href="http://www.codecogs.com/eqnedit.php?latex=V_%7BOUT%28std%29%7D%20=%20V_%7BOUT%28act%29%7D%20%5Cfrac%20%7BV_%7BDD%7D%7D%7B2V_%7BREF%7D%7D" target="_blank"><br /></a>
<br />
Rearranging the terms, substituting and solving for x:<br />
<br />
<a href="http://www.codecogs.com/eqnedit.php?latex=V_%7BOUT%28act%29%7D%20=%20V_%7BOUT%28std%29%7D%20%5Cfrac%7B2V_%7BREF%7D%7D%7BV_%7BDD%7D%7D" target="_blank"><img src="http://latex.codecogs.com/gif.latex?V_%7BOUT%28act%29%7D%20=%20V_%7BOUT%28std%29%7D%20%5Cfrac%7B2V_%7BREF%7D%7D%7BV_%7BDD%7D%7D" title="V_{OUT(act)} = V_{OUT(std)} \frac{2V_{REF}}{V_{DD}}" /></a>
<br />
<br />
<a href="http://www.codecogs.com/eqnedit.php?latex=V_%7BOUT%28act%29%7D%20=%20%5Cleft%20[%5Cfrac%7BR_%7BL%7D%7D%7BR_%7BL%7D@plus;ax%5E%7Bb%7D%7DV_%7BDD%7D%20%5Cright%20]%5Cfrac%7B2V_%7BREF%7D%7D%7BV_%7BDD%7D%7D" target="_blank"><img src="http://latex.codecogs.com/gif.latex?V_%7BOUT%28act%29%7D%20=%20%5Cleft%20[%5Cfrac%7BR_%7BL%7D%7D%7BR_%7BL%7D+ax%5E%7Bb%7D%7DV_%7BDD%7D%20%5Cright%20]%5Cfrac%7B2V_%7BREF%7D%7D%7BV_%7BDD%7D%7D" title="V_{OUT(act)} = \left [\frac{R_{L}}{R_{L}+ax^{b}}V_{DD} \right ]\frac{2V_{REF}}{V_{DD}}" /></a>
<br />
<br />
<a href="http://www.codecogs.com/eqnedit.php?latex=V_%7BOUT%28act%29%7D%20=%20%5Cfrac%7B2V_%7BREF%7DR_%7BL%7D%7D%7BR_%7BL%7D@plus;ax%5E%7Bb%7D%7D" target="_blank"><img src="http://latex.codecogs.com/gif.latex?V_%7BOUT%28act%29%7D%20=%20%5Cfrac%7B2V_%7BREF%7DR_%7BL%7D%7D%7BR_%7BL%7D+ax%5E%7Bb%7D%7D" title="V_{OUT(act)} = \frac{2V_{REF}R_{L}}{R_{L}+ax^{b}}" /></a>
<br />
<br />
<a href="http://www.codecogs.com/eqnedit.php?latex=R_%7BL%7D@plus;ax%5E%7Bb%7D%20=%20%5Cfrac%7B2V_%7BREF%7DR_%7BL%7D%7D%7BV_%7BOUT%28act%29%7D%7D" target="_blank"><img src="http://latex.codecogs.com/gif.latex?R_%7BL%7D+ax%5E%7Bb%7D%20=%20%5Cfrac%7B2V_%7BREF%7DR_%7BL%7D%7D%7BV_%7BOUT%28act%29%7D%7D" title="R_{L}+ax^{b} = \frac{2V_{REF}R_{L}}{V_{OUT(act)}}" /></a>
<br />
<br />
<a href="http://www.codecogs.com/eqnedit.php?latex=ax%5E%7Bb%7D%20=%20%5Cfrac%7B2V_%7BREF%7DR_%7BL%7D%7D%7BV_%7BOUT%28act%29%7D%7D%20-%20R_%7BL%7D" target="_blank"><img src="http://latex.codecogs.com/gif.latex?ax%5E%7Bb%7D%20=%20%5Cfrac%7B2V_%7BREF%7DR_%7BL%7D%7D%7BV_%7BOUT%28act%29%7D%7D%20-%20R_%7BL%7D" title="ax^{b} = \frac{2V_{REF}R_{L}}{V_{OUT(act)}} - R_{L}" /></a>
<br />
<br />
<a href="http://www.codecogs.com/eqnedit.php?latex=x%5E%7Bb%7D%20=%20%5Cleft%20[%20%5Cfrac%7BR_%7BL%7D%7D%7Ba%7D%20%5Cright%20]%20%5Cleft%20[%5Cfrac%7B2V_%7BREF%7D%7D%7BV_%7BOUT%28act%29%7D%7D%20-%201%20%5Cright%20]" target="_blank"><img src="http://latex.codecogs.com/gif.latex?x%5E%7Bb%7D%20=%20%5Cleft%20[%20%5Cfrac%7BR_%7BL%7D%7D%7Ba%7D%20%5Cright%20]%20%5Cleft%20[%5Cfrac%7B2V_%7BREF%7D%7D%7BV_%7BOUT%28act%29%7D%7D%20-%201%20%5Cright%20]" title="x^{b} = \left [ \frac{R_{L}}{a} \right ] \left [\frac{2V_{REF}}{V_{OUT(act)}} - 1 \right ]" /></a>
<br />
<br />
<a href="http://www.codecogs.com/eqnedit.php?latex=x%20=%20%5Csqrt[b]%7B%5Cleft%20[%20%5Cfrac%7BR_%7BL%7D%7D%7Ba%7D%20%5Cright%20]%20%5Cleft%20[%5Cfrac%7B2V_%7BREF%7D%7D%7BV_%7BOUT%28act%29%7D%7D%20-%201%20%5Cright%20]%7D" target="_blank"><img src="http://latex.codecogs.com/gif.latex?x%20=%20%5Csqrt[b]%7B%5Cleft%20[%20%5Cfrac%7BR_%7BL%7D%7D%7Ba%7D%20%5Cright%20]%20%5Cleft%20[%5Cfrac%7B2V_%7BREF%7D%7D%7BV_%7BOUT%28act%29%7D%7D%20-%201%20%5Cright%20]%7D" title="x = \sqrt[b]{\left [ \frac{R_{L}}{a} \right ] \left [\frac{2V_{REF}}{V_{OUT(act)}} - 1 \right ]}" /></a>
<br />
<br />
R<sub>L</sub> is a constant and will be contingent upon the particular TGS2610 unit being used (its ID# will determine what R<sub>L</sub>will be).<br />
<br />
<br />
<h4 style="margin: 1em 0">
II. Look-up Table</h4>
The other method--which is less CPU intensive--would be to compare the standardized V<sub>OUT</sub> as a fraction of V<sub>DD</sub> with the values in a look-up table to determine % LEL (eg., to the nearest %). The look-up table is populated with values computed using the following formula, where x = %LEL and <i>a</i> and <i>b</i> derived from power regression:<br />
<br />
<a href="http://www.codecogs.com/eqnedit.php?latex=%5Cfrac%7BV_%7BOUT%28std%29%7D%7D%7BV_%7BDD%7D%7D%20=%20%5Cfrac%7BR_%7BL%7D%7D%7BR_%7BL%7D@plus;ax%5E%7Bb%7D%7D" target="_blank"><img src="http://latex.codecogs.com/gif.latex?%5Cfrac%7BV_%7BOUT%28std%29%7D%7D%7BV_%7BDD%7D%7D%20=%20%5Cfrac%7BR_%7BL%7D%7D%7BR_%7BL%7D+ax%5E%7Bb%7D%7D" title="\frac{V_{OUT(std)}}{V_{DD}} = \frac{R_{L}}{R_{L}+ax^{b}}" /></a>
<br />
<br />
As we already obtained in the direct computation method:<br />
<br />
<a href="http://www.codecogs.com/eqnedit.php?latex=V_%7BOUT%28std%29%7D%20=%20V_%7BOUT%28act%29%7D%20%5Cfrac%20%7BV_%7BDD%7D%7D%7B2V_%7BREF%7D%7D" target="_blank"><img src="http://latex.codecogs.com/gif.latex?V_%7BOUT%28std%29%7D%20=%20V_%7BOUT%28act%29%7D%20%5Cfrac%20%7BV_%7BDD%7D%7D%7B2V_%7BREF%7D%7D" title="V_{OUT(std)} = V_{OUT(act)} \frac {V_{DD}}{2V_{REF}}" /></a> <br />
<br />
We then divide V<sub>OUT(std)</sub> by V<sub>DD</sub> to obtain a unitless fraction:<br />
<br />
<a href="http://www.codecogs.com/eqnedit.php?latex=%5Cfrac%7BV_%7BOUT%28std%29%7D%7D%7BV_%7BDD%7D%7D%20=%20%5Cfrac%20%7BV_%7BOUT%28act%29%7D%7D%7B2V_%7BREF%7D%7D" target="_blank"><img src="http://latex.codecogs.com/gif.latex?%5Cfrac%7BV_%7BOUT%28std%29%7D%7D%7BV_%7BDD%7D%7D%20=%20%5Cfrac%20%7BV_%7BOUT%28act%29%7D%7D%7B2V_%7BREF%7D%7D" title="\frac{V_{OUT(std)}}{V_{DD}} = \frac {V_{OUT(act)}}{2V_{REF}}" /></a>
<br />
<br />
So all the MCU has to do is evaluate the relatively simply equation on the right and then look for the closest value in the table to determine %LEL.erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com0tag:blogger.com,1999:blog-4092193417928690079.post-3696540595117461632012-06-18T22:11:00.001+08:002012-06-18T22:11:07.061+08:00June 6, 2012 Venus transit picsAnother off topic entry. Should've uploaded these images on the day I took them. Transit began about an hour after sunrise and ended noon. It was a depressingly cloudy day and I had to keep monitoring the skies and took pics whenever there was a break. Never actually had absolutely clear skies--haze and high altitude cirrus clouds were practically a fixture. The first five images below were obviously taken even with clouds in the way (you hear Joni Mitchell singing Both Sides Now?). Well, at least I got something. It <i>is</i> a once-in-a-lifetime event, you know. <br />
<br />
Camera used was an el cheapo <a href="http://www.usa.canon.com/cusa/support/consumer/digital_cameras/powershot_a_series/powershot_a480#Specifications" target="_blank">Canon Powershot A480</a> point and shoot. I used one green-tinted welding goggle glass pane during the first couple of hours but doubled it by around 9am. I just held the glass in front of the lens. Camera was for most part mounted on a tripod. ISO rating was initially set to 80 but in most of the shots it was in auto. Maximum optical + digital zoom using spot metering with the sun centered in the metering zone. Bracketing was performed throughout the shoot--I incremented/decremented exposure compensation by one notch (1/3 stop). Shot some 250 frames, 80% of which are either severely overexposed or underexposed, blurry from camera shake (two welding glass plates is so effective in limiting sunlight that shutter speed went as low as 1/60 sec), lack of focus or hazy due to cloud cover. Venus of course crossed the sun in a straight line but because I moved the camera the planet seems to be all over the place.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVDulPxjNSiJ5m0haxHjqDokYCZRNtCqnuctxsfbTI31ClJH4RK5Y46MC8jSRORaPIZbC-cGTC6dnB7zlPwFFJtoYfFGGez0rK-rvNbYBFCXhYlpD-tjCcd3H50ZjH9EKwdcTWY64d6yw/s1600/venus-transit-1622.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVDulPxjNSiJ5m0haxHjqDokYCZRNtCqnuctxsfbTI31ClJH4RK5Y46MC8jSRORaPIZbC-cGTC6dnB7zlPwFFJtoYfFGGez0rK-rvNbYBFCXhYlpD-tjCcd3H50ZjH9EKwdcTWY64d6yw/s320/venus-transit-1622.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNl6LxYxoKvjYVSWxhEazVZ1Cg7WMvjxIKgrToDjsSiGRfWSqUc7JRpzAGuZ3fXM_Zd4pU5UrYrUCWsCq0Ac1hDRRCEGkKQVEMZL5ZcS5E8zEUtiSBEKAs47X87BpW92GP0y-pe7bIa24/s1600/venus-transit-1630.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNl6LxYxoKvjYVSWxhEazVZ1Cg7WMvjxIKgrToDjsSiGRfWSqUc7JRpzAGuZ3fXM_Zd4pU5UrYrUCWsCq0Ac1hDRRCEGkKQVEMZL5ZcS5E8zEUtiSBEKAs47X87BpW92GP0y-pe7bIa24/s320/venus-transit-1630.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0tIm3hXmKOWRIPdhKP8MlUxMZhzd-iEAejesf_9wa0x_1aTf65JBDQb4HpztQ7pnT9F6cUpttZpKIwWSMYM9vzx2JtrqCmAN-QVYfhHzCZK2doI5HvMv8PE7SX6qin0jxIMbf6WkQzb4/s1600/venus-transit1631.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0tIm3hXmKOWRIPdhKP8MlUxMZhzd-iEAejesf_9wa0x_1aTf65JBDQb4HpztQ7pnT9F6cUpttZpKIwWSMYM9vzx2JtrqCmAN-QVYfhHzCZK2doI5HvMv8PE7SX6qin0jxIMbf6WkQzb4/s320/venus-transit1631.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBhOZnIylj2EWSymHCLYtAz6XgtM-vir41F4nFFl942tlXfbCW9bhNqJHjQCnCcizr0vll7KGBl0tHEVbP4tQPcZpGAkj0NTMx240A5pDFi480pzvLiHr2GFEXas03J3T2k7O-8IyEElc/s1600/venus-transit-1705.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBhOZnIylj2EWSymHCLYtAz6XgtM-vir41F4nFFl942tlXfbCW9bhNqJHjQCnCcizr0vll7KGBl0tHEVbP4tQPcZpGAkj0NTMx240A5pDFi480pzvLiHr2GFEXas03J3T2k7O-8IyEElc/s320/venus-transit-1705.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1qvOaPAmNuF7uWO61gAj7zy0la5yPTN-y5rlhIMrDZM9OeebLBd6Q8Ttqv2B0zNdKGgc-16wGh9A6Yz9uyaIXQbyXeoHCNyT_AQdmQRHCxM8W6pPqRx9ZBnNa5US2yUFvjO5mJCNquew/s1600/venus-transit-1716.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj1qvOaPAmNuF7uWO61gAj7zy0la5yPTN-y5rlhIMrDZM9OeebLBd6Q8Ttqv2B0zNdKGgc-16wGh9A6Yz9uyaIXQbyXeoHCNyT_AQdmQRHCxM8W6pPqRx9ZBnNa5US2yUFvjO5mJCNquew/s320/venus-transit-1716.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEJ6E73rlBk3XLJxrXXXwNsPzIwuaqOLNRh6f8lni9qgcsq6GEDrTkvwszo9FQBEpyqOuOQYOaNzIqJDWljgTrXlY8EHgX91nHEQc8jCa7KoQ8OZ6loOWRLYZRlaOo-I_vjMi-xLxhWFQ/s1600/venus-transit-1751.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEJ6E73rlBk3XLJxrXXXwNsPzIwuaqOLNRh6f8lni9qgcsq6GEDrTkvwszo9FQBEpyqOuOQYOaNzIqJDWljgTrXlY8EHgX91nHEQc8jCa7KoQ8OZ6loOWRLYZRlaOo-I_vjMi-xLxhWFQ/s320/venus-transit-1751.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6r5sFOaveqgJy0GO5_RRgjhqJ0-hLOJnXJHyAR79V6XahF54Gbc92asEFe7e9_tkVqRFyiA_s3ds4JoFNgbQ-gqRpbYkxIU2gQwfbOf64A_ReX89HHzfFA25CdHN4UNFWzoQJCHjphO4/s1600/venus-transit-1760.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6r5sFOaveqgJy0GO5_RRgjhqJ0-hLOJnXJHyAR79V6XahF54Gbc92asEFe7e9_tkVqRFyiA_s3ds4JoFNgbQ-gqRpbYkxIU2gQwfbOf64A_ReX89HHzfFA25CdHN4UNFWzoQJCHjphO4/s320/venus-transit-1760.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhi8slfXlvQekQ1SkhdcUsJKa0dDMBuhOEkBQokzNFvm4ca1aGdx0jvIkmFS8ydhUF41qtgr3C6TV9YjWMgCnOJt8qd5MoOqL6-W0YAuqNN3yuFgY93cUEWEE8Mm45LZECO7_1n20Fgvdk/s1600/venus-transit-1774.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhi8slfXlvQekQ1SkhdcUsJKa0dDMBuhOEkBQokzNFvm4ca1aGdx0jvIkmFS8ydhUF41qtgr3C6TV9YjWMgCnOJt8qd5MoOqL6-W0YAuqNN3yuFgY93cUEWEE8Mm45LZECO7_1n20Fgvdk/s320/venus-transit-1774.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj86jujRMbCXMlWgxBCQCvus3ngdwOumftsefW72dJM6Fd4Z3A1VjjCI2DMee7RGwyRFOKJmeuNNItm4fT9uOPB0qKnBUWkB_OCt6Q6iS8rkdd1OS5VDgEd3oxKFDKp1ENswhw7xNVvzEk/s1600/venus-transit-1787.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj86jujRMbCXMlWgxBCQCvus3ngdwOumftsefW72dJM6Fd4Z3A1VjjCI2DMee7RGwyRFOKJmeuNNItm4fT9uOPB0qKnBUWkB_OCt6Q6iS8rkdd1OS5VDgEd3oxKFDKp1ENswhw7xNVvzEk/s320/venus-transit-1787.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUeMJFr-L9DPRBhA-7Bxan4Z70YTshPA-bRpyiVh0Vu0bfGSdWgqml4RlHkIvfC1w1MfSKJuRbX_Rkbo2cWpfySF1wWCJZw-2jiMIezw9w5IBDJYxI_DdHmKAEaPSb2WgDvzrw8uSBSU4/s1600/venus-transit-1813.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUeMJFr-L9DPRBhA-7Bxan4Z70YTshPA-bRpyiVh0Vu0bfGSdWgqml4RlHkIvfC1w1MfSKJuRbX_Rkbo2cWpfySF1wWCJZw-2jiMIezw9w5IBDJYxI_DdHmKAEaPSb2WgDvzrw8uSBSU4/s320/venus-transit-1813.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIZGlqTZUTPICPo7Y2wtIwRny3Cj22yKeca7MM7AW8DuVXzzJ4IP8R_SBmXHxhPBMhwj9Kc1T0i_d4a5kLI-cRLyXJwOBTf4n_VG0Lts3iJ3Y44E3-cKGoTZbMjifrCzABd2q7ZmxdPVY/s1600/venus-transit-1817.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIZGlqTZUTPICPo7Y2wtIwRny3Cj22yKeca7MM7AW8DuVXzzJ4IP8R_SBmXHxhPBMhwj9Kc1T0i_d4a5kLI-cRLyXJwOBTf4n_VG0Lts3iJ3Y44E3-cKGoTZbMjifrCzABd2q7ZmxdPVY/s320/venus-transit-1817.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNC1KW7PBGOfL5J5-IwkBVTdLd4KVvwtO1LWZtQ5lOF3SBkhjWw9zyZ5zYWWv3f62V0czQ_A_zG7zrUSi-X5Jb6Qp-CX6S9FdTHPXnT5UmKmrUFkDO247Lff7l-3lhCJ06BLbVIdkz2w4/s1600/venus-transit-1819.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNC1KW7PBGOfL5J5-IwkBVTdLd4KVvwtO1LWZtQ5lOF3SBkhjWw9zyZ5zYWWv3f62V0czQ_A_zG7zrUSi-X5Jb6Qp-CX6S9FdTHPXnT5UmKmrUFkDO247Lff7l-3lhCJ06BLbVIdkz2w4/s320/venus-transit-1819.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5i_XGtHBnyFpXx_PtvuolOOcUeAcfqbUULVS0C3ep9tTe8pb4tIbIu2t2JB6GR4svk4P33fPz_Rb8bjiDk6MHxbbG9S1xQ-ruEEYMzlvhHsbiy-PaR7HhZqoQ0o-cixRZ-Idcf2Yohiw/s1600/venus-transit-1836.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5i_XGtHBnyFpXx_PtvuolOOcUeAcfqbUULVS0C3ep9tTe8pb4tIbIu2t2JB6GR4svk4P33fPz_Rb8bjiDk6MHxbbG9S1xQ-ruEEYMzlvhHsbiy-PaR7HhZqoQ0o-cixRZ-Idcf2Yohiw/s320/venus-transit-1836.png" width="320" /></a></div>
<br />erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com1tag:blogger.com,1999:blog-4092193417928690079.post-66925627498372516252012-06-17T03:06:00.000+08:002012-06-17T03:16:26.882+08:00Braking distance and time on level road and on an inclineOff topic entry. Nothing to do with electronics. But I've been obsessed with (or should I say possessed by) this topic for the past week or so. Just want to have all the equations and derivations in one place so I can refresh my memory if I need to in the future. <br />
<br />
Let<br />
v<sub>i</sub> = initial velocity, m/s<br />
v<sub>f</sub> = final velocity, m/s<br />
<span style="text-decoration: overline;">v</span> = average velocity, m/s<br />
a = deceleration rate, m/s<sup>2</sup><br />
t = time to stop the vehicle, s<br />
d = braking distance, m<br />
μ = coefficient of friction, unitless<br />
F<sub>f</sub> = force of friction, kg·m/s<sup>2</sup><br />
F<sub>n</sub> = normal force (force perpendicular to surface), kg<i></i>·m/s<sup>2</sup><br />
F<sub>g</sub> = force due gravity pulling the vehicle down a slope, kg·m/s<sup>2</sup><br />
m = mass, kg<br />
g = gravitational acceleration, 9.8 m/s<sup>2</sup><br />
θ = angle of inclination (positive if vehicle going uphill, negative if going downhill)<br />
<br />
<a href="http://hyperphysics.phy-astr.gsu.edu/hbase/mot.html" target="_blank">Final velocity</a> is given by the equation:<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?v_%7Bf%7D=v_%7Bi%7D+-at" title="v_{f}=v_{i}+-at" /><br />
<br />
If v<sub>f</sub> = 0 then<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?t=%5Cfrac%7Bv_%7Bi%7D%7D%7Ba%7D" title="t=\frac{v_{i}}{a}" /><br />
<br />
<a href="http://hyperphysics.phy-astr.gsu.edu/hbase/mot.html" target="_blank">Distance</a> is given by:<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?d=%5Coverline%7Bv%7Dt" title="d=\overline{v}t" /><br />
<br />
<a href="http://hyperphysics.phy-astr.gsu.edu/hbase/mot.html" target="_blank">Average velocity</a> is:<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?%5Coverline%7Bv%7D=%5Cfrac%7Bv_%7Bi%7D+v_%7Bf%7D%7D%7B2%7D" title="\overline{v}=\frac{v_{i}+v_{f}}{2}" /><br />
<br />
Given v<sub>f</sub> = 0 and substituting we have:<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?d=%5Cfrac%7Bv_%7Bi%7D%7D%7B2%7D%5Cfrac%7Bv_%7Bi%7D%7D%7Ba%7D=%5Cfrac%7Bv_%7Bi%7D%5E%7B2%7D%7D%7B2a%7D" title="d=\frac{v_{i}}{2}\frac{v_{i}}{a}=\frac{v_{i}^{2}}{2a}" /><br />
<br />
Alternatively we can derive the distance formula using kinetic energy and work equations:<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?%5Ctextup%7Bkinetic%20energy%7D=%20%5Cfrac%7B1%7D%7B2%7Dmv_%7Bi%7D%5E%7B2%7D" title="\textup{kinetic energy}= \frac{1}{2}mv_{i}^{2}" /><br />
<br />
<img src="http://latex.codecogs.com/gif.latex?F=ma" title="F=ma" /><br />
<br />
<img src="http://latex.codecogs.com/gif.latex?%5Ctextup%7Bwork%7D%20=%20Fd%20=%20mad" title="\textup{work} = Fd = mad" /><br />
<br />
Equating work and energy we have:<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?mad=%20%5Cfrac%7B1%7D%7B2%7Dmv_%7Bi%7D%5E%7B2%7D" title="mad= \frac{1}{2}mv_{i}^{2}" /><br />
<br />
Solving for d we obtain:<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?d=%5Cfrac%7Bv_%7Bi%7D%5E%7B2%7D%7D%7B2a%7D" title="d=\frac{v_{i}^{2}}{2a}" /><br />
<br />
The <a href="http://physics.bu.edu/%7Eduffy/py105/Friction.html" target="_blank">force of friction</a> is given by the following formula:<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?F_%7Bf%7D=%5Cmu%20F_%7Bn%7D" title="F_{f}=\mu F_{n}" /><br />
<br />
If the vehicle is on a level surface then normal force is just its weight. Therefore,<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?F_%7Bf%7D=%5Cmu%20mg" title="F_{f}=\mu mg" /><br />
<br />
Since<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?F=ma" title="F=ma" /><br />
<br />
then<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?ma%20=%5Cmu%20mg" title="ma =\mu mg" /><br />
<br />
Therefore,<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?a%20=%5Cmu%20g" title="a =\mu g" /><br />
<br />
Substituting the above into the distance and time equations we get:<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?d=%5Cfrac%7Bv_%7Bi%7D%5E%7B2%7D%7D%7B2%5Cmu%20g%7D=%5Cfrac%7Bv_%7Bi%7Dt%7D%7B2%7D" title="d=\frac{v_{i}^{2}}{2\mu g}=\frac{v_{i}t}{2}" /><br />
<br />
<img src="http://latex.codecogs.com/gif.latex?t=%5Cfrac%7Bv_%7Bi%7D%7D%7Bug%7D=%5Cfrac%7B2d%7D%7Bv_%7Bi%7D%7D" title="t=\frac{v_{i}}{ug}=\frac{2d}{v_{i}}" /><br />
<br />
Interesting to note that we can (at least in principle) determine the coefficient of friction by noting the velocity when the brakes are fully applied and time to bring the vehicle to a dead stop. <br />
<br />
<br />
Now let's tackle the situation when the vehicle is braking on a slope.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwt7zr2o2twvUEc7neeWXgLrodxNJqaQQbTSBaf23B9Y_NTz_KL-phUPbjlmrmkhmLsK0-tHVqCnemRzbIsYt4nqzsM9HtFjC4LzD-3Wf2C37Y_ijmGfJW2YWemSNA7eYhsH_VMwwFi5w/s1600/vector-diagram-friction.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjwt7zr2o2twvUEc7neeWXgLrodxNJqaQQbTSBaf23B9Y_NTz_KL-phUPbjlmrmkhmLsK0-tHVqCnemRzbIsYt4nqzsM9HtFjC4LzD-3Wf2C37Y_ijmGfJW2YWemSNA7eYhsH_VMwwFi5w/s320/vector-diagram-friction.png" width="320" /></a></div>
When the vehicle is on an incline then a portion of the gravitational
force acts to pull the vehicle down the slope. This force we denote as F<sub>g</sub> and is
derived via basic vector analysis. Refer to the <a href="http://en.wikipedia.org/wiki/Free_body_diagram" target="_blank">free-body diagram.</a> On the left of the diagram is an object on an incline with an angle of
θ. Its weight is labeled as mg. Normal force F<sub>n</sub> is the component of mg that is perpendicular to the incline. F<sub>g</sub> is the component of mg that is parallel to the incline. To the right I've drawn a vector addition of the forces. Since this is a right triangle we can use the pertinent trigonometric functions: Sin θ = opposite side / hypotenuse. Cos θ = adjacent side / hypotenuse. Since the hypotenuse = mg we obtain the following: <br />
<br />
<img src="http://latex.codecogs.com/gif.latex?F_%7Bn%7D=mg%5Ccos%20%5Ctheta" title="F_{n}=mg\cos \theta" /><br />
<br />
<img src="http://latex.codecogs.com/gif.latex?F_%7Bg%7D=mg%5Csin%20%5Ctheta" title="F_{g}=mg\sin \theta" /><br />
<br />
An aside: Since the above are the legs of the right triangle it follows from the Pythagorean theorem that<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?%5Ctextup%7Bweight%7D%20=%20mg%20=%20%5Csqrt%7B%20%5Cleft%20%28mg%5Csin%20%5Ctheta%20%5Cright%20%29%5E%7B2%7D%20+%20%5Cleft%20%28mg%5Ccos%20%5Ctheta%20%5Cright%20%29%5E2%7D" title="\textup{weight} = mg = \sqrt{ \left (mg\sin \theta \right )^{2} + \left (mg\cos \theta \right )^2}" /><br />
<br />
<br />
When on a slope with the brakes engaged so that the vehicle is at a
standstill F<sub>f</sub> is the force that keeps the vehicle from slipping and
sliding down and acts in the opposite direction as F<sub>g</sub> which is
pulling it down the incline. The net force holding the vehicle in place is
therefore F<sub>f</sub> - F<sub>g</sub>. It stands to reason that when F<sub>g</sub> becomes >=
F<sub>f</sub> then the vehicle--even with the brake pads fully gripping the brake
discs and drums--will begin to slip and slide. We can
calculate the angle of inclination at which this will occur:<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?F_%7Bf%7D=%5Cmu%20F_%7Bn%7D=F_%7Bg%7D" title="F_{f}=\mu F_{n}=F_{g}" /><br />
<br />
<img src="http://latex.codecogs.com/gif.latex?%5Cmu%20F_%7Bn%7D=F_%7Bg%7D" title="\mu F_{n}=F_{g}" /><br />
<br />
<img src="http://latex.codecogs.com/gif.latex?%5Cmu%20mg%5Ccos%20%5Ctheta%20=%20mg%5Csin%20%5Ctheta" title="\mu mg\cos \theta = mg\sin \theta" /><br />
<br />
<img src="http://latex.codecogs.com/gif.latex?%5Cmu%20=%20%5Cfrac%7B%5Csin%20%5Ctheta%7D%7B%5Ccos%20%5Ctheta%20%7D%20=%20%5Ctan%20%5Ctheta" title="\mu = \frac{\sin \theta}{\cos \theta } = \tan \theta" /><br />
<br />
<img src="http://latex.codecogs.com/gif.latex?%5Ctheta%20=%20%5Carctan%20%5Cmu" title="\theta = \arctan \mu" /><br />
<br />
Bear in mind that in this case we are using the static coefficient of friction rather than kinetic (or sliding) coefficient of friction since the vehicle is at rest with respect to the road.<br />
<br />
When a vehicle is going downhill its braking distance will increase because a fraction of
the gravitational force is acting to pull it down even when the brakes are applied.
θ will be negative if going downhill, and so F<sub>g</sub> will be negative. When going uphill part of gravitational force adds to the force of friction thereby decreasing braking distance. <br />
<br />
The sum of forces acting on the vehicle parallel to its direction of motion is given by:<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?F_%7Bf%7D+F_%7Bg%7D" title="F_{f}+F_{g}" /><br />
<br />
Since<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?F_%7Bf%7D%20=%5Cmu%20F_%7Bn%7D" title="F_{f} =\mu F_{n}" /><br />
<br />
Therefore,<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?F_%7Bf%7D%20+%20F_%7Bg%7D%20=%20%5Cmu%20F_%7Bn%7D%20+%20F_%7Bg%7D" title="F_{f} + F_{g} = \mu F_{n} + F_{g}" /><br />
<br />
<img src="http://latex.codecogs.com/gif.latex?=%20%5Cmu%20mg%5Ccos%20%5Ctheta%20+%20mg%5Csin%20%5Ctheta%20=%20mg%5Cleft%20%28%20%5Cmu%20%5Ccos%20%5Ctheta%20+%20%5Csin%20%5Ctheta%20%5Cright%20%29" title="= \mu mg\cos \theta + mg\sin \theta = mg\left ( \mu \cos \theta + \sin \theta \right )" /><br />
<br />
Since<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?F=ma" title="F=ma" /><br />
<br />
<img src="http://latex.codecogs.com/gif.latex?ma%20=%20mg%5Cleft%20%28%20%5Cmu%20%5Ccos%20%5Ctheta%20+%20%5Csin%20%5Ctheta%20%5Cright%20%29" title="ma = mg\left ( \mu \cos \theta + \sin \theta \right )" /><br />
<br />
<img src="http://latex.codecogs.com/gif.latex?a%20=%20g%5Cleft%20%28%20%5Cmu%20%5Ccos%20%5Ctheta%20+%20%5Csin%20%5Ctheta%20%5Cright%20%29" title="a = g\left ( \mu \cos \theta + \sin \theta \right )" /><br />
<br />
Substituting into our distance and time equations we obtain:<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?d%20=%20%5Cfrac%20%7Bv_%7Bi%7D%5E2%7D%20%7B2g%5Cleft%20%28%20%5Cmu%20%5Ccos%20%5Ctheta%20+%20%5Csin%20%5Ctheta%20%5Cright%20%29%7D" title="d = \frac {v_{i}^2} {2g\left ( \mu \cos \theta + \sin \theta \right )}" /><br />
<br />
<img src="http://latex.codecogs.com/gif.latex?t%20=%20%5Cfrac%20%7Bv_%7Bi%7D%7D%20%7Bg%5Cleft%20%28%20%5Cmu%20%5Ccos%20%5Ctheta%20+%20%5Csin%20%5Ctheta%20%5Cright%20%29%7D" title="t = \frac {v_{i}} {g\left ( \mu \cos \theta + \sin \theta \right )}" /><br />
<br />
The above are exact equations in contrast to the <a href="http://nexus.umn.edu/Courses/ce3201/CE3201-L3-02.pdf" target="_blank">approximate formula using road grade</a>. The gradient is equal to the rise over run, or tangent of θ. The arctangent of the gradient = angle θ. <br />
<br />
Reminder: when using the above equations θ should be negative when the vehicle is going downhill. Given -90° < θ < 0, sin θ is negative while cos θ is positive. Hence F<sub>g</sub> is effectively subtracted from F<sub>f</sub>. When going uphill and θ is positive then F<sub>f</sub> is effectively increased by amount F<sub>g</sub>. As anyone who's driven knows, it's harder to bring a vehicle to a stop when going downhill. And that it's easier to do so when going uphill than when on a level road. <br />
<br />
Beware that the above distances and times do not take into account the driver's perception and reaction time--the time to perceive the need to stop and the time to actually to apply the brakes. This takes a minimum of <a href="http://www.sdt.com.au/safedrive-directory-STOPPINGDISTANCE.htm" target="_blank">around half a second</a> for a driver who's alert and concentrating on the road, but may be much greater if the driver is drowsy, tired, intoxicated, on his cell phone, has poor eyesight, etc. or if visibility is poor among other possible factors beyond the driver's control. <br />
<br />
The distance traveled by the vehicle during this time is simply<br />
<br />
<img src="http://latex.codecogs.com/gif.latex?v_%7Bi%7Dt_%7Bperception+reaction%7D" title="v_{i}t_{perception+reaction}" /><br />
<br />
<br />
Here are a couple of online braking distance calculators which don't, however, have provisions for vehicles on an incline:<br />
<a href="http://www.csgnetwork.com/stopdistcalc.html">http://www.csgnetwork.com/stopdistcalc.html</a><br />
<a href="http://forensicdynamics.com/stopping-distance-calculator">http://forensicdynamics.com/stopping-distance-calculator</a><br />
<br />
Tables of coefficients of friction:<br />
<a href="http://www.engineeringtoolbox.com/friction-coefficients-d_778.html">http://www.engineeringtoolbox.com/friction-coefficients-d_778.html</a><br />
<a href="http://www.roymech.co.uk/Useful_Tables/Tribology/co_of_frict.htm">http://www.roymech.co.uk/Useful_Tables/Tribology/co_of_frict.htm</a><br />
<br />
Summary of the relevant data from the above tables:<br />
<br />
<table border="1" cellpadding="5" cellspacing="0"><tbody>
<tr><th>tire material</th> <th>road type and condition</th> <th>kinetic coefficient of friction</th>
</tr>
<tr><td>rubber</td> <td>dry concrete</td><td>0.6 - 0.85</td>
</tr>
<tr><td>rubber</td>
<td>wet concrete</td>
<td>0.45 - 0.75</td>
</tr>
<tr><td>rubber</td>
<td>dry asphalt</td>
<td>0.5 - 0.8</td>
</tr>
<tr><td>rubber</td>
<td>wet asphalt</td>
<td>0.25 - 0.75</td>
</tr>
</tbody></table>
<br />
A point worth mentioning: While the wheels are turning static
coefficient of friction is operative because at the point of contact the
tire is not sliding along the road. This is desirable because the
static coefficient is higher than the kinetic
coefficient of friction thus leading to decreased braking distance. Anti-lock brakes (ABS)
prevent the wheels from locking even when the driver slams the brake
pedal, therefore preventing the tires from skidding and sliding.
However, the speed of the tire is lower than the vehicle's and so <a href="http://arxiv.org/pdf/cond-mat/0502495.pdf" target="_blank">some slip occurs between tire and road</a>.
The static coefficient of friction cannot therefore be applied to vehicles even if they're equipped with ABS. For safety reasons it's best to
be conservative and use the kinetic coefficient.<br />
<br />
<br />
The take home lessons from the above formulas:<br />
<br />
Braking distance varies with the square of velocity. Thus, doubling one's speed quadruples the distance. Increasing one's speed from 40kph to 100kph increases the braking distance by (100/40)<sup>2</sup> = 6.25 times. Braking time, on the other hand, increases linearly. And thus, since 100kph is 2.5 times 40kph, braking time at 100kph is 2.5x that at 40kph.<br />
<br />
The worst braking scenario is when the coefficient of friction is low--when the roads are wet, muddy, or iced up--and one is going downhill, with braking time and distance becoming greater as the inclination increases.<br />
<br />
Hence, from the standpoint of safety the best thing a driver can do is to keep speeds low and/or to keep one's distance from other vehicles (and pedestrians) as large as practically possible. One can also apply the <a href="http://www.youtube.com/watch?v=mf5d2DP4Pp0" target="_blank">two-second rule</a>. Although as weather and road conditions worsen this should be increased to <a href="http://en.wikipedia.org/wiki/Two-second_rule" target="_blank">three or four seconds</a>.erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com6tag:blogger.com,1999:blog-4092193417928690079.post-1106251165119142712012-06-13T13:54:00.000+08:002012-06-13T14:06:57.976+08:00Detecting liquified petroleum gas (LPG)<a href="http://www.figaro.co.jp/en/top.html" target="_blank">Figaro</a>
manufactures sensors that can sniff out various kinds of gases (e.g.
hydrogen, methane, carbon monoxide, etc.). I've designed a simple circuit built around the Figaro <a href="http://www.figaro.co.jp/en/data/pdf/20091110164017_59.pdf" target="_blank">LPM2610</a> module which will sound an alarm if and when the level of
liquified petroleum gas (LPG) in the kitchen rises above a certain predetermined
level.<br />
<br />
<span class="hasCaption">I knew the dimensions of the module from the datasheet but when they arrived I
was still quite surprised they're tiny. Nothing against it of course. I
don't want a huge daughter board sitting on my main board.
The surface mount chips are all resistors. No electrostatic discharge
sensitive components here at all. The thermistor is the unmarked black
chip at the upper left corner. </span> <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgu2lmmEgdT4OInJa4E10Wkkkr7FM2FF6y8UlDNkhNY0fJyDTQbMsD4ffNLv8HECmlIdceNlLUweoS2_R71F6kQcKmLuH44Rv9L5qr3SXJZx640-lgihhTSdGWlyRqvpvA59WblpIiTlUE/s1600/LPM2610-a.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgu2lmmEgdT4OInJa4E10Wkkkr7FM2FF6y8UlDNkhNY0fJyDTQbMsD4ffNLv8HECmlIdceNlLUweoS2_R71F6kQcKmLuH44Rv9L5qr3SXJZx640-lgihhTSdGWlyRqvpvA59WblpIiTlUE/s320/LPM2610-a.png" width="320" /></a></div>
<br />
<br />
As per datasheet the dark blue bands on the trimpot should be lined up to ensure it's in the calibrated position. <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLE3tsaAINSm7Hf4JxQu12cVWiCISvOA3kUAt16HVJwhNlUnw-5Gh9aMruI4kp9tyr1m6eb7UM9yYkQxpgs5R39_lijU4ZMmeUIxkinn1zP4bY80IgHexH1mcwHz9c8GHyWXManeiUO4k/s1600/LPM2610-b.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLE3tsaAINSm7Hf4JxQu12cVWiCISvOA3kUAt16HVJwhNlUnw-5Gh9aMruI4kp9tyr1m6eb7UM9yYkQxpgs5R39_lijU4ZMmeUIxkinn1zP4bY80IgHexH1mcwHz9c8GHyWXManeiUO4k/s320/LPM2610-b.png" width="320" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUzwrS9-z3VXWJwjTkDVlpFd-c1np2Mzs5Z0jQwVPLnVl2BBATOLk5ApLvsZCvESZ5Y8gTHUGfPeobyMkkY0Na8vy2On1BV-EdyCdhtY-AyLCZDIvlVoBzs7TQEqFlaLRS4OkYs7CDzIA/s1600/LPM2610-c.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUzwrS9-z3VXWJwjTkDVlpFd-c1np2Mzs5Z0jQwVPLnVl2BBATOLk5ApLvsZCvESZ5Y8gTHUGfPeobyMkkY0Na8vy2On1BV-EdyCdhtY-AyLCZDIvlVoBzs7TQEqFlaLRS4OkYs7CDzIA/s320/LPM2610-c.png" width="320" /></a></div>
<br />
<br />
The Figaro module contains the
<a href="http://www.figaro.co.jp/en/data/pdf/20091110164925_54.pdf" target="_blank">TGS2610</a> sensor and various resistors to create two voltage dividers--one for the sensor output and the other for the reference voltage. Whenever sensor voltage V<sub>OUT</sub> is equal to the temperature-compensated reference voltage this indicates the LPG concentration in the air is (theoretically) 10% of the lower explosive limit (LEL) of
isobutane. As per the datasheet, butane LEL is 18,000 ppm which is equivalent to 1.8% by volume (although <a href="http://www.aristatek.com/Newsletter/02%2011%20November/Technical%20Tidbit.htm" target="_blank">one website</a> puts it at 16,000 ppm or 1.6%).<br />
<br />
In <a href="http://www.figarosensor.com/products/2610app.pdf" target="_blank">Application Note for LP Gas Detectors using TGS2610</a> Figaro provides a wealth of information to get an application up and running. One of the more important tips therein is that the TGS2610 needs a warm up time to get the sensor up to its working temperature. Figaro recommends waiting 2.5 minutes. During this preheating stage the sensor's output is unreliable. In the same AN Figaro suggests circuits to detect heater as well as sensor failures. <br />
<br />
According to the datasheet the typical current through the heater @5.0V is 56mA. Although Figaro suggests using a 3.57-ohm current sense resistor to detect heater failure, at the moment what I have are a couple of 2 ohms. 56mA x 2 ohms = 112mV. Therefore, my V<sub>DD</sub> has to be around 5.1V to obtain a voltage drop of ~5.0V across the heater. So I configured a power supply using an LM317 voltage regulator to output this value. V<sub>OUT</sub> and V<sub>REF</sub> were unity-gain buffered while the current sense voltage was gained by 20. Output of the op amps were hooked up to a <a href="http://www.dataq.com/products/startkit/di145.html" target="_blank">Dataq DI-145</a> to record the values. I then performed a cold start (i.e, the module had not been powered for at least an hour to make sure it's at room temperature).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcVBBVdN_9l9VWlnR2HSFJHwm1aNr5pMHQlwPX8_GGcoCZmo3lLWKyA4pg9JW3qRC0aZc9Bpoktm0bGwOGX8wBd9W7tDKzr2SVj7k49HqleKvTCrte6CuIVLPJ3O-vWQNUFYd7HdmJkqU/s1600/LPM2610-test-circuit.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcVBBVdN_9l9VWlnR2HSFJHwm1aNr5pMHQlwPX8_GGcoCZmo3lLWKyA4pg9JW3qRC0aZc9Bpoktm0bGwOGX8wBd9W7tDKzr2SVj7k49HqleKvTCrte6CuIVLPJ3O-vWQNUFYd7HdmJkqU/s320/LPM2610-test-circuit.png" width="320" /></a></div>
In the screenshot below you can see that while the heater is ramping up to its working temperature V<sub>OUT</sub> gradually settles down (approx 0.3 to 0.4V). This takes >100sec.<br />
<br />
Interestingly, voltage across heater current sense resistor spikes upon power up and then exponentially settles. This inrush current suggests that the heater has a cold resistance lower than its hot resistance--just like the filament of an incandescent lamp. <br />
<br />
Channel 1: V<sub>OUT</sub><br />
Ch2: V<sub>REF</sub><br />
Ch3: voltage across 2-ohm current sense resistor amplified 20x<br />
Ch4: unused<br />
<br />
Vertical: 0.5 volts/div<br />
Horizontal: 5 sec/div<br />
Data acquisition rate: 16 samples/sec <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZ3DyEjtlfezANIWDDU-X_BlWdTb6Nzptbnb2tSNumRg48LsRVNQGNd_Uc2W1ESeoPdt9SuUAfSit0RUwhX10inlM9K5w7nZKb4WE2BD0s05d0XPBDDXPyAuUujbC3rOOuQ2LxEMQel4A/s1600/windaq+lpm2610+a.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZ3DyEjtlfezANIWDDU-X_BlWdTb6Nzptbnb2tSNumRg48LsRVNQGNd_Uc2W1ESeoPdt9SuUAfSit0RUwhX10inlM9K5w7nZKb4WE2BD0s05d0XPBDDXPyAuUujbC3rOOuQ2LxEMQel4A/s320/windaq+lpm2610+a.png" width="320" /></a></div>
<br />
To get a higher resolution and better picture of what's happening during power up I increased the sample rate to the Dataq's maximum of 240/sec. <br />
<br />
What's interesting in the image below is how V<sub>OUT</sub> stays at nearly zero volt (~20 to 30mV according to Windaq) for around 1 second before climbing. Since this is the voltage across the load resistor (R<sub>L</sub>), apparently the semiconductor sensor (R<sub>S</sub>) which is in series with R<sub>L</sub> has an extremely high resistance at room temperature which drops drops dramatically as it begins to heat up, thus behaving like a negative temperature coefficient (NTC) thermistor. However, its resistance begins to gradually increase as it reaches a certain temperature. Figaro has this <a href="http://www.figaro.co.jp/en/item2.html" target="_blank">short intro</a> to the construction and operating principles of its sensors<br />
<br />
Channel 1: V<sub>OUT</sub><br />
Ch2: V<sub>REF</sub><br />
Ch3: voltage across 2ohm current sense resistor and then amplified by a nominal gain of 20<br />
Ch4: V<sub>DD</sub><br />
<br />
Vertical: 0.491 volts/div for Channels 1 to 3, 0.231 V/div for Ch4<br />
Horizontal: 0.33 sec/div<br />
Data acquisition rate: 240 samples/sec<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjn-Ygh03N_nvW_JRhEJ0OhjdbuQrTiqdyP2O1OQttp5_6yIlnJ7xCB4Kn2VgNELHe7k_tYnkvVkxJRR_CXuCosz5zBQhXP30J72CefW-Ls_g96grc9EkLMUYOYxVnZm_EKnKSDXcBD5PU/s1600/windaq+lpm2610+b.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjn-Ygh03N_nvW_JRhEJ0OhjdbuQrTiqdyP2O1OQttp5_6yIlnJ7xCB4Kn2VgNELHe7k_tYnkvVkxJRR_CXuCosz5zBQhXP30J72CefW-Ls_g96grc9EkLMUYOYxVnZm_EKnKSDXcBD5PU/s320/windaq+lpm2610+b.png" width="320" /></a></div>
<br />
<h4 style="font-size: 1.1em; margin: 1.5em 0;">
Complete Working Circuit</h4>
Given that Figaro AN gives a suggestion on how to detect sensor failure, it must be that this is a distinct possibility. Since I'm using a quad op amp (<a href="http://www.microchip.com/stellent/idcplg?IdcService=SS_GET_PAGE&nodeId=1335&dDocName=en010451" target="_blank">MCP609</a>) I decided to make use of what would be an unused device and connected V<sub>OUT</sub> to it and then configured it to have the same gain as the amplifier for the heater current sense resistor. Normally the output of this noninverting amplifier would be close to V<sub>DD</sub>. However, when the sensor does fail op amp output will be much closer to ground.<br />
<br />
Schematic of a fully working LPG detector: <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PWPh_0xRCr1QP9-iOFogheIWA12fTt7NWsdhhepD4eEWQey2oVaFQBW39ZcXTJiojGhnpzsCRVKCoUEq7OBMi9T9-gx6Js0wsbXgt7jwsCHYz9d3lOr0SnAKkNUJQCwFG9iYz6JvCXE/s1600/LPM2610-MCU-schematic.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="174" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5PWPh_0xRCr1QP9-iOFogheIWA12fTt7NWsdhhepD4eEWQey2oVaFQBW39ZcXTJiojGhnpzsCRVKCoUEq7OBMi9T9-gx6Js0wsbXgt7jwsCHYz9d3lOr0SnAKkNUJQCwFG9iYz6JvCXE/s320/LPM2610-MCU-schematic.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhI6ePWVN2i-ytvh5afHohcN1o6H1_keW3cO_jEM57QbdoRXCCF6xrna56_kseAknV0lwP3WuHLytpimM-cQoI8hAIG0qC6cdICX_UZOj4KnyPhDuaVk51K03V24vKCaTxHQ9R88nE3ih8/s1600/LPM2610-MCU-schematic.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><br /></a></div>
<br />
Instead of using comparators as the Figaro AN suggests an MCU with ADC and/or a comparator simplifies the design and affords far more flexibility. The schematic shows the connections given a Microchip <a href="http://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en027148" target="_blank">PIC12F615</a> 8-pin microcontroller. The 1K resistors in series with the output of two op amps are only necessary because the MCU pins to which they are connected are also used for in-circuit serial programming. The resistors isolate the op amps during programming. Without them the PICkit2/3 will not be able to properly upload the firmware. A buzzer and an LED provide the alarms.<br />
<br />
I've routed V<sub>OUT</sub> to one of the MCU's comparator inverting input pin (CIN0- / AN1) and V<sub>REF</sub> to the comparator noninverting input pin (CIN+ / AN0). This allows the firmware to use the comparator as well as the ADC to compare the two voltages. Thus, in the firmware below, I have preprocessor directives that allow the user to choose--before compilation--either ADC or voltage comparator. <br />
<br />
To make sure the firmware works I initially used simple voltage dividers to emulate the outputs of the Figaro module:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuVLbMzW57yT92inFJUHE2eduFua6VYMDB6FLadUgU0oSSgR4TZATRKIjfzqK_YzxnRrIO5vrOO8otCCP1YwJtfrU-o4vktxlJ6DBdG5vwaLZJjb2aLb3bYruY5pAasVWtmkXczFz4p5c/s1600/LPM2610-emulation-schematic.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="234" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuVLbMzW57yT92inFJUHE2eduFua6VYMDB6FLadUgU0oSSgR4TZATRKIjfzqK_YzxnRrIO5vrOO8otCCP1YwJtfrU-o4vktxlJ6DBdG5vwaLZJjb2aLb3bYruY5pAasVWtmkXczFz4p5c/s320/LPM2610-emulation-schematic.png" width="320" /></a></div>
Heater failure was simulated by simply disconnecting the 91-ohm resistor from V<sub>DD </sub>. Potentiometer permits testing the full range of V<sub>OUT</sub> from 0 to V<sub>DD</sub>. <br />
<br />
After I was satisfied the firmware was bug-free, I removed all the resistors and plugged the LPM2610 into the breadboard and tested the circuit in the kitchen with the stove disconnected from the mains power so that the stove's spark plug like igniters wouldn't function as I turned the knobs to let gas out. During the test what got me immediately concerned is that it took what seemed a rather high concentration of gas to trigger the sensor. I had to place the breadboard really up close to the stove burner where the gas comes out to get the circuit to trigger the alarm. When the breadboard was a couple of feet away, I had to keep the stove knob depressed for longer than I felt safe (and the smell of gas was getting to me). I had a DMM hooked up to V<sub>OUT</sub> so I could monitor it. And from that feedback I felt that 10% LEL was still too high a threshold. Why not lower it to 5% or perhaps even less than that? And while at it why not include a digital display showing how much gas the sensor is detecting? Since that involves a host of mathematical acrobatics I will tackle that project in another blog entry.<br />
<br />
One other revelation of note. LPG is usually a mix of butane and propane. At room temperature butane has a <a href="http://www.engineeringtoolbox.com/gas-density-d_158.html" target="_blank">density</a> of ~2.5 kg/m<sup>3</sup>, propane ~1.9, and air ~1.2. Since the two gases have higher densities than air (butane being twice as heavy as air) they ought to sink and crawl along the floor. And so that's where I plan to install the circuit when I finish building it. But during the test I got practically the same reading at floor level as before I started letting gas leak out. Instead, and oddly, the sensor was giving a higher reading at half a meter above the stove. It's possible that air currents at the time of the test could have carried the gas upwards. Exit velocity of the gas and its trajectory could be factors too. And there may be other parameters as well of course. Will need to reassess and determine where best to place the sensor circuit.<br />
<br />
<h4 style="font-size: 1.1em; margin: 1.5em 0;">
Firmware</h4>
Upon power up there will be a 150-second preheating period indicated by a an audible alert (a short beep once every second). During normal operation the outputs of the LPM2610 is checked four times a second--gas travels and diffuses through the air at a very low speed so there's no need to rapidly poll the sensor. In fact 4 samples/sec may even be considered too fast. During each reading heater and sensor are checked. If voltage across current sense resistor is below a set threshold then the LSb of the16-bit variable VHEAT is set (made = 1). The latest reading
is in the LSb while the oldest reading is in the MSb. If VHEAT = 0xFFFF
and its current state is that heater is ok, then state is now that heater is faulty. A "heater just became faulty" edge trigger is issued. On the other hand, if
VHEAT = 0x0000 and and its current state is that heater is faulty then
state is now that heater is ok. A "heater has just returned to normal operation" edge trigger is issued. Any VHEAT value other than 0x0000 and 0xFFFF is analogous to a bouncing switch contact and no action is taken. All bits in VHEAT are then shifted left with zero getting shifted into the LSb. <br />
<br />
To check the integrity of the sensor the value of V<sub>OUT</sub> (gained by 20) is checked. If it dips below a set threshold then the
LSb of the16-bit variable VSENSE is set (made = 1). The latest reading
is in the LSb while the oldest reading is in the MSb. If VSENSE = 0xFFFF
and its current state is that sensor is ok, then state is now that
sensor is faulty. A "sensor just became faulty" edge trigger is issued.
On the other hand, if
VSENSE = 0x0000 and and its current state is that sensor is faulty then
state is now that sensor is ok. A "sensor has just returned to normal operation"
edge trigger is issued. Any VSENSE value other than 0x0000 and 0xFFFF is analogous to a bouncing switch contact and no action is taken. All bits in VSENSE are then shifted left with
zero getting shifted into the LSb.
<br />
<br />
If heater and sensor are both ok (but not if either is faulty), then V<sub>OUT</sub> is compared to V<sub>REF</sub>. If V<sub>OUT</sub> is > V<sub>REF</sub> then the LSb of the16-bit variable VLPG is set (made = 1). The latest reading is in the LSb while the oldest reading is in the MSb. If VLPG = 0xFFFF and its current state is that LPG is absent, then state is now that LPG is present. An "LPG present" edge trigger is issued. On the other hand, if VLPG = 0x0000 and and its current state is that LPG is present then state is now that LPG is absent. An "LPG absent" edge trigger is issued. Any VLPG value other than 0x0000 and 0xFFFF is analogous to a bouncing switch contact and no action is taken. All bits in VLPG are then shifted left with zero getting shifted into the LSb. <br />
<br />
If either heater or sensor is faulty or if LPG is detected then the appropriate alarm is sounded. The alarm patterns for each of the conditions are as follows:<br />
<br />
Heater fault: 100ms beep -> 500ms silence -> 100ms beep -> 4000ms silence -> repeat <br />
Sensor fault: 100ms beep -> 500ms silence -> 100ms beep -> 500ms silence -> 100ms beep -> 4000ms silence -> repeat<br />
LPG detection: 500ms beep -> 500ms silence -> 500ms beep -> 500ms silence -> 2000ms silence -> repeat<br />
<br />
Should heater be faulty and for some strange reason reverts to normal, then firmware will enter an infinite loop to let the watchdog timer time out and reset the MCU. This is to force the heater to go through the preheating stage.<br />
<br />
<br />
<pre>/*
Liquified petroleum gas (LPG) detector
When LPG level exceeds a set threshhold audible and visual alarms are activated
When Figaro LPM2610 heater or sensor fault is detected, alarms are activated
Configuration: all enabled except for code protect
Internal oscllator @4MHz, I/0 on clock pins
CONFIG :$2007 : 0x036C
ALARMS/ALERTS:
During power up delay: 25ms beeps every second for around 2.5mins
Heater fault: 100ms beep -> 500ms silence -> 100ms beep -> 4000ms silence -> repeat
Sensor fault: 100ms beep -> 500ms silence -> 100ms beep -> 500ms silence -> 100ms beep -> 4000ms silence -> repeat
LPG detection: 500ms beep -> 500ms silence -> 500ms beep -> 500ms silence -> 2000ms silence -> repeat
LEL = lower explosion limit
LEL of butane = 18,000ppm = 1.8% by volume
For the LPM2610 when Vout = Vref then LPG level is ~10% LEL
*/
// ===========================================================================================
// Values for the following defines can be changed as needed
// ===========================================================================================
//***********************************************
// comment out the following define when done debugging
#define debug
//***********************************************
#ifdef debug
#define powerupdelaysec 10
#else
#define powerupdelaysec 150 // number of seconds to allow heater to reach working temperature during power up reset
// according to datasheet recommended preheating time is 2.5min
#endif
//***********************************************
// comment out the following define if vlpg and vref will be measured by the ADC instead of volt comaparator module
// either ADC or comparator can be used
#define use_comparator
//***********************************************
#define heat_resist_volt 75 // actual millivolts across current sense resistor below which is considered practically zero and therefore indication of heater failure
#define sense_volt 50 // actual millivolts of LM2610 sensor output voltage below which is considered practically zero and therefore indication of sensor failure
#define gain 20 // op amp gain
#define shortbeep_ms 100 // duration of a short beep, in actual milliseconds. maximum of 524ms
#define shortpause_ms 500 // duration of a short pause, in actual milliseconds. maximum of 524ms
#define longpause_ms 4000 // duration of a long pause, in actual milliseconds. *minimum* of 500ms
#define lpg_beep_ms 500 // LPG detected alarm: duration of first beep, in actual milliseconds. maximum of 524ms
#define lpg_pause1_ms 500 // LPG detected alarm: duration of first pause, in actual milliseconds. maximum of 524ms
#define lpg_pause2_ms 2000 // LPG detected alarm: duration of second pause, in actual milliseconds. *mininum* of 500ms
#define reset_beep_ms 25 // power up delay alert: duration of beep, in actual milliseconds. maximum of 524ms
#define reset_pause_ms 1000 // power up delay alert: duration of pause between beeps, in actual milliseconds. *mininum* of 500ms
// ===========================================================================================
// !! WARNING WILL ROBINSON !! DO NOT ALTER THE DEFINES BELOW !!
// ===========================================================================================
#define int1 bit
#define int8 unsigned char
#define int16 unsigned int
#define int32 unsigned long
#define on 1
#define off 0
#define input 1 // for TRISx
#define output 0 // for TRISx
#define analog 1 // for ANSELx
#define digital 0 // for ANSELx
#define hi 1 // switch level high
#define lo 0 // switch level low
#define Vdd 5100 // Vdd voltage
#define heater_threshold heat_resist_volt*gain*1024/Vdd // convert to 10-bit ADC value given ADC ref voltage = Vdd
#define sense_threshold sense_volt*gain*1024/Vdd // convert to 10-bit ADC value given ADC ref voltage = Vdd
// TMR1H initial values for various alarms (Fosc = 4MHz, timer1 prescale = 1:8, TMR1L starts at zero)
#define t1h_shortbeep 256-(shortbeep_ms*1000/(256*8) + 0.5)
#define t1h_shortpause 256-(shortpause_ms*1000/(256*8) + 0.5)
#define t1h_longpause 256-(500*1000/(256*8) + 0.5)
#define longpause_cycles longpause_ms / 500
// TMR1H initial values for LPG detection alarm (Fosc = 4MHz, timer1 prescale = 1:8, TMR1L starts at zero)
#define t1h_lpgbeep 256-(lpg_beep_ms*1000/(256*8) + 0.5)
#define t1h_lpgpause1 256-(lpg_pause1_ms*1000/(256*8) + 0.5)
#define t1h_lpgpause2 256-(500*1000/(256*8) + 0.5)
#define lpg_pause2_cycles lpg_pause2_ms / 500
// TMR1H initial values for power-up delay alert (Fosc = 4MHz, timer1 prescale = 1:8, TMR1L starts at zero)
#define t1h_resetbeep 256-(reset_beep_ms*1000/(256*8) + 0.5)
#define t1h_resetpause 256-(500*1000/(256*8) + 0.5)
#define reset_pause_cycles reset_pause_ms / 500
#define buz GPIO.f5 // buzzer
#define tris_buz TRISIO.f5
#define tris_vref TRISIO.f0 // LPM2610 Vref pin
#define tris_vlpg TRISIO.f1 // LPM2610 Vout pin
#define tris_vsense TRISIO.f2 // LPM2610 Vout pin with gain = 20
#define tris_vheat TRISIO.f4 // voltage across heater current sense resistor with gain = 20
// GP4 is AN3
#define an_vref ANSEL.f0
#define an_vlpg ANSEL.f1
#define an_vsense ANSEL.f2
#define an_vheat ANSEL.f3
#define ch_vref 0 // for use with ADCON0 when selecting analog channel
#define ch_vlpg 1 // for use with ADCON0 when selecting analog channel
#define ch_vsense 2 // for use with ADCON0 when selecting analog channel
#define ch_vheat 3 // for use with ADCON0 when selecting analog channel
#define cmch_vlpg 0 // for use with CMCON0 when selecting comparator channel
#define _lpg_absent 0 // for use with variable statelpg
#define _lpg_present 1 // for use with variable statelpg
#define _heater_ok 0 // for use with variable stateheater
#define _heater_fault 1 // for use with variable stateheater
#define _sensor_ok 0 // for use with variable statesense
#define _sensor_fault 1 // for use with variable statesense
#define none 0 // for use with edge detect
bit stateheater; // status of LPM2610 heater, whether ok or faulty
bit statesense; // status of LPM2610 sensor, whether ok or faulty
bit statelpg; // status of lpg detection, whether detected or not
bit powerreset; // 1 = power up delay in progress; ISR needs this to determine which alarm pattern to sound
enum {heatfaultbegin = 1, heatfaultend} HEATEDGE; // edge detect: 0 = no edge detected, 1 = heater fault has just been detected, 2 = heater fault has just been resolved
enum {sensefaultbegin = 1, sensefaultend} SENSEEDGE; // edge detect: 0 = no edge detected, 1 = sensor fault has just been detected, 2 = sensor fault has just been resolved
enum {lpgdetectbegin = 1, lpgdetectend} LPGEDGE; // edge detect: 0 = no edge detected, 1 = level of LPG in the air has just gone above detection threshold, 2 = level of LPG has just dropped below threshold
enum {_heatbeep1, _heatpause1, _heatbeep2, _heatpause2} STATEHEATALARM;
enum {_sensebeep1, _sensepause1, _sensebeep2, _sensepause2, _sensebeep3, _sensepause3} STATESENSEALARM;
enum {_lpgbeep1, _lpgpause1, _lpgbeep2, _lpgpause2} STATELPGALARM;
enum {_resetbeep1, _resetpause1} STATERESETALARM;
int16 VLPG = 0; // 0x0000 = no lpg detected, 0xFFFF = lpg detected, any other value implies bouncing
int16 VHEAT = 0; // 0x0000 = LPM2610 heater functioning, 0xFFFF = heater fault detected, any other value implies bouncing
int16 VSENSE = 0; // 0x0000 = LPM2610 sensor functioning, 0xFFFF = sensor fault detected, any other value implies bouncing
// ===========================================================================================
// Functions
// ===========================================================================================
void BuzzOn()
{
buz = on;
}
void BuzzOff()
{
buz = off;
}
// turn off buz and led and disable timer
void DisarmAlarm()
{
T1CON.TMR1ON = 0;
INTCON.GIE = 0;
BuzzOff();
}
void ArmAlarm()
{
INTCON.GIE = 1;
TMR1L = 0;
T1CON = 0b110001; // prescale = 1:8, timer1 on
PIR1.TMR1IF = 1;
}
// Figaro LPM2610 needs preheating time during which output signal is unreliable.
void PowerUpDelay()
{
int8 i;
OPTION_REG = 0b10001111; // global weak pull ups disabled, prescaler assigned to WDT, prescale = 1:128
STATERESETALARM = 0;
powerreset = 1;
ArmAlarm();
for (i = 0; i < powerupdelaysec; i++)
{
asm clrwdt // nominal WDT timeout = 18ms without prescale, given WDT prescale = 1:128 WDT = ~2sec
Delay_ms(1000);
}
powerreset = 0;
DisarmAlarm();
OPTION_REG = 0b10000111; // global weak pull ups disabled, prescaler assigned to timer0, prescale = 1:256
// WDT timeout = 18ms
}
void IniReg()
{
GPIO = 0;
ANSEL = 0b1011111; // A/D Conversion Clock = Fosc/16, all four analog channels are analog
tris_buz = output;
WPU = 0; // disable individual pullups
OPTION_REG.NOT_GPPU = 1; // disable global pull up
INTCON.PEIE = 1;
PIE1.TMR1IE = 1;
#ifdef use_comparator
CMCON0 = 0b10000; // comparator off, COUT internal only, COUT logic inverted, CMVin+ connected to CIN+
CMCON0.CMCH = cmch_vlpg; // only one comparator channel is used -- the one connected to Vout of LPM2610
#endif
stateheater = _heater_ok;
statesense = _sensor_ok;
statelpg = _lpg_absent;
}
int16 ADC(int8 channel)
{
ADCON0 = channel << 2; // shift in channel
ADCON0.ADFM = 1; // right justified format
ADCON0.VCFG = 0; // VDD as ref voltage
ADCON0.ADON = 1; // ADC on
Delay_us(20); // datasheet says ADC acquisition time typically takes 12us
ADCON0.GO = 1; // start ADC conversion
while(ADCON0.GO) ; // wait
ADCON0.ADON = 0; // ADC off
return ADRESH*256 + ADRESL;
}
void ReadFigaro()
{
HEATEDGE = none;
SENSEEDGE = none;
LPGEDGE = none;
if (ADC(ch_vheat) < heater_threshold)
++VHEAT;
if (VHEAT == 0xFFFF && stateheater == _heater_ok)
{
HEATEDGE = heatfaultbegin;
stateheater = _heater_fault;
}
else if (!VHEAT && stateheater == _heater_fault)
{
HEATEDGE = heatfaultend;
stateheater = _heater_ok;
}
VHEAT <<= 1;
if (ADC(ch_vsense) < sense_threshold)
++VSENSE;
if (VSENSE == 0xFFFF && statesense == _sensor_ok)
{
SENSEEDGE = sensefaultbegin;
statesense = _sensor_fault;
}
else if (!VSENSE && statesense == _sensor_fault)
{
SENSEEDGE = sensefaultend;
statesense = _sensor_ok;
}
VSENSE <<= 1;
if (stateheater == _heater_ok && statesense == _sensor_ok); // do not read figaro outputs if heater or sensor is faulty
{
// ===========================================================================================
// ADC or comparator can be used to measure vlpg and vref
#ifdef use_comparator
CMCON0.CMON = 1;
Delay_us(10); // datasheet does not specify a settling time but it has a "mode change to output valid time" of 10us. So to be on the safe side allow some delay between comparator turn-on and reading of output
if (CMCON0.COUT)
++VLPG;
CMCON0.CMON = 0; // comparator module consumes >60uA @5V so turn off after reading channel
#else
if (ADC(ch_vlpg) > ADC(ch_vref))
++VLPG;
#endif
// ===========================================================================================
if (VLPG == 0xFFFF && statelpg == _lpg_absent)
{
LPGEDGE = lpgdetectbegin;
statelpg = _lpg_present;
}
else if (!VLPG && statelpg == _lpg_present)
{
LPGEDGE = lpgdetectend;
statelpg = _lpg_absent;
}
VLPG <<= 1;
}
}
void Status()
{
if (LPGEDGE == lpgdetectbegin)
{
STATELPGALARM = 0;
ArmAlarm();
}
else if (LPGEDGE == lpgdetectend)
DisarmAlarm();
if (SENSEEDGE == sensefaultbegin)
{
STATESENSEALARM = 0;
ArmAlarm();
}
else if (SENSEEDGE == sensefaultend)
DisarmAlarm();
if (HEATEDGE == heatfaultbegin)
{
STATEHEATALARM = 0;
ArmAlarm();
}
else if (HEATEDGE == heatfaultend)
while(1) ; // if heater was faulty but somehow fault condition got resolved then restart the MCU
// let WDT timeout reset the MCU so that heater will have preheat time
} // void Status()
void interrupt()
{
int8 COUNT;
if (PIR1.TMR1IF)
{
PIR1.TMR1IF = 0;
if (stateheater == _heater_fault)
{
switch (STATEHEATALARM)
{
default:
case _heatbeep1:
BuzzOn();
TMR1H = t1h_shortbeep;
STATEHEATALARM = _heatpause1;
break;
case _heatpause1:
BuzzOff();
TMR1H = t1h_shortpause;
STATEHEATALARM = _heatbeep2;
break;
case _heatbeep2:
BuzzOn();
TMR1H = t1h_shortbeep;
STATEHEATALARM = _heatpause2;
COUNT = 0;
break;
case _heatpause2:
BuzzOff();
TMR1H = t1h_longpause;
if (++COUNT >= longpause_cycles)
STATEHEATALARM = _heatbeep1;
break;
} // switch (STATEHEATALARM)
} // if (stateheater == _heater_fault)
else if (statesense == _sensor_fault)
{
switch (STATESENSEALARM)
{
default:
case _sensebeep1:
BuzzOn();
TMR1H = t1h_shortbeep;
STATESENSEALARM = _sensepause1;
break;
case _sensepause1:
BuzzOff();
TMR1H = t1h_shortpause;
STATESENSEALARM = _sensebeep2;
break;
case _sensebeep2:
BuzzOn();
TMR1H = t1h_shortbeep;
STATESENSEALARM = _sensepause2;
break;
case _sensepause2:
BuzzOff();
TMR1H = t1h_shortpause;
STATESENSEALARM = _sensebeep3;
break;
case _sensebeep3:
BuzzOn();
TMR1H = t1h_shortbeep;
STATESENSEALARM = _sensepause3;
COUNT = 0;
break;
case _sensepause3:
BuzzOff();
TMR1H = t1h_longpause;
if (++COUNT >= longpause_cycles)
STATESENSEALARM = _sensebeep1;
break;
} // switch (STATESENSEALARM)
} // if (statesense == _sensor_fault)
else if (statelpg == _lpg_present)
{
switch (STATELPGALARM)
{
default:
case _lpgbeep1:
BuzzOn();
TMR1H = t1h_lpgbeep;
STATELPGALARM = _lpgpause1;
break;
case _lpgpause1:
BuzzOff();
TMR1H = t1h_lpgpause1;
STATELPGALARM = _lpgbeep2;
break;
case _lpgbeep2:
BuzzOn();
TMR1H = t1h_lpgbeep;
STATELPGALARM = _lpgpause2;
COUNT = 0;
break;
case _lpgpause2:
BuzzOff();
TMR1H = t1h_lpgpause2;
if (++COUNT >= lpg_pause2_cycles)
STATELPGALARM = _lpgbeep1;
break;
} // switch (STATELPGALARM)
} // if (statelpg == _lpg_present)
else if (powerreset)
{
switch (STATERESETALARM)
{
default:
case _resetbeep1:
BuzzOn();
TMR1H = t1h_resetbeep;
STATERESETALARM = _resetpause1;
COUNT = 0;
break;
case _resetpause1:
BuzzOff();
TMR1H = t1h_resetpause;
if (++COUNT >= reset_pause_cycles)
STATERESETALARM = _resetbeep1;
break;
} // switch (STATERESETALARM)
} // if (powerreset)
} // if (PIR1.TMR1IF)
} // void interrupt()
void main()
{
int8 T0COUNT = 0;
IniReg();
PowerUpDelay();
while(1)
{
asm clrwdt;
if (INTCON.T0IF)
{
INTCON.T0IF = 0;
if (++T0COUNT >= 4) // read sensor approx every quarter second
{
T0COUNT = 0;
ReadFigaro();
Status();
} // if (++T0COUNT >= 4)
} // if (INTCON.T0IF)
} // while(1)
} // void main()
</pre>erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com4tag:blogger.com,1999:blog-4092193417928690079.post-84479165986926723972012-05-18T16:34:00.000+08:002012-05-18T16:36:25.037+08:00BevSense v4 -- Getting it right this timeVersion 3 was supposed to be definitive. But it turned out to be a bit of a facepalm. Although it achieved the goal of power reduction and using the slowest clock available, the drawback--and it's a major one--is that the output of the piezo transducer was severely compromised. It's volume is more than just noticeably softer. And I found this out only after uploading v3 to the pcb (not the breadboard version I was using for firmware testing). You see, connecting the PICkit2 causes RA1 (ICSPCLK) pin to drop well below VDD causing the circuit to think that the low probe has detected liquid and so turns on the appropriate audible alert. The sound level I heard was with firmware version 2. Once I had uploaded v3 and before I could remove the PICkit, the circuit again sounded the same alert. But now the transducer output volume was markedly lower. In fact it was too low even as the PWM duty cycle is the same for both v2 and v3. At that moment my heart sank. <br />
<br />
The reason, I believe, is the PWM frequency. With v2 it was ~2.1kHz. In v3 it's ~1.9kHz. As I had discussed before, the highest dB output of the transducer was determined to be around 2.15kHz. But because the PWM resolution is extremely coarse when using 31kHz, the closest frequency that could be obtained was 1.9kHz. (Decreasing <code>PR2</code> by just one shifts the frequency to 2.58kHz). <br />
<br />
Increasing the duty cycle would of course increase the volume, but that method is just out of the question since it eye-poppingly and unacceptably raises the current draw. So I decided it would be best to switch to the 500kHz clock whenever the transducer is on, and use the 31kHz clock at all other times. And so v4 was born.<br />
<br />
Incidentally, I bench-tested another transducer--the same model--and to my ear its
highest sound level output is at 2kHz. So I've taken the average of 2.15
and 2kHz and set the PWM to 2.083kHz in the firmware below. <br />
<br />
As you can see in the program listing below <code>OSCCON</code> is configured to use the 500kHz MFINTOSC in the function <code>BuzzOn()</code> and to use the 31kHz LFINTOSC in <code>BuzzOff()</code>. In all <code>STATE</code>s other than <code>_standby</code>, timer 0 is used to set the cycle time--the time interval between probes readings. But remember the clock frequency is continually being switched back and forth (when liquid has been detected or when a probe error has occurred). In order to have a more or less equal cycle time when using either clock frequencies, I've pegged the value that <code>TMR0</code> is initialized to as a constant and varied the timer0 prescale value. Since 500kHz / 31kHz = approx 16, the prescale value when in 500kHz mode must be 16x that of the value when using the low frequency clock. I've set <code>TMR0</code> = 256 - 66 giving a theoretical cycle time of ~17ms. Number of stored probe readings has been reduced from 8 to 6. The 17ms cycle was chosen so that 17ms x 6 readings = ~100ms. Using the Saleae Logic, measured cycle time is ~18.6ms @31kHz and ~17.0ms @500kHz (see the screenshots below)<br />
<br />
For timer1 I took the opposite tact. I kept the prescale value constant (for each STATE) and varied the initial value of <code>TMR1H</code> since <code>TMR1H</code> initial value is different for transducer on-time and off-time anyway. Up to v3 transducer on-time and off-time during liquid detection was equal. In v4 I've decreased the on-time slightly to reduce current draw just a tad.<br />
<br />
The transducer on-times during probe error have been limited to a couple of 100ms pulses every 8.5 seconds. <br />
<br />
<pre>/*
Liquid Level Indicator version 4
May 2012
processor = PIC12LF1840
compiler = mikroC v5.6.0
configuration word:
INTOSC with I/O on clk pin
enabled: power up timer, WDT via SWDTEN, MCLR, stack over/underflow
all else disabled
CONFIG1 :$8007 : 0x09CC
CONFIG2 :$8008 : 0x1613
To minimize power consumption BOR should be disabled and MCLR enabled (so that RA3 is not left floating)
*/
#define int8 unsigned char
#define on 1
#define off 0
#define input 1 // for TRISx
#define output 0 // for TRISx
#define analog 1 // for ANSELx
#define digital 0 // for ANSELx
#define buzz LATA.f2
#define an_plo ANSELA.f1
#define an_phi ANSELA.f4
#define tris_plo TRISA.f1
#define tris_phi TRISA.f4
#define wpu_plo WPUA.f1 // weak pull for low probe
#define wpu_phi WPUA.f4 // weak pull for high probe
#define ch_plo 0 // comparator input channel for low probe
#define ch_phi 1 // comparator input channel for high probe
#define t0_ini 256-66 // TMR0 initial value (clock = 31kHz / 500kHz)
#define t1h_buzzon 256-196 // TMR1H initial value for audible indicator when low probe immersed or low and high probe immersed -- length of time buzzer on (clock = 500kHz)
#define t1h_buzzoff 256-15 // TMR1H initial value for audible indicator when low probe immersed or low and high probe immersed -- length of time buzzer off (clock = 31kHz)
#define t1h_beep1 256-49 // TMR1H initial value for first beep during probe error (clock = 500kHz)
#define t1h_pause1 256-9 // TMR1H initial value for first pause during probe error (clock = 31kHz)
#define t1h_beep2 256-49 // TMR1H initial value for second beep during probe error (clock = 500kHz)
#define t1h_pause2 256-242 // TMR1H initial value for second pause during probe error (clock = 31kHz)
#define osc500khz 0b111000 // 500kHz MFINTOSC, for use with OSCCON
#define osc31khz 0b0 // 31kHz LFINTOSC, for use with OSCCON
#define wdt512ms 0b10011 // WDT time out = 512ms, for use with WDTCON
int8 PLOval; // stores the last six low probe readings
int8 PHIval; // stores the last six high probe readings
enum {_bouncing, _standby, _plo_immersed, _plophi_immersed, _phi_immersed}
STATE = _bouncing, // current state
PREVSTATE = _bouncing; // previous state
enum {_beep1, _pause1, _beep2, _pause2} STATEPERROR; // for use with buzzer sound pattern when probe error detected
// ===========================================================================================
// Functions
// ===========================================================================================
void IniReg()
{
ANSELA = digital;
TRISA = output;
PORTA = 0;
an_plo = analog;
an_phi = analog;
tris_plo = input;
tris_phi = input;
WPUA = 0; // disable individual pull ups
OPTION_REG = 0b0; // global pull ups enabled
DACCON0 = 0; // DAC off, DAC is not output on DACOUT pin, Vdd as positive source
//DACCON1 = 0b10000; // 0x10, Vref = 16/32 = 50% of Vdd
DACCON1 = 0b11000; // 0x18, Vref = 24/32 = 75% of Vdd
CM1CON0 = 0b10; // comparator off, comp output polarity not inverted, comp output internal only,
// comparator in low power low speed mode, hysteresis enabled
CM1CON1 = 0b10000; // comparator interrupts disabled, C1VP connected to DAC, C1VN connected to C1N0-
// initialize probe readings to zero (no liquid detection)
PLOval = 0;
PHIval = 0;
// 500kHz MFINTOSC is used only when piezoelectric transducer is turned on
// piezoelectric transducer empirically determined to be loudest at ~2 to ~2.15kHz
// Current draw of the transducer has been determined to be directly proportional to duty cycle.
// Audio volume of the transducer is proportional to duty cycle, but whether it is linear, logarithmic, or otherwise, is unknown
// PWM Period = (PR2 + 1) x 4 x Tosc x (TMR2 Prescale Value), where Tosc = 1/Fosc
// Duty Cycle = (CCPR1L:CCP1CON<5:4>) / [4 x (PR2 + 1)]
// Given PR2 = 59, timer2 prescale = 1, CCPR1L:CCP1CON<5:4> = 60, Fosc = 500kHz,
// PWM period = 480us (freq = 2.083kHz) and duty cycle = 25%
PR2 = 59;
CCPR1L = 0b1111; // with CCPR1L = 0b1111 and CCP1CON<5:4> = 0b00, CCPR1L:CCP1CON<5:4> = 0b111100 = 60 decimal
CCP1CON = 0b1100; // PWM mode with P1A active high, P1B disabled, CCP1CON<5:4> = 0b00
PIE1.TMR1IE = 1; // timer1 interrupt enabled
INTCON.PEIE = 1; // peripheral interrupt enabled. Global interrupt is by default disabled upon any reset. GIE is enabled only when timer1 is enabled (i.e., when buzzer needs to be sounded)
WDTCON = wdt512ms; // WDT time out = 512ms. This is the sleep time between probe reads when in Standby mode
// longer sleep times would be better from a power consumption perspective
// but would be too long when the user starts using the unit for its intended purpose -- to detect liquid and as soon as possible
} // void IniReg()
// turn on buzzer
void BuzzOn()
{
OSCCON = osc500khz; // 500kHz clock used only when piezo transducer is actually sounding
TMR2 = 0;
PIR1.TMR2IF = 0;
T2CON.TMR2ON = 1;
OPTION_REG = 0b100; // global pull ups enabled, timer0 prescale = 1:32
}
// turn off buzzer
void BuzzOff()
{
buzz = off;
OSCCON = osc31khz; // 31kHz clock used whenever piezo transducer not sounding
T2CON.TMR2ON = 0;
OPTION_REG = 0b0; // global pull ups enabled, timer0 prescale = 1:2
}
// audible indicator is armed, with the particular sequence of sounds emitted depending on
// whether liquid has reached low probe, low and high probe, or just the high probe
void EnableAudible()
{
TMR1L = 0;
PIR1.TMR1IF = 0;
INTCON.GIE = 1;
BuzzOn();
}
// both low and high probes not immersed
// PLOval = PHIval = 0
void Standby()
{
STATE = _standby;
wpu_plo = 1;
wpu_phi = 1;
// =========================
// disable audible indicator
BuzzOff();
T1CON.TMR1ON = 0;
INTCON.GIE = 0;
// =========================
}
// low probe immersed -- cup nearly full
void PLOimmersed()
{
if (PREVSTATE != _plo_immersed)
{
STATE = _plo_immersed;
TMR1H = t1h_buzzon;
T1CON = 0b1100001; // Timer1 clock source is system clock (FOSC), prescale = 1:4, timer1 oscillator off, timer1 on
// given 500kHz clock and TMR1H ini value = 256-196, timer1 tick is ~400ms
// given 31kHz clock and TMR1H ini value = 256-15, timer1 tick is ~500ms
EnableAudible();
}
}
// low and high probes both immersed -- cup is full
void PLOPHIimmersed()
{
if (PREVSTATE != _plophi_immersed)
{
STATE = _plophi_immersed;
TMR1H = t1h_buzzon;
T1CON = 0b1000001; // Timer1 clock source is system clock (FOSC), prescale = 1:1, timer1 oscillator off, timer1 on
// given 500kHz clock and TMR1H ini value = 256-196, timer1 tick is ~100ms
// given 31kHz clock and TMR1H ini value = 256-15, timer1 tick is ~125ms
EnableAudible();
}
}
// probe error -- only high probe is immersed
void PHIimmersed()
{
if (PREVSTATE != _phi_immersed)
{
STATE = _phi_immersed;
TMR1H = t1h_beep1;
T1CON = 0b1100001; // Timer1 clock source is system clock (FOSC), prescale = 1:4, timer1 oscillator off, timer1 on
STATEPERROR = _beep1;
EnableAudible();
}
}
void DetermineState()
{
asm clrwdt
// ===============================
// Read Probes -- for modes other than standby
// Individual weak pull ups are enabled prior to reading and disabled after reading to minimize current draw since the probes are immersed and weak pull ups will draw current
// DAC, weak pull ups, and comparator are turned on before reading and then turned off afterwards to minimize power consumption
// current reading stored in PXXval.f0
// ===============================
CM1CON0.C1ON = 1; // turn on comparator (current draw ~4.5uA)
DACCON0.DACEN = 1; // turn on DAC (current draw ~19uA)
wpu_plo = 1; // enable low probe weak pull up
CM1CON1.C1NCH = ch_plo;
if (CMOUT)
PLOval.f0 = 1;
wpu_plo = 0; // disable low probe weak pull up
wpu_phi = 1; // enable high probe weak pull up
CM1CON1.C1NCH = ch_phi;
if (CMOUT)
PHIval.f0 = 1;
wpu_phi = 0; // disable high probe weak pull up
DACCON0.DACEN = 0; // turn off DAC
CM1CON0.C1ON = 0; // turn off comparator
// ===============================
// Determine state/mode
// ===============================
if (!PHIval)
{
if (!PLOval)
Standby();
else if (PLOval == 0b111111)
PLOimmersed();
}
else if (PHIval == 0b111111)
{
if (PLOval == 0b111111)
PLOPHIimmersed();
else if (!PLOval)
PHIimmersed();
}
// all bits (readings) of PXXval can now be shifted left
// clear two MSb since we are retaining only 6 readings
PLOval <<= 1;
PHIval <<= 1;
PLOval.f6 = 0;
PHIval.f6 = 0;
PREVSTATE = STATE;
} // void DetermineState()
void interrupt()
{
if (PIR1.TMR1IF)
{
if (STATE == _phi_immersed)
{
if (++STATEPERROR > _pause2)
STATEPERROR = _beep1;
if (!STATEPERROR)
{
BuzzOn();
TMR1H = t1h_beep1; // given 500kHz, system clock as clock source, prescale = 1:4, TMR1H ini value = 49, timer1 tick is ~100ms
}
else if (STATEPERROR == _pause1)
{
BuzzOff();
TMR1H = t1h_pause1; // given 31kHz, system clock as clock source, prescale = 1:4, TMR1H ini value = 9, timer1 tick is ~300ms
}
else if (STATEPERROR == _beep2)
{
BuzzOn();
TMR1H = t1h_beep2; // given 500kHz, system clock as clock source, prescale = 1:4, TMR1H ini value = 49, timer1 tick is ~100ms
}
else
{
BuzzOff();
TMR1H = t1h_pause2; // given 31kHz, system clock as clock source, prescale = 1:4, TMR1H ini value = 242, timer1 tick is ~8000ms
}
} // if (STATE == _phi_immersed)
else // if (STATE == _plo_immersed || STATE == _plophi_immersed)
{
if (T2CON.TMR2ON)
{
BuzzOff();
TMR1H = t1h_buzzoff;
}
else
{
BuzzOn();
TMR1H = t1h_buzzon;
}
}
PIR1.TMR1IF = 0;
} // if (PIR1.TMR1IF)
} // void interrupt()
void main()
{
IniReg();
while(1)
{
if (STATE != _standby)
{
while (!INTCON.TMR0IF) ;
TMR0 = t0_ini;
INTCON.TMR0IF = 0;
DetermineState(); // just one function call to reduce execution time
}
else
{
asm sleep // WDT is automatically cleared right before sleep and after waking up
// MCU wakes up upon WDT timeout
// Reduction of MCU awake time during Standby mode reduces current draw
// Ways by which instructions executed when MCU is awake are reduced:
// 1. Probe reading is stored in PXXval only if reading is high
// 2. Weak pull ups are kept on since they don't consume current unless probes are immersed.
// 3. Standby mode is when PLOval = PHIval = PLOlevel = PHIlevel = 0 therefore we need only monitor for instantaneous readings that are high
// 4. Reduction/elimination of function calls and using inline code instead
// DAC and voltage comparator are turned on before reading and then turned off afterwards to minimize power consumption
DACCON0.DACEN = 1; // turn on DAC
CM1CON0.C1ON = 1; // turn on comparator
CM1CON1.C1NCH = ch_phi;
if (CMOUT)
{
STATE = _bouncing;
PLOval.f0 = 1;
}
CM1CON1.C1NCH = ch_plo;
if (CMOUT)
{
STATE = _bouncing;
PHIval.f0 = 1;
}
CM1CON0.C1ON = 0; // turn off comparator
DACCON0.DACEN = 0; // turn off DAC
}
} // while(1)
} // void main()
</pre>
<br />
<br />
To measure various time intervals and widths using the logic analyzer I temporarily modified the firmware as follows. Added are the <code>LATA.f5</code> and <code>LATA.f0</code> statements.<br />
<br />
The following permits us to see measure cycle time and idle time.<br />
<br />
<pre> if (STATE != _standby)
{
LATA.f5 = 1;
while (!INTCON.TMR0IF) ;
<b>LATA.f5 = 0;</b>
TMR0 = t0_ini;
INTCON.TMR0IF = 0;
DetermineState(); // just one function call to reduce execution time
}
</pre>
<br />
The following allows measurement of transducer on/off times.<br />
<br />
<pre>void interrupt()
{
LATA.f0 = 1;
if (PIR1.TMR1IF)
{
... other statements
} // if (PIR1.TMR1IF)
LATA.f0 = 0;
} // void interrupt()
</pre>
<br />
In the following screenshots firmware was in <code>STATE = _plophi_immersed</code> (both probes immersed in liquid). Bear in mind in this mode/state, transducer is designed to be on ~100ms and off ~125ms. Channel 7 is hooked up to RA5 pin; Ch6 to RA0.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiGca4NCWd2zUenk5vmhyphenhyphencdVX1mXGM3yIVbp5geJa2sHsUoQ57Lj6h2sP4UVjf6ckIJyLl88uZpuuNEw2PARTWoeQx239GB8ypmKP3v-krrY-4grz0Nst2_POZObvCCe2nsa9yLBj-_S8/s1600/bevsense1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="135" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiGca4NCWd2zUenk5vmhyphenhyphencdVX1mXGM3yIVbp5geJa2sHsUoQ57Lj6h2sP4UVjf6ckIJyLl88uZpuuNEw2PARTWoeQx239GB8ypmKP3v-krrY-4grz0Nst2_POZObvCCe2nsa9yLBj-_S8/s320/bevsense1.png" width="320" /></a></div>
<br />
Length of time that transducer is on is ~103ms (see value for "Period" in the screenshot)<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6v-7sFK6WHHEiFCGdZ5hliPqMgobTOw0lGjsT-Ob7kGB_BiQm7XS8p-hX9bbCK1u1R82Y6lzoM5WF4svPovaDNQdZLZOLWSTqmg4XeUPKWXyNIISl9FeayFgfZk9Nsh6zTfwythg1hpM/s1600/bevsense2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="135" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6v-7sFK6WHHEiFCGdZ5hliPqMgobTOw0lGjsT-Ob7kGB_BiQm7XS8p-hX9bbCK1u1R82Y6lzoM5WF4svPovaDNQdZLZOLWSTqmg4XeUPKWXyNIISl9FeayFgfZk9Nsh6zTfwythg1hpM/s320/bevsense2.png" width="320" /></a></div>
<br />
Time that transducer is off is ~124ms.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYJ5NYLZl88ZkTHVV11sIRE60j1s03lTIUKJAT7fg9fBgynmrMLWjUt-5hkSmNPcqrwT586ct09oBkMQEszQZemP8cgjAORx4AsaGzign-rwOVPcV1jHIuS_x3rDNzsf6HgloWmDZBHhs/s1600/bevsense3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="135" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYJ5NYLZl88ZkTHVV11sIRE60j1s03lTIUKJAT7fg9fBgynmrMLWjUt-5hkSmNPcqrwT586ct09oBkMQEszQZemP8cgjAORx4AsaGzign-rwOVPcV1jHIuS_x3rDNzsf6HgloWmDZBHhs/s320/bevsense3.png" width="320" /></a></div>
<br />
<br />
With clock = 500kHz (transducer is sounding), out of the ~17.0ms cycle time the firmware is spending 16.35ms doing nothing but waiting for timer0 interrupt flag to get set. <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzLc4vCYpgI3YUlYoU1ANMNiztEsyWe4SNg4GOxO8mbYiLou_W3ODFHCSTYX2ltaL0BqkyV2uJ-Cxj9G1ghydVd3_2V_QxwuObV8uNs1EuRqAltFXMXdpcHRXYgX4POw9YNWCnC1mCGBY/s1600/bevsense4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="135" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzLc4vCYpgI3YUlYoU1ANMNiztEsyWe4SNg4GOxO8mbYiLou_W3ODFHCSTYX2ltaL0BqkyV2uJ-Cxj9G1ghydVd3_2V_QxwuObV8uNs1EuRqAltFXMXdpcHRXYgX4POw9YNWCnC1mCGBY/s320/bevsense4.png" width="320" /></a></div>
<br />
With clock = 31kHz (transducer off), cycle time ~18.5ms and "dead time" ~8ms. That's still a fair amount of idle time even with the LFINTOSC.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdMsRztQPC-Zpff93UCUfS9F6se0SL6DkHtyNZFC_VhAHXJ_Fz6lWhLfWs8YFUcju5IcLCHcuumG3jiJo4ZtM8SIWRibKnDbJLTkGPsaD5gOMXwIdEmB2r92wnsy9KwWmnhJ_mpFqSlEE/s1600/bevsense5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="135" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdMsRztQPC-Zpff93UCUfS9F6se0SL6DkHtyNZFC_VhAHXJ_Fz6lWhLfWs8YFUcju5IcLCHcuumG3jiJo4ZtM8SIWRibKnDbJLTkGPsaD5gOMXwIdEmB2r92wnsy9KwWmnhJ_mpFqSlEE/s320/bevsense5.png" width="320" /></a></div>
<br />erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com0tag:blogger.com,1999:blog-4092193417928690079.post-74193364194394762922012-05-14T14:38:00.000+08:002012-05-14T14:38:55.258+08:00Panasonic IR remote control protocolI used an <a href="http://catalog.osram-os.com/catalogue/catalogue.do?favOid=00000002000246c8001c0023&act=showBookmark" target="_blank">Osram SFH5110-38 IR receiver</a> and <a href="http://www.saleae.com/logic/" target="_blank">Saleae Logic</a> to capture the output of a Panasonic air conditioner remote control. I googled but couldn't find any site providing decoding info for the 32-bit packet. The closest I've found shows the protocol for a <a href="http://users.telenet.be/davshomepage/panacode.htm" target="_blank">Panasonic 22-bit packet</a>. Though not exactly what I'm looking for, the page does provide crucial information about pulse widths and what constitutes a logic one and zero. <br />
<br />
The Panasonic remote control uses pulse distance encoding where pulse widths for both logic 1 and 0 are the same: 2T, but the periods are not: 4T for logic zero and 8T for logic 1. Thus a logic 0 is high for 2T and low for 2T while a logic 1 is high for 2T and low for 6T. The start pulse is 8T high and 8T low. From the logic analyzer measurements T for the particular unit I have is anywhere from 435 to 440µs. (Bear in mind that at the receiver end everything is inverted--high is low, and low is high, with the output of the IR receiver normally high when no transmission is being received.)<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzh0s0DtYnvk63VtfqQ2zo791qAt6BtKGkh_xvH4sQCjjBMNZNuRfxRuxC7ji0Wnzlrnk-KDiPOxQQjNMxx4oNzVAKiZ9Qc8CAEjLqyTxSi_03fP-l51j9d4Ex9MjApMRBaY39qJ4QsW0/s1600/panasonic-ir-protocol.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzh0s0DtYnvk63VtfqQ2zo791qAt6BtKGkh_xvH4sQCjjBMNZNuRfxRuxC7ji0Wnzlrnk-KDiPOxQQjNMxx4oNzVAKiZ9Qc8CAEjLqyTxSi_03fP-l51j9d4Ex9MjApMRBaY39qJ4QsW0/s320/panasonic-ir-protocol.png" width="320" /></a></div>
<br />
The remote control sends out the least significant byte (LSB) is first. And for every byte the least significant bits (LSb) are sent out first. A start pulse and four bytes constitute one packet. The LSB is the command code. This code is sent again (without inversion) as the second byte. The third byte is the address code. This is transmitted again and is the fourth and most significant byte.<br />
<br />
With the exception of three, keeping the buttons depressed does not continually transmit a signal. Instead exactly three packets are sent without pause (i.e., a start pulse immediately follows after the last bit of the packet has been transmitted). The series of three packets is then capped with an end pulse which is equivalent to a start pulse and 2T high.<br />
<br />
Holding down the up arrow, down arrow, or fan speed buttons will continually send the packet (with no end pulse). Panasonic must've made a mistake with the fan speed button since it need not send the packet continuously. Testing shows that even if the fan speed button on the remote control is kept depressed the speed of the blower is toggled only once per button push (it has to be released and pressed again to change fan speed).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiTtBUK16WbhVKNsqmN_ss2E-3uwdIX0U7FNw-u27amkKRRojEi-MLSe4vaCfiDInzUav-Jf7A3DkYYRMo7uJQoIKgHzfFp1LNmrw_Atwt_myNWMkR9zbezf7hU1y2dPLkCDia0kiLLWM/s1600/panasonic-remote-1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiTtBUK16WbhVKNsqmN_ss2E-3uwdIX0U7FNw-u27amkKRRojEi-MLSe4vaCfiDInzUav-Jf7A3DkYYRMo7uJQoIKgHzfFp1LNmrw_Atwt_myNWMkR9zbezf7hU1y2dPLkCDia0kiLLWM/s320/panasonic-remote-1.png" width="180" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUSMugCDCh1YGSShc9f4zCTwWSbmGLjE3gCjFWY93xLXj7fkIyo2ouCo_H1RYxnsgGBNJiWORhXIQkUM_7YoU4BTlx2K4QosiKys7ucoE0D5TKAT-D3SzL3_SuuCFPlZb_glghp08ZKKE/s1600/panasonic-remote-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="154" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUSMugCDCh1YGSShc9f4zCTwWSbmGLjE3gCjFWY93xLXj7fkIyo2ouCo_H1RYxnsgGBNJiWORhXIQkUM_7YoU4BTlx2K4QosiKys7ucoE0D5TKAT-D3SzL3_SuuCFPlZb_glghp08ZKKE/s320/panasonic-remote-2.png" width="320" /></a></div>
<br />
From the looks of it the numbers at the back of the control are stamped after molding. Another Panasonic air conditioner remote has the same "model number" A75C2454 but has "batch number" 9225 instead of the 91X4 on this unit.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhI8Of7FGzLck5_bSn_9HmsOZ4kQgpGek7C8xPyfx8PwKpm50bPZ1zT4DyRIhEq1U42U46rN3lNheTwQlQ2CDhvkvQoP94wSQVJzEe5iyEfYI6Xbp4WVUl2H-mytsLSW7VpS1mweuoniq4/s1600/panasonic-remote-3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhI8Of7FGzLck5_bSn_9HmsOZ4kQgpGek7C8xPyfx8PwKpm50bPZ1zT4DyRIhEq1U42U46rN3lNheTwQlQ2CDhvkvQoP94wSQVJzEe5iyEfYI6Xbp4WVUl2H-mytsLSW7VpS1mweuoniq4/s320/panasonic-remote-3.png" width="320" /></a></div>
<br />
I tested all the buttons on the remote control and collected the following command and address codes. <br />
<br />
<table border="1" cellpadding="5" cellspacing="0">
<caption>Panasonic air conditioner remote control codes</caption>
<tbody>
<tr>
<th rowspan="2">Key/Button</th>
<th>MSB</th>
<th></th>
<th></th>
<th>LSB</th>
</tr>
<tr>
<th colspan="2">Address</th>
<th colspan="2">Command</th>
</tr>
<tr>
<td>Operation</td>
<td>0x33</td>
<td>0x33</td>
<td>0x91</td>
<td>0x91</td>
</tr>
<tr>
<td>Mode</td>
<td>0x33</td>
<td>0x33</td>
<td>0x92</td>
<td>0x92</td>
</tr>
<tr>
<td>Fan Speed</td>
<td>0x33</td>
<td>0x33</td>
<td>0x93</td>
<td>0x93</td>
</tr>
<tr>
<td>Up arrow</td>
<td>0x33</td>
<td>0x33</td>
<td>0x94</td>
<td>0x94</td>
</tr>
<tr>
<td>Down arrow</td>
<td>0x33</td>
<td>0x33</td>
<td>0x95</td>
<td>0x95</td>
</tr>
<tr>
<td>Timer</td>
<td>0x33</td>
<td>0x33</td>
<td>0x96</td>
<td>x096</td>
</tr>
<tr>
<td>Set/Cancel</td>
<td>0x33</td>
<td>0x33</td>
<td>0x97</td>
<td>0x97</td>
</tr>
<tr>
<td>Air Swing</td>
<td>0x30</td>
<td>0x30</td>
<td>0x80</td>
<td>0x80</td>
</tr>
<tr>
<td>Powerful</td>
<td>0x35</td>
<td>0x35</td>
<td>0x86</td>
<td>0x86</td>
</tr>
</tbody></table>erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com3tag:blogger.com,1999:blog-4092193417928690079.post-3907678995939871292012-05-13T16:35:00.000+08:002012-05-18T21:10:52.515+08:00BevSense v3Thought I'd give it a more succinct and at the same time livelier name than the absolutely eggheady and dull "<a href="http://electromotiveforces.blogspot.com/2012/05/further-improvements-to-beverage-sensor.html" target="_blank">liquid level indicator</a>."<br />
<br />
Tweaked the firmware and made a number of major changes to reduce current in all modes as well as number of instructions executed when not in standby mode--i.e., when liquid has been detected or if a probe error has occurred. Among the changes are:<br />
<ul>
<li style="padding-bottom: 1em;">Only the 31kHz LFINTOSC is used for all modes. With the PWM module taking care of energizing the piezo transducer it's no longer necessary to use the 500kHz clock and toggle it on and off in the ISR. The 500kHz clock is the default frequency upon reset and is used only during initialization of various SFRs.</li>
<li style="padding-bottom: 1em;">WDT timeout is permanently set to 512 ms.</li>
<li style="padding-bottom: 1em;">8 probe readings instead of 5 are stored in <code>PXXval</code>. Because number of instructions has been reduced, even with just a 31kHz clock, time interval between probe reads is usually less than 10ms. So debouncing the probes takes less than 100ms. </li>
<li style="padding-bottom: 1em;">States <code>_begin</code>, <code>_insta_plo</code>, and <code>_insta_phi</code>, and functions <code>InstaPLO()</code>, <code>InstaPHI()</code>, and <code>DisableAudible</code> have been discarded. </li>
<li style="padding-bottom: 1em;">Audible indicator is now only disabled when entering standby mode. It isn't necessary to disable it in any other mode since only standby mode needs it shut down.</li>
<li style="padding-bottom: 1em;">Variables <code>PLOlevel</code>, <code>PHIlevel</code>, <code>plo</code>, <code>phi</code>, <code>buzzing</code> have been discarded. These have been found to be redundant and to increase execution time.</li>
<li style="padding-bottom: 1em;">Timer1 interrupt is always enabled; only global interrupt is enabled/disabled when audible indicator is enabled/disabled </li>
<li style="padding-bottom: 1em;">Timer0 is no longer used. There is now no fixed time between reads when
not in standby mode, although the time between probe reads has been
determined to be 9.5 to 9.8ms depending on the mode, excluding interrupt service. Measured ISR execution time when low probe is immersed or when both low and high probes are immersed is ~3.1ms or ~3.2ms depending on whether the buzzer is being turned on or off. </li>
<li style="padding-bottom: 1em;">Probe reading and determination of status/mode have been highly optimized. And for standby mode no function calls outside main() are made, in order to reduce execution time. </li>
<li style="padding-bottom: 1em;">Although the use of <code>switch / case</code> is more elegant, given the way mikroC implements it, execution time takes a tad longer than using a series of <code>if</code> statements. So in the ISR the probe error routine now uses <code>if / else</code> statements. ISR times using switch / case are
~4.8ms, ~5.3ms, ~5.8ms, ~6.4ms for the first beep, first pause,
second beep, second pause, respectively. With <code>if / else</code> times are ~4.3ms, ~4.9ms, ~5.6ms and ~ 5.4ms, respectively. </li>
<li style="padding-bottom: 1em;">Because of the 31kHz clock, the buzzer frequency of 2.15kHz can no longer be attained with the PWM module. The best that can obtained is 1.94kHz.</li>
<li style="padding-bottom: 1em;">Left shifting of bits in <code>PXXval</code> is now done after determination of status/mode</li>
</ul>
With the various improvements average standby mode current is now down to around 0.63 to 0.64µA. When liquid is detected maximum current draw is ~2.0mA and average is ~1.0mA. <br />
<br />
<br />
<pre>/*
Liquid Level Indicator version 3
May 2012
processor = PIC12LF1840
compiler = mikroC v5.6.0
configuration word:
INTOSC with I/O on clk pin
enabled: power up timer, WDT via SWDTEN, MCLR, stack over/underflow
all else disabled
CONFIG1 :$8007 : 0x09CC
CONFIG2 :$8008 : 0x1613
To minimize power consumption BOR should be disabled and MCLR enabled (so that RA3 is not left floating)
Internal oscillator starts at 500kHz after power up then switched to 31kHz LFINTOSC after initialization of SFRs and remains at that setting throughout
*/
#define int8 unsigned char
#define on 1
#define off 0
#define input 1 // for TRISx
#define output 0 // for TRISx
#define analog 1 // for ANSELx
#define digital 0 // for ANSELx
#define buzz LATA.f2
#define an_plo ANSELA.f1
#define an_phi ANSELA.f4
#define tris_plo TRISA.f1
#define tris_phi TRISA.f4
#define wpu_plo WPUA.f1
#define wpu_phi WPUA.f4
#define ch_plo 0 // comparator input channel for low probe
#define ch_phi 1 // comparator input channel for high probe
#define t1h_fill 256-15 // TMR1H initial value for audible indicator when low probe immersed or low and high probe immersed
#define t1h_beep1 256-30 // TMR1H initial value for first beep during probe error
#define t1h_pause1 256-19 // TMR1H initial value for first pause during probe error
#define t1h_beep2 256-30 // TMR1H initial value for second beep during probe error
#define t1h_pause2 256-122 // TMR1H initial value for second pause during probe error
#define osc31khz 0b0 // 31kHz LFINTOSC, for use with OSCCON
#define wdt512ms 0b10011 // WDT time out = 512ms, for use with WDTCON
int8 PLOval; // stores the last eight low probe readings
int8 PHIval; // stores the last eight high probe readings
enum {_bouncing, _standby, _plo_immersed, _plophi_immersed, _phi_immersed}
STATE = _bouncing, // current state
PREVSTATE = _bouncing; // previous state
enum {_beep1, _pause1, _beep2, _pause2} STATEPERROR; // for use with buzzer sound pattern when probe error detected
// ===========================================================================================
// Functions
// ===========================================================================================
void IniReg()
{
ANSELA = digital;
TRISA = output;
PORTA = 0;
an_plo = analog;
an_phi = analog;
tris_plo = input;
tris_phi = input;
WPUA = 0; // disable individual pull ups
OPTION_REG = 0; // global pull ups enabled
DACCON0 = 0; // DAC off, DAC is not output on DACOUT pin, Vdd as positive source
//DACCON1 = 0b10000; // 0x10, Vref = 16/32 = 50% of Vdd
DACCON1 = 0b11000; // 0x18, Vref = 24/32 = 75% of Vdd
CM1CON0 = 0b10; // comparator off, comp output polarity not inverted, comp output internal only,
// comparator in low power low speed mode, hysteresis enabled
CM1CON1 = 0b10000; // comparator interrupts disabled, C1VP connected to DAC, C1VN connected to C1N0-
// piezoelectric transducer empirically determined to be loudest at 2.15kHz
// Current draw of the transducer has been determined to be directly proportional to duty cycle.
// Audio volume of the transducer is proportional to duty cycle, but whether it is linear, logarithmic, or otherwise, is unknown
// PWM Period = (PR2 + 1) x 4 x Tosc x (TMR2 Prescale Value), where Tosc = 1/Fosc
// Duty Cycle = (CCPR1L:CCP1CON<5:4>) / [4 x (PR2 + 1)]
// Given PR2 = 3, timer2 prescale = 1, CCPR1L:CCP1CON<5:4> = 4, Fosc = 31kHz,
// PWM period = 516us (freq = 1.937kHz) and duty cycle = 25%
T2CON = 0; // prescale = 1:1, postscale = 1:1, timer2 off
PR2 = 3;
CCPR1L = 0b1; // with CCPR1L = 0b1 and CCP1CON<5:4> = 0b00, CCPR1L:CCP1CON<5:4> = 0b100 = 4 decimal
CCP1CON = 0b1100; // PWM mode with P1A active high, P1B disabled, CCP1CON<5:4> = 0b00
// initialize probe readings to zero and levels to zero
PLOval = 0;
PHIval = 0;
PIE1.TMR1IE = 1; // timer1 interrupt enabled
INTCON.PEIE = 1; // peripheral interrupt enabled. Global interrupt is by default disabled upon any reset. GIE is enabled only when timer1 is enabled (i.e., when buzzer needs to be sounded)
OSCCON = osc31khz; // default INTOSC frequency = 500khz. Change to 31kHz LFINTOSC after initialization.
WDTCON = wdt512ms; // WDT time out = 512ms. This is the sleep time between probe reads when in Standby mode
// longer sleep times would be better from a power consumption perspective
// but would be too long when the user starts using the unit for its intended purpose -- to detect liquid and as soon as possible
} // void IniReg()
// turn on buzzer
void BuzzOn()
{
TMR2 = 0;
PIR1.TMR2IF = 0;
T2CON.TMR2ON = 1;
}
// turn off buzzer
void BuzzOff()
{
buzz = off;
T2CON.TMR2ON = 0;
}
// audible indicator is armed, with the particular sequence of sounds emitted depending on
// whether liquid has reached low probe, low and high probe, or just the high probe
void EnableAudible()
{
TMR1L = 0;
PIR1.TMR1IF = 0;
INTCON.GIE = 1;
BuzzOn();
}
// both low and high probes not immersed
// PLOval = PHIval = 0
void Standby()
{
STATE = _standby;
BuzzOff(); // following three statements disable audible indicator
T1CON.TMR1ON = 0; //
INTCON.GIE = 0; //
wpu_plo = 1; // enable weak pullup for low probe
wpu_phi = 1; // enable weak pullup for high probe
}
// low probe immersed -- cup nearly full
void PLOimmersed()
{
if (PREVSTATE != _plo_immersed)
{
STATE = _plo_immersed;
TMR1H = t1h_fill;
T1CON = 0b1100001; // Timer1 clock source is system clock (FOSC), prescale = 1:4, timer1 oscillator off, timer1 on
// given 31kHz clock and TMR1H ini value = 15, timer1 tick is ~500ms
EnableAudible();
}
}
// low and high probes both immersed -- cup is full
void PLOPHIimmersed()
{
if (PREVSTATE != _plophi_immersed)
{
STATE = _plophi_immersed;
TMR1H = t1h_fill;
T1CON = 0b1000001; // Timer1 clock source is system clock (FOSC), prescale = 1:1, timer1 oscillator off, timer1 on
// given 31kHz clock and TMR1H ini value = 15, timer1 tick is ~125ms
EnableAudible();
}
}
// probe error -- only high probe is immersed
void PHIimmersed()
{
if (PREVSTATE != _phi_immersed)
{
STATE = _phi_immersed;
TMR1H = t1h_beep1;
T1CON = 0b1000001; // Timer1 clock source is system clock (FOSC), prescale = 1:1, timer1 oscillator off, timer1 on
EnableAudible();
STATEPERROR = _beep1;
}
}
void DetermineState()
{
asm clrwdt
// ===============================
// Read Probes -- for modes other than standby
// Individual weak pull ups are enabled prior to reading and disabled after reading to minimize current draw since the probes are immersed and weak pull ups will draw current
// DAC, weak pull ups, and comparator are turned on before reading and then turned off afterwards to minimize power consumption
// current reading stored in PXXval.f0
// ===============================
CM1CON0.C1ON = 1; // turn on comparator (current draw ~4.5uA)
DACCON0.DACEN = 1; // turn on DAC (current draw ~19uA)
wpu_plo = 1; // enable low probe weak pull up
CM1CON1.C1NCH = ch_plo;
if (CMOUT)
PLOval.f0 = 1;
wpu_plo = 0; // disable low probe weak pull up
wpu_phi = 1; // enable high probe weak pull up
CM1CON1.C1NCH = ch_phi;
if (CMOUT)
PHIval.f0 = 1;
wpu_phi = 0; // disable high probe weak pull up
DACCON0.DACEN = 0; // turn off DAC
CM1CON0.C1ON = 0; // turn off comparator
// ===============================
// Determine state/mode
// ===============================
if (!PHIval)
{
if (!PLOval)
Standby();
else if (PLOval == 0xFF)
PLOimmersed();
}
else if (PHIval == 0xFF)
{
if (PLOval == 0xFF)
PLOPHIimmersed();
else if (!PLOval)
PHIimmersed();
}
// all bits (readings) of PXXval can now be shifted left
// discard PXXval.f7 and make PXXval.f0 ready for the new reading
PLOval <<= 1;
PHIval <<= 1;
PREVSTATE = STATE;
} // void DetermineState()
void interrupt()
{
if (PIR1.TMR1IF)
{
if (STATE == _phi_immersed)
{
if (++STATEPERROR > _pause2)
STATEPERROR = _beep1;
if (!STATEPERROR)
{
BuzzOn();
TMR1H = t1h_beep1; // given 31kHz, system clock as clock source, prescale = 1:1, TMR1H ini value = 30, timer1 tick is ~250ms
}
else if (STATEPERROR == _pause1)
{
BuzzOff();
TMR1H = t1h_pause1; // given 31kHz, system clock as clock source, prescale = 1:1, TMR1H ini value = 19, timer1 tick is ~160ms
}
else if (STATEPERROR == _beep2)
{
BuzzOn();
TMR1H = t1h_beep2; // given 31kHz, system clock as clock source, prescale = 1:1, TMR1H ini value = 30, timer1 tick is ~250ms
}
else
{
BuzzOff();
TMR1H = t1h_pause2; // given 31kHz, system clock as clock source, prescale = 1:1, TMR1H ini value = 122, timer1 tick is ~1000ms
}
} // if (STATE == _phi_immersed)
else // if (STATE == _plo_immersed || STATE == _plophi_immersed)
{
TMR1H = t1h_fill;
if (T2CON.TMR2ON)
BuzzOff();
else
BuzzOn();
}
PIR1.TMR1IF = 0;
} // if (PIR1.TMR1IF)
} // void interrupt()
void main()
{
IniReg();
while(1)
{
if (STATE != _standby)
DetermineState(); // just one function call to reduce execution time
else
{
asm sleep // WDT is automatically cleared right before sleep and after waking up
// MCU wakes up upon WDT timeout
// Reduction of MCU awake time during Standby mode reduces current draw
// Ways by which instructions executed when MCU is awake are reduced:
// 1. Probe reading is stored in PXXval only if reading is high
// 2. Weak pull ups are kept on since they don't consume current unless probes are immersed.
// 3. Standby mode is when PLOval = PHIval = PLOlevel = PHIlevel = 0 therefore we need only monitor for instantaneous readings that are high
// 4. Reduction/elimination of function calls and using inline code instead
// DAC and voltage comparator are turned on before reading and then turned off afterwards to minimize power consumption
DACCON0.DACEN = 1; // turn on DAC
CM1CON0.C1ON = 1; // turn on comparator
CM1CON1.C1NCH = ch_phi;
if (CMOUT)
{
STATE = _bouncing;
PLOval.f0 = 1;
}
CM1CON1.C1NCH = ch_plo;
if (CMOUT)
{
STATE = _bouncing;
PHIval.f0 = 1;
}
CM1CON0.C1ON = 0; // turn off comparator
DACCON0.DACEN = 0; // turn off DAC
}
} // while(1)
} // void main()
</pre>erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com0tag:blogger.com,1999:blog-4092193417928690079.post-48471562727819744332012-05-10T20:52:00.002+08:002012-05-10T20:52:53.754+08:00A simple mod to assist the visually impairedAppliance remote controls usually have more than half a dozen keys. And generally they follow the 80/20 rule: 80% of the keys are used 20% of the time. For the blind, it's a nightmare scanning the keys with their fingers and inadvertently pressing the wrong ones, particularly buttons they're unfamiliar with because they don't need to use them.<br /><br />For a particular remote control that my dad uses, only 2 keys out of the total of 9 are relevant to him. To prevent possible errors in operation, I opened up the plastic case and inserted a sheet of paper between the rubber keypad and the printed circuit board. The paper presents a barrier that's electrically nonconductive and prevents contact between the electrically conductive pads of the keys and their corresponding contacts on the pcb. I then cut out the two areas for the buttons that he will be using.<br /><br />Thus, when either of the two keys are pressed, no paper blocks the buttons from making contact with the circuit board, allowing normal operation. On the other hand, pressing any of the other keys, however hard you may do so, will not cause the remote control to send any infrared signal to the appliance because the paper prevents electrical contact.<br /><br />If in the future other keys will need to be "unblocked" it's a simple matter of cutting out the relevant portions of the paper insert.erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com0tag:blogger.com,1999:blog-4092193417928690079.post-88165944896252455992012-05-09T16:44:00.001+08:002012-05-09T16:44:26.078+08:00Princeton Tec EOS headlamp repairI took two of the three AAA batteries from the EOS yesterday to use in an appliance remote control I had just brought back from the dead. I bought replacement batteries today and when I was about to install them I was utterly horrified to see the lone AAA inside shrouded in white greenish ectoplasm. I quickly removed and threw it away. Cleaned the battery case with tissue and used isopropanol soaked Q-tips to remove the gunk from the nooks. Installed fresh batteries. The headlamp immediately lit up as I put the last battery in. That shouldn't happen and was the first omen that not everything is well. Then when I switched off the lamp it magically switched back on again at full brightness a couple of seconds later. Oh no! It's possessed! And it kept turning back on whenever I switched it. And so exorcism was called for.<br />
<br />
To my my initial consternation I discovered that the battery case was held down not by what I thought were torx screws but by heat stakes. I actually tried to carve the heads of the stakes off using the torx screwdriver. No dice. Had to go to the drill press and drill them out, which turned out to be painless--for me that is.<br />
<br />
I could see that the chemical spill had reached the circuit board, particularly the ground (negative power supply) solder joint. I proceeded to clean the board with cotton and alcohol. After I was satisfied I installed the batteries. Uh oh. The lamp turned on automatically again. And after switching it off, it switched itself back on.<br />
<br />
I suspected that the battery goo must have shorted out some of the vias and/or solder joints on the other side of the board. And I now had no choice--other than resigning myself to removing the batteries every time I'm done with the lamp--but to uncouple the board from the battery case. That meant desoldering the two battery terminals and gouging the four heat stakes locking the board to the case. <br />
<br />
I actually thought there were only two heat stakes--yes, my eyesight is really that poor. And that's the reason I had a hard time prying the board off the case. Good thing I didn't inadvertently snap the board or break any tracks.<br />
<br />
Turning the board over I immediately noticed that part of the solder mask on the ground trace had been eaten away. Alkaline battery chemical is really nasty stuff! I lost no time and quickly cleaned the board with alcohol.<br />
<br />
To test the board I used the 3.3V output of my ATX power supply. Homemade cables with banana plugs on one end and alligator clips on the other conveniently routed power to the board without having to solder any wires. Moment of truth. I switched on the power supply. The lamp didn't turn on! Good sign. I then turned the lamp on and then off. 2 seconds ... 3 sec .. 4 ... 5 ... The headlamp's no longer possessed! You should've heard me singing and doing a jig. <br />
<br />
With anxiety level and blood pressure back to normal it was then that I started taking pictures. So all the photos below are after the clean up. I now kind of regret I don't have shots of the ectoplasm-covered battery while it was still in the battery case. Would've been a great <i>pièce de résistance</i> for this horror story.<br />
<br />
<br />
<br />
<span class="hasCaption">That's an Opulent Rebel Star 1-watt white LED
on its own star pcb. Green board contains all the drive circuitry.
Round black cylinder on the left is an inductor. Black square glob on
the right is the microcontroller.</span><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpZZq4HlZtIYwL3RdmmPv4kxr9EomBXIiOlwyTA4ahPCFQPSgsRdjKm559hViucSvFcJ7p_fprXexdKadWJzT-7U2hcI3jquDOnwZCmnj_ECPSsnLsDwgaObyoM9KhaIeU2j4210jhBjA/s1600/princetec69.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="272" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpZZq4HlZtIYwL3RdmmPv4kxr9EomBXIiOlwyTA4ahPCFQPSgsRdjKm559hViucSvFcJ7p_fprXexdKadWJzT-7U2hcI3jquDOnwZCmnj_ECPSsnLsDwgaObyoM9KhaIeU2j4210jhBjA/s320/princetec69.png" width="320" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-Gr1XYyk-b02TXHw9TY_jWmi9q-vBwKErBYnDnLLx1hPKP7gqEe4S9dVpXRFk2nwF3nNxrQk7Nb2GvR4rshyTwMuNQnFdNTI62ItuwrEjkal998w8d1TFtxRR4ZB34N0DSRvtdpTc_3s/s1600/princetec72.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-Gr1XYyk-b02TXHw9TY_jWmi9q-vBwKErBYnDnLLx1hPKP7gqEe4S9dVpXRFk2nwF3nNxrQk7Nb2GvR4rshyTwMuNQnFdNTI62ItuwrEjkal998w8d1TFtxRR4ZB34N0DSRvtdpTc_3s/s320/princetec72.png" width="320" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxRT589NrZhtNN3hSsFzdKf8RU-JaowvxqVl5USA0rQ0HBb92edCBPqDe7R0vnz7vgBekfTkJNxkwaQfQsiGGvjt3vdc60GGIz8zGxxWdkzkRoQUTzGlGtsOu0vnQak-hQaUPUBrugDkI/s1600/princetec73.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxRT589NrZhtNN3hSsFzdKf8RU-JaowvxqVl5USA0rQ0HBb92edCBPqDe7R0vnz7vgBekfTkJNxkwaQfQsiGGvjt3vdc60GGIz8zGxxWdkzkRoQUTzGlGtsOu0vnQak-hQaUPUBrugDkI/s320/princetec73.png" width="320" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBpH9m2X2sEvMbKi1hVQGrtbPdPws76ofc7Sv5-bAslZNCb-KlUSsMNSQSBBBleNcEPotZxQk-vDvD0uWMxiRBSGmU4ocFwUcnuUuM-SoB00y2p7MRM1VEQzMrjO_04jPGl7nwgVco1O8/s1600/princetec74.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBpH9m2X2sEvMbKi1hVQGrtbPdPws76ofc7Sv5-bAslZNCb-KlUSsMNSQSBBBleNcEPotZxQk-vDvD0uWMxiRBSGmU4ocFwUcnuUuM-SoB00y2p7MRM1VEQzMrjO_04jPGl7nwgVco1O8/s320/princetec74.png" width="320" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtqjt_POCsHfyLl2HSPCflN1yUkjRYdJ_v8KLfaHUcGouVIsaN35_6oBrKhhwZ1JtJ0Lp1JBb5JZi7uniz8UfrXuqPREr3qGnwno_O6F9ccjx6jZFeEUfDPihjFidaS9UjH7oRNHrdCKI/s1600/princetec75.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtqjt_POCsHfyLl2HSPCflN1yUkjRYdJ_v8KLfaHUcGouVIsaN35_6oBrKhhwZ1JtJ0Lp1JBb5JZi7uniz8UfrXuqPREr3qGnwno_O6F9ccjx6jZFeEUfDPihjFidaS9UjH7oRNHrdCKI/s320/princetec75.png" width="320" /></a></div>
<br />
White rectangular thing on the top right is the momentary contact switch.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEIF_gN8S3t3pzFBFpBTFHTjtBZbS82vJqCogeaFl_aPWexKCLbHxkKM5ROaPT3HwrCS3U6Ec-TNlu0Ze2dc5BK_Q9hDhpzsVlFogDdB-rVui_ROeTgquP1bVlrtZYR38_lZGnpcE5wMI/s1600/princetec76.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEIF_gN8S3t3pzFBFpBTFHTjtBZbS82vJqCogeaFl_aPWexKCLbHxkKM5ROaPT3HwrCS3U6Ec-TNlu0Ze2dc5BK_Q9hDhpzsVlFogDdB-rVui_ROeTgquP1bVlrtZYR38_lZGnpcE5wMI/s320/princetec76.png" width="320" /></a></div>
<br />
<span class="hasCaption">Solder mask partially dissolved by the
horrible stuff that leaked out of the alkaline AAA battery. The gold
flashing surely prevented the copper from being attacked. The offending battery chemical spill bridged
the cluster of three vias and the ground throughhole solder
pad.</span><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuZLDgQRFpAhHJR820Ivtyrf-HeuQqkW1vrFUFors3b4gXImTtDDtzSEA7iWDsAPlf9BXkwb1Y9dptREsQohG-WYP-ldFFbdDU5uNdymX-re_OkM6gkbemoLty-I11CG36xQp_3lQiH24/s1600/princetec77.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuZLDgQRFpAhHJR820Ivtyrf-HeuQqkW1vrFUFors3b4gXImTtDDtzSEA7iWDsAPlf9BXkwb1Y9dptREsQohG-WYP-ldFFbdDU5uNdymX-re_OkM6gkbemoLty-I11CG36xQp_3lQiH24/s320/princetec77.png" width="320" /></a></div>
<br />
Another angle of the same. All that glitters is gold.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbIpvxVEIJncIzwBATndEf05gFiKZLw-d7HhlbHSBOmOpuEas0sVvaQdPhH5fhETGGZ_fuuvW00cegK165iEJb0b6gukMTdSxbfyIOFXe_nv5V62VM5happHzAmz9YBvEaDMGqEsz91RQ/s1600/princetec78.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbIpvxVEIJncIzwBATndEf05gFiKZLw-d7HhlbHSBOmOpuEas0sVvaQdPhH5fhETGGZ_fuuvW00cegK165iEJb0b6gukMTdSxbfyIOFXe_nv5V62VM5happHzAmz9YBvEaDMGqEsz91RQ/s320/princetec78.png" width="320" /></a></div>
<br />
Who's Azoteq?<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkLyzH5c6nE1WuAEIQr1VbDMGB16apgo75EJe0JPu0QBUEMZ4yXrNPxFD3q1OJvCiZREgIqBWkWvGjp_S5QQPMkDs7J9XB5-ezTi23-1_aaavPDLf4QZ2PTtVP6v34Du50iSesXf2nHes/s1600/princetec79.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjkLyzH5c6nE1WuAEIQr1VbDMGB16apgo75EJe0JPu0QBUEMZ4yXrNPxFD3q1OJvCiZREgIqBWkWvGjp_S5QQPMkDs7J9XB5-ezTi23-1_aaavPDLf4QZ2PTtVP6v34Du50iSesXf2nHes/s320/princetec79.png" width="320" /></a></div>
The LED lens<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdz6LW0EOlRdcNTv22TH2LWAC3Ua06Ezqabez-1k8HiDwjSMt5XWOvfeflCH-b32ptDmKA9BYIRP82BnUAB46dJuYCFhmrByzIiM8Ka9VmLisdf_Az0_GZdCc8FzECaxelyWHru812AEM/s1600/princetec82.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdz6LW0EOlRdcNTv22TH2LWAC3Ua06Ezqabez-1k8HiDwjSMt5XWOvfeflCH-b32ptDmKA9BYIRP82BnUAB46dJuYCFhmrByzIiM8Ka9VmLisdf_Az0_GZdCc8FzECaxelyWHru812AEM/s320/princetec82.png" width="320" /></a></div>
<br />
The holes snapped into four studs on the battery case<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0jK0ws2IcL_2g_4K4mqIEC9UEIWdUUx3SHgV0jCFpYgPXfZzpJf9WO2Ps0zPlrXDhnwmt_zjp0aw-tX6qQ-iihzphYAi2HieLXfhhKRBXYcU3bZR-4XqJawUrxKkf_txJpdiBSMiydzQ/s1600/princetec84.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh0jK0ws2IcL_2g_4K4mqIEC9UEIWdUUx3SHgV0jCFpYgPXfZzpJf9WO2Ps0zPlrXDhnwmt_zjp0aw-tX6qQ-iihzphYAi2HieLXfhhKRBXYcU3bZR-4XqJawUrxKkf_txJpdiBSMiydzQ/s320/princetec84.png" width="320" /></a></div>
<br />
The heat stakes are on the four crosses. The LED lens sits on the four studs close to the center. <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitESse5nZz-kVYBEgyETgBSVekPddWzXUVgd_qkgVCNeopCCck6dln-3it09YTORR441igSEs7gDGaxFyxzXDV4n3roC6Ir65IdgDXWZCPkDDxgRjbA0OKb0fDLvRBFuWQEozxbr_FxZM/s1600/princetec85.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="288" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitESse5nZz-kVYBEgyETgBSVekPddWzXUVgd_qkgVCNeopCCck6dln-3it09YTORR441igSEs7gDGaxFyxzXDV4n3roC6Ir65IdgDXWZCPkDDxgRjbA0OKb0fDLvRBFuWQEozxbr_FxZM/s320/princetec85.png" width="320" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixNwZkKrmuzghoTWgRLqt5VQgdpSiMyqik245umYtw-K755L1yS416yevULIuOYfAm72JWIA2XAxkeNteXRHPf4fEIizD-_nSObLWJNE_VjIlnWufQoE_JPHx9xL0MFivT5K_1-RkOwIQ/s1600/princetec87.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="285" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixNwZkKrmuzghoTWgRLqt5VQgdpSiMyqik245umYtw-K755L1yS416yevULIuOYfAm72JWIA2XAxkeNteXRHPf4fEIizD-_nSObLWJNE_VjIlnWufQoE_JPHx9xL0MFivT5K_1-RkOwIQ/s320/princetec87.png" width="320" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRz4gNJIg-O_CwbWYOJw_J8YcfJCvZmAFsN1zRXZCETE77QUVdMp1mp0NyVSLTSIvrJPLznb8eB8Ihbb7cOpWc7VT5oWE7n4kc_eGiwMtIKFVIWa9SBAfILHGY5RamnBY2BZFw5_ztfSs/s1600/princetec89.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRz4gNJIg-O_CwbWYOJw_J8YcfJCvZmAFsN1zRXZCETE77QUVdMp1mp0NyVSLTSIvrJPLznb8eB8Ihbb7cOpWc7VT5oWE7n4kc_eGiwMtIKFVIWa9SBAfILHGY5RamnBY2BZFw5_ztfSs/s320/princetec89.png" width="320" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtPKcD1U1BTPsUllJUfzMx8g0x36jo_kfHwdCd8xbVHaftDcY1m3Py4bkRiWsENwPUvFo7GEL3Vqc9Na_TQRbZzdaRjhvMnfWGmRwQ6h3bf-mc82xoAvWwTGD7lO-59m755FtNxwApxXY/s1600/princetec90.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtPKcD1U1BTPsUllJUfzMx8g0x36jo_kfHwdCd8xbVHaftDcY1m3Py4bkRiWsENwPUvFo7GEL3Vqc9Na_TQRbZzdaRjhvMnfWGmRwQ6h3bf-mc82xoAvWwTGD7lO-59m755FtNxwApxXY/s320/princetec90.png" width="320" /></a></div>
<br />
<br />erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com1tag:blogger.com,1999:blog-4092193417928690079.post-29378504493678870012012-05-06T22:26:00.000+08:002012-05-06T22:26:09.791+08:00Current draw of the pcb versions of the beverage sensorJust finished assembling a second unit of the <a href="http://electromotiveforces.blogspot.com/2012/04/liquid-level-indicator-for-visually.html" target="_blank">liquid level indicator</a>. I should give dad half a dozen--he's too concerned about breaking and overusing the gadget. Found a balloon whisk made of stainless steel wires. Used that for the probes. Now corrosion won't ever be a problem (hopefully).<br />
<br />
All along I've been measuring the currents of a breadboarded circuit. It was only prudent to check to see how the pcb versions are doing. Both units have been loaded with the <a href="http://electromotiveforces.blogspot.com/2012/05/further-improvements-to-beverage-sensor.html" target="_blank">latest firmware version</a>. Here are the values I got for the two units. <br />
<br />
<table border="1" cellpadding="5" cellspacing="0">
<tbody>
<tr>
<th rowspan="2">Standby mode</th>
<th colspan="3">Current (µA)</th>
<th rowspan="2">Battery voltage (V)</th>
</tr>
<tr>
<th>Max</th>
<th>Min</th>
<th>Ave</th>
</tr>
<tr>
<td>1st unit (April 28)</td>
<td>2.05</td>
<td>0.48</td>
<td>0.74</td>
<td>3.099</td>
</tr>
<tr>
<td>2nd unit (May 6)</td>
<td>2.29</td>
<td>0.56</td>
<td>0.80</td>
<td>3.207</td>
</tr>
</tbody></table>
<br />
The earlier unit didn't use a new battery. Its initial voltage was around 3.14 when I installed it in the circuit. The latest unit does use a new one. With a higher voltage than the 3.0V of the breadboarded version the higher currents are surprising. The latest unit, however, seems to be drawing a lot more current. But it seems to be within the specs of the MCU. Then again there could be some leakage current along the board. <br />
<br />
I failed to mention in the last post that I also measured the DAC and voltage comparator currents in the breadboarded version. I modified the firmware so that the comparator was kept enabled throughout. I then measured the standby current. Thereafter, I changed the firmware back to its original form and kept the DAC enabled throughout and measured the standby current. I obtained a DAC current of approximately 19µA and a voltage comparator current of around 4.5µA. Enabling these modules only during probe reads does indeed help minimize power consumption.erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com0tag:blogger.com,1999:blog-4092193417928690079.post-1245663422072151572012-05-06T14:56:00.000+08:002012-05-18T21:32:21.779+08:00Further improvements to the beverage sensorI can't help myself. I'm driven to endlessly tweak and minimize the power consumption of the <a href="http://electromotiveforces.blogspot.com/2012/05/measuring-current-draw-of-liquid-level.html" target="_blank">liquid level indicator</a>. My biggest headache at the moment is the piezo transducer which is sucking as much as 4mA. Not good for the battery at all. As per the <a href="http://www.maxell.co.jp/e/products/industrial/battery/cr/pdf/CR2032_DataSheet_e.pdf" target="_blank">Maxell CR2032 datasheet</a> the plots in the discharge capacity vs discharge current graph don't go beyond 3.5mA which probably implies the battery isn't designed to output that much current. Moreover, at room temperature and above, the discharge capacity takes a nosedive after current exceeds 2mA.<br />
<br />
To address these issues and remain within the limits of the battery, I've decreased the pulse duration to the transducer by reducing the duty cycle from 50% to 24%. As it was, achieving the 50% duty cycle the firmware used timer2 interrupt to toggle pin RA2 output. To implement a DC other than 50% I've opted to enable the PWM module. RA2 is also the CCP1 pin (one of two alternate pins) so this isn't a problem.<br />
<br />
I tested various duty cycle values and recorded the associated current. Bear in mind that for this test I modified the firmware slightly so that the buzzer sounds continuously--there are no pauses wherein the buzzer is turned off--when probes are immersed. Here are the values I obtained. <br />
<br />
<table border="1" cellpadding="5" cellspacing="0">
<tbody>
<tr>
<th>Duty Cycle (%)</th>
<th>Average Current (mA)</th>
</tr>
<tr>
<td>50</td>
<td>3.99</td>
</tr>
<tr>
<td>24</td>
<td>2.154</td>
</tr>
<tr>
<td>12</td>
<td>1.162</td>
</tr>
<tr>
<td>5</td>
<td>0.545</td>
</tr>
</tbody></table>
<br />
I then posted the numbers on a spreadsheet and had it perform a simple linear regression analysis. Turns out the correlation is >99%. Coefficient <i>a</i> = 7.60 and constant <i>b</i> = 0.23. So the equation--valid for DC = 5% to 50% and
series capacitance = 1uF--is<br />
<br />
I = <i>a</i>DC + <i>b = </i>7.6DC + 0.23<br />
<br />
where I = current, DC = duty cycle. That <i>b</i> is nonzero befuddled me for a moment since when DC = 0, current should be zero. Then I realized <i>b </i>is the overheard current--this is current flowing through the
various microcontroller modules including the MFINTOSC, comparator, DAC, weak pull-ups, and WDT.<br />
<br />
Unfortunately I don't have a sound level meter to measure the change in audio volume. Volume definitely is reduced as DC is lowered but I don't know the exact relationship. Nevertheless, halving DC to 24% does not greatly diminish the sound level and is still very much audible. In the firmware below I use 24% as the duty cycle. I chose this value because the maximum current that the transducer draws at this DC is around 2mA which in the CR2032 datasheet translates to a discharge capacity of around 200mAh. We can reduce DC further of course but transducer volume suffers. So the DC value is a compromise between battery life and buzzer volume.<br />
<br />
On a different note (ha! pun unintended or is it?), I've been able to further reduce the current during standby mode by taking down 4 statements which correspond to 4 assembly instructions. According to my tests the internal weak pull-ups don't consume any current unless the input pins have a path to ground--meaning, in the case of our circuit, liquid is detected or the pin is shorted to ground. So what I did was to keep the global and individual weak pull-up register bits enabled during standby mode. But once liquid is detected the individual weak pull-ups are enabled only during probe reads. As you'll see in the firmware, there are now two functions for probe reads: <code>ReadProbesV1()</code> and <code>ReadProbesV2()</code>. There's loads of unused Flash memory (only around 10% is used) so having two nearly identical functions albeit with a net result of lower power consumption makes a lot of sense. Measurements show that maximum current has dropped from ~2.54 to ~2.05µA (this is the current draw when the MCU is awake) and average current from ~0.75µA to ~0.69µA. The latter is an 8% reduction. That isn't too bad I think.<br />
<br />
Just to make sure setting unused pins as digital output is really better than configuring them as inputs, I compared the currents for the various configurations.<br />
<br />
<table border="1" cellpadding="5" cellspacing="0">
<tbody>
<tr>
<th rowspan="2">Configuration</th>
<th colspan="3">Current (µA)</th>
</tr>
<tr>
<th>Max</th>
<th>Min</th>
<th>Ave</th>
</tr>
<tr>
<td>All unused pins are digital inputs</td>
<td>12.57</td>
<td>8.81</td>
<td>9.33</td>
</tr>
<tr>
<td>All unused pins are analog inputs</td>
<td>8.01</td>
<td>4.95</td>
<td>5.31</td>
</tr>
<tr>
<td>All unused pins are digital outputs (with RAx pulled to ground)</td>
<td>2.54</td>
<td>0.36</td>
<td>0.74</td>
</tr>
</tbody></table>
<br />
Without a shadow of a doubt, digital output pins win hands down.<br />
<br />
<br />
Below is the latest version of the firmware. Besides the changes mentioned above, there are other minor ones which don't significantly affect power consumption. <br />
<br />
<pre>/*
Liquid Level Indicator version 2
May 2012
processor = PIC12LF1840
compiler = mikroC v5.6.0
configuration word:
INTOSC with I/O on clk pin
enabled: power up timer, WDT via SWDTEN, MCLR, stack over/underflow
all else disabled
CONFIG1 :$8007 : 0x09CC
CONFIG2 :$8008 : 0x1613
To minimize power consumption BOR should be disabled and MCLR enabled (so that RA3 is not left floating)
*/
#define int1 bit
#define int8 unsigned char
#define int16 unsigned int
#define int32 unsigned long
#define on 1
#define off 0
#define input 1 // for TRISx
#define output 0 // for TRISx
#define analog 1 // for ANSELx
#define digital 0 // for ANSELx
#define hi 1 // switch level high
#define lo 0 // switch level low
#define buzz LATA.f2
#define an_plo ANSELA.f1
#define an_phi ANSELA.f4
#define tris_plo TRISA.f1
#define tris_phi TRISA.f4
#define wpu_plo WPUA.f1
#define wpu_phi WPUA.f4
#define ch_plo 0 // comparator input channel for low probe
#define ch_phi 1 // comparator input channel for high probe
#define t1h_fill 256-30 // TMR1H initial value, for audible indicator when low probe immersed or low and high probe immersed
#define t1h_beep1 256-31 // TMR1H initial value, for first beep during probe error
#define t1h_pause1 256-19 // TMR1H initial value, for first pause during probe error
#define t1h_beep2 256-31 // TMR1H initial value, for second beep during probe error
#define t1h_pause2 256-122 // TMR1H initial value, for second pause during probe error
#define osc500khz 0b111000 // 500kHz MFINTOSC, for use with OSCCON
#define osc31khz 0b0 // 31kHz LFINTOSC, for use with OSCCON
#define wdt8ms 0b111 // value for WDTCON
#define wdt16ms 0b1001 // value for WDTCON
#define wdt32ms 0b1011 // value for WDTCON
#define wdt128ms 0b1111 // value for WDTCON
#define wdt256ms 0b10001 // value for WDTCON
#define wdt512ms 0b10011 // value for WDTCON
int8 PLOval; // last five low probe readings
int8 PHIval; // last five high probe readings
bit PLOlevel; // voltage level of low probe when not bouncing (hi = 1, lo = 0)
bit PHIlevel; // voltage level of high probe when not bouncing (hi = 1, lo = 0)
bit plo; // comparator reading of low probe. 1 = liquid detected, 0 = no detection
bit phi; // comparator reading of high probe. 1 = liquid detected, 0 = no detection
bit buzzing; // buzzer status flag; 1 = buzzer is on, 0 = buzzer off
enum {_begin, _standby, _insta_plo, _immersed_plo, _insta_phi, _immersed_phi, _probe_error}
STATE = _begin, // current state
PREVSTATE = _begin; // previous state
enum {_beep1, _pause1, _beep2, _pause2} STATEPERROR; // for use with buzzer sound pattern when probe error detected
// ===========================================================================================
// Functions
// ===========================================================================================
void IniReg()
{
ANSELA = digital;
TRISA = output;
PORTA = 0;
an_plo = analog;
an_phi = analog;
tris_plo = input;
tris_phi = input;
WPUA = 0; // disable individual pull ups
OPTION_REG = 0b00000010; // global pull ups enabled, timer0 prescale = 1:8
// given clock = 500khz, TMR0 increments from zero to 255, prescale = 1:8, timer0 tick = 256*8 / (500kHz/4) = 16.384ms
wpu_plo = 1;
wpu_phi = 1;
DACCON0 = 0; // DAC off, DAC is not output on DACOUT pin, Vdd as positive source
DACCON1 = 0b11000; // 0x18, Vref = 24/32 = 75% of Vdd
CM1CON0 = 0b10; // comparator off, comp output polarity not inverted, comp output internal only,
// comparator in low power low speed mode, hysteresis enabled
CM1CON1 = 0b10000; // comparator interrupts disabled, C1VP connected to DAC, C1VN connected to C1N0-
// piezoelectric transducer empirically determined to be loudest at 2.15kHz
// transducer is energized at a fixed frequency of 2.155kHz and fixed duty cycle of 24%
// Current draw of the transducer has been determined to be directly proportional to duty cycle.
// Audio volume of the transducer is proportional to duty cycle, but whether it is linear, logarithmic, or otherwise, is unknown
// PWM Period = (PR2 + 1) x 4 x Tosc x (TMR2 Prescale Value), where Tosc = 1/Fosc
// Duty Cycle = (CCPR1L:CCP1CON<5:4>) / [4 x (PR2 + 1)]
// Given PR2 = 57, timer2 prescale = 1, CCPR1L:CCP1CON<5:4> = 56, Fosc = 500kHz,
// PWM period = 464us (freq = 2.155kHz) and duty cycle = 24%
PR2 = 57;
T2CON = 0; // prescale = 1:1, postscale = 1:1, timer2 off
CCPR1L = 0b1110; // with CCPR1L = 0b1110 and CCP1CON<5:4> = 0b00, CCPR1L:CCP1CON<5:4> = 0b111000 = 56 decimal
INTCON.GIE = 0;
INTCON.PEIE = 1;
// initialize probe readings to zero and level to zero
PLOval = 0;
PHIval = 0;
PLOlevel = lo;
PHIlevel = lo;
buzzing = 0;
OSCCON = osc31khz; // default INTOSC frequency = 500khz. Change to 31kHz LFINTOSC after initialization.
// in all probability when coin battery is inserted and circuit powered up, the probes are not immersed in liquid and so the MCU will be put to sleep
} // void IniReg()
// turn on buzzer
void BuzzOn()
{
TMR2 = 0;
PIR1.TMR2IF = 0;
CCP1CON = 0b1100; // PWM mode with P1A active high, P1B disabled, CCP1CON<5:4> = 0b00
T2CON.TMR2ON = 1;
buzzing = 1;
}
// turn off buzzer
void BuzzOff()
{
buzz = off;
T2CON.TMR2ON = 0;
CCP1CON = 0; // CCP off
buzzing = 0;
}
// audible indicator is armed and the specific sequence of sounds emitted depends on
// whether liquid has reached low probe or high probe or whether a probe error has occurred.
void EnableAudible()
{
TMR1L = 0;
PIR1.TMR1IF = 0;
PIE1.TMR1IE = 1;
INTCON.GIE = 1;
BuzzOn();
}
void DisableAudible()
{
BuzzOff();
T1CON.TMR1ON = 0;
PIE1.TMR1IE = 0;
INTCON.GIE = 0;
}
// ReadProbes Version 1 -- for Standby mode only -- weak pull ups are always on since weak pull ups don't consume current unless probes are immersed.
// reducing the number of instructions (enabling and disabling weak pull ups) reduces time when MCU is awake and therefore reduces current draw
// voltage comparator reads the probes
// Weak pull ups are assumed to be already enabled. DAC and comparator are turned on before reading and then turned off afterwards to minimize power consumption
void ReadProbesV1()
{
DACCON0.DACEN = 1; // turn on DAC
CM1CON0.C1ON = 1; // turn on comparator
CM1CON1.C1NCH = ch_plo;
if (CMOUT)
plo = 1;
else
plo = 0;
CM1CON1.C1NCH = ch_phi;
if (CMOUT)
phi = 1;
else
phi = 0;
CM1CON0.C1ON = 0; // turn off comparator
DACCON0.DACEN = 0; // turn off DAC
} // void ReadProbesV1()
// ReadProbes Version 2 -- for all other modes except Standby.
// Individual weak pull ups are enabled and disabled to minimize current draw since the probes are immersed and weak pull ups will draw current
// voltage comparator reads the probes
// DAC, weak pull ups, and comparator are turned on before reading and then turned off afterwards to minimize power consumption
void ReadProbesV2()
{
DACCON0.DACEN = 1; // turn on DAC
CM1CON0.C1ON = 1; // turn on comparator
wpu_plo = 1; // enable low probe weak pull up
CM1CON1.C1NCH = ch_plo;
if (CMOUT)
plo = 1;
else
plo = 0;
wpu_plo = 0; // disable low probe weak pull up
wpu_phi = 1; // enable high probe weak pull up
CM1CON1.C1NCH = ch_phi;
if (CMOUT)
phi = 1;
else
phi = 0;
wpu_phi = 0; // disable high probe weak pull up
CM1CON0.C1ON = 0; // turn off comparator
DACCON0.DACEN = 0; // turn off DAC
} // void ReadProbesV2()
void Debounce()
{
// shift all bits to the left
// if probe reading is high then PXXval bit 0 = 1
PLOval <<= 1;
if (plo)
++PLOval;
PLOval.f5 = 0; // only bits 0 to 4 (5 least significant bits) are used so bits 5 to 7 must be cleared
// need only clear bit 5 because this zero will eventually be left shifted into bits 6 and 7
PHIval <<= 1;
if (phi)
++PHIval;
PHIval.f5 = 0; // only bits 0 to 4 (5 least significant bits) are used so bits 5 to 7 must be cleared
// need only clear bit 5 because this zero will eventually be left shifted into bits 6 and 7
if (!PLOlevel)
{
if (PLOval == 0b11111)
PLOlevel = hi;
}
else if (!PLOval)
PLOlevel = lo;
if (!PHIlevel)
{
if (PHIval == 0b11111)
PHIlevel = hi;
}
else if (!PHIval)
PHIlevel = lo;
} // void DebounceSwitch()
void Standby()
{
if (PREVSTATE != _standby)
{
STATE = _standby;
OSCCON = osc31khz; // 31kHz LFINTOSC
DisableAudible();
WDTCON = wdt512ms;
wpu_plo = 1;
wpu_phi = 1;
}
asm sleep
}
void InstaPLO()
{
if (PREVSTATE != _insta_plo)
{
STATE = _insta_plo;
OSCCON = osc31khz; // 31kHz LFINTOSC
DisableAudible();
}
asm clrwdt // this clrwdt is absolutely necessary or there will be a wdt timeout every time plo is high
WDTCON = wdt8ms; // since plo is high (liquid has been detected during this pass) shorten sleep and check plo more often than during standby mode
asm sleep
WDTCON = wdt128ms; // return wdt period to at least 32ms-- one loop through void main() at clock = 31khz LFINTOSC takes around 10 to 14ms
}
void InstaPHI()
{
if (PREVSTATE != _insta_phi)
{
STATE = _insta_phi;
OSCCON = osc31khz; // 31kHz LFINTOSC
DisableAudible();
}
asm clrwdt // this clrwdt is absolutely necessary or there will be a wdt timeout every time phi is high
WDTCON = wdt8ms; // since phi is high (liquid has been detected during this pass) shorten sleep and check phi more often than during standby mode
asm sleep
WDTCON = wdt128ms; // return wdt period to at least 32ms-- instructions in void main() during 31khz LFINTOSC take around 10 to 14ms
}
void ImmersedPLO()
{
if (PREVSTATE != _immersed_plo)
{
OSCCON = osc500khz; // 500kHz MFINTOSC
STATE = _immersed_plo;
INTCON.TMR0IF = 0;
TMR0 = 0;
WDTCON = wdt128ms;
TMR1H = t1h_fill;
T1CON = 0b110001; // Timer1 clock source is instruction clock (FOSC/4), prescale = 1:8, timer1 oscillator off, timer1 on
EnableAudible();
}
}
void ImmersedPHI()
{
if (PREVSTATE != _immersed_phi)
{
OSCCON = osc500khz; // 500kHz MFINTOSC
STATE = _immersed_phi;
INTCON.TMR0IF = 0;
TMR0 = 0;
WDTCON = wdt128ms;
TMR1H = t1h_fill;
T1CON = 0b10001; // Timer1 clock source is instruction clock (FOSC/4), prescale = 1:2, timer1 oscillator off, timer1 on
EnableAudible();
}
}
void ProbeError()
{
if (PREVSTATE != _probe_error)
{
OSCCON = osc500khz; // 500kHz MFINTOSC
STATE = _probe_error;
INTCON.TMR0IF = 0;
TMR0 = 0;
WDTCON = wdt128ms;
TMR1H = t1h_beep1;
T1CON = 0b100001; // Timer1 clock source is instruction clock (FOSC/4), prescale = 1:4, timer1 oscillator off, timer1 on
EnableAudible();
STATEPERROR = _beep1;
}
}
void Status()
{
if (!PHIlevel)
{
if (!PLOlevel)
{
if (plo) // current pass through comparator has detected liquid at low probe but there is no confirmation of submersion yet (PLOlevel is low), and hi probe not submerged
InstaPLO();
else if (phi)
InstaPHI();
else
Standby();
}
else
ImmersedPLO();
}
else // if (PHIlevel)
{
if (PLOlevel)
ImmersedPHI();
else
ProbeError();
}
PREVSTATE = STATE;
}
void interrupt()
{
if (PIE1.TMR1IE && PIR1.TMR1IF)
{
if (STATE == _probe_error)
{
if (++STATEPERROR > _pause2)
STATEPERROR = _beep1;
switch (STATEPERROR)
{
default:
case _beep1:
BuzzOn();
TMR1H = t1h_beep1;
break;
case _pause1:
BuzzOff();
TMR1H = t1h_pause1;
break;
case _beep2:
BuzzOn();
TMR1H = t1h_beep2;
break;
case _pause2:
BuzzOff();
TMR1H = t1h_pause2;
break;
} // switch
} // if (STATE == _probe_error)
else // if (STATE == _immersed_plo || STATE == _immersed_phi)
{
TMR1H = t1h_fill;
if (buzzing)
BuzzOff();
else
BuzzOn();
}
PIR1.TMR1IF = 0;
} // if (PIE1.TMR1IE && PIR1.TMR1IF)
} // void interrupt()
void main()
{
IniReg();
while(1)
{
if (STATE == _standby) // during standby there is no need to run the debounce routine. Just record the instantaneous value of the probes
{
ReadProbesV1();
PLOval.f0 = plo;
PHIval.f0 = phi;
Status();
}
else
{
if (OSCCON == osc500khz) // this conditional is equivalent to "if (STATE == _immersed_plo || STATE == _immersed_phi || STATE == _probe_error)" since these states run at 500kHz
{
while (!INTCON.TMR0IF) ; // do nothing until one timer0 tick has elapsed
INTCON.TMR0IF = 0;
asm clrwdt
}
ReadProbesV2();
Debounce();
Status();
}
}
}
</pre>erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com0tag:blogger.com,1999:blog-4092193417928690079.post-65874391201360871642012-05-03T08:48:00.000+08:002012-05-03T08:50:42.629+08:00Measuring the current draw of the liquid level indicatorI breadboarded the <a href="http://electromotiveforces.blogspot.com/2012/04/liquid-level-indicator-for-visually.html" target="_blank">liquid level indicator</a> circuit to measure its current draw when operating in its different modes. I powered it using the PICkit2 set to 3.0V. I ran out of 1µF capacitors so C1 = 1.5µF. To simulate immersion in a water-based liquid I used 1K resistors.<br />
<br />
V<sub>DD </sub>was measured using a Fluke 8842A. All current measurements were taken using a Fluke 87V set to high resolution, with MIN-MAX enabled, and with selector knob set to µA when measuring standby mode current and to mA range for all others. Reading was nulled (REL button pressed) before measurements were taken. The 87V probes were connected on the high side in series with the PICkit 2 V<sub>DD</sub>. The last significant digits of the average values when the transducer was buzzing kept jumping around--that's the reason for the approximation sign. <br />
<br />
<table border="1" cellpadding="5" cellspacing="0">
<caption>Current draw @V<sub>DD</sub> = 3.01V, ambient temp. = 31°C</caption>
<tbody>
<tr><th>Mode/Condition</th>
<th>Min</th>
<th>Max</th>
<th>Ave</th>
<th>units</th>
</tr>
<tr>
<td>Standby mode</td>
<td>12.65</td>
<td>15.55</td>
<td>13.08</td>
<td>µA</td>
</tr>
<tr>
<td>only low probe immersed</td>
<td>0.137</td>
<td>3.827</td>
<td>~2.01</td>
<td>mA</td>
</tr>
<tr>
<td>low and high probes immersed</td>
<td>0.328</td>
<td>3.654</td>
<td>~2.00</td>
<td>mA</td>
</tr>
<tr>
<td>only high probe immersed</td>
<td>0.139</td>
<td>3.855</td>
<td>~1.28</td>
<td>mA</td>
</tr>
</tbody></table>
<br />
<br />
Since WDT is always on and since it runs off the 31kHz LFINTOSC, both of these modules are always enabled in all modes even during sleep. Therefore the minimum current consumption of the circuit is the sum of these two. According to the datasheet typical current for the LFINTOSC is 4.0µA with a maximum of 22µA. WDT typical current is around 0.5µA. [See very important current measurements for these two and discussion below].<br />
<br />
As I pored over the datasheet I noticed the brownout reset consumes a hefty amount of current--6.9µA typical. It also occurred to me that since I had disabled the MCLR (in the configuration word) this means pin RA3 is configured as a digital input, but is floating--it has no pull-up or pull-down resistor--which can only mean that it's drawing more current than it should. So I checked how the current consumption would change by first enabling MCLR and then disabling BOR. The following are the results. Values in the first row is just a control since the measurements below were made several hours after I had performed the ones above. <br />
<br />
<table border="1" cellpadding="5" cellspacing="0">
<caption>Current draw @V<sub>DD</sub> = 3.01V, ambient temp. = 30°C</caption>
<tbody>
<tr><th>Mode/Condition</th>
<th>Min</th>
<th>Max</th>
<th>Ave</th>
<th>units</th>
</tr>
<tr>
<td>Standby mode, MCLR disabled, BOR enabled</td>
<td>11.73</td>
<td>14.27</td>
<td>12.12</td>
<td>µA</td>
</tr>
<tr>
<td>Standby mode, MCLR enabled, BOR enabled</td>
<td>7.47</td>
<td>9.66</td>
<td>7.93</td>
<td>µA</td>
</tr>
<tr>
<td>Standby mode, MCLR enabled, BOR disabled</td>
<td>0.23</td>
<td>2.57</td>
<td>0.75</td>
<td>µA</td>
</tr>
</tbody></table>
<br />
Clearly, the reduction in power consumption is just incredible. From the averages we can infer that a floating RA3 pin wastes around 4µA, while BOR guzzles some 7µA--just as the datasheet says. With MCLR enabled and BOR disabled, I measured the currents for when low and
high probes are immersed and they're practically the same as in the first table above. <br />
<br />
With all these data we can now compute battery life. The <a href="http://www.maxell.co.jp/e/products/industrial/battery/cr/pdf/CR2032_DataSheet_e.pdf" target="_blank">Maxell CR2032</a> coin cell I'm using has a nominal capacity of 220mAh. Given the average current of 0.75µA when in standby mode the battery should be able to last for 220mAh / 0.75µA = 33.5 years! But of course I built the circuit to be used not displayed on the shelf. So let's say it's used thrice daily and its probes are immersed in liquid for 30 seconds each time. That's 1.5 minutes every 24 hours. In those 90 seconds the circuit is drawing an average current of 2.0mA. Therefore, everyday, the average current is:<br />
<br />
(1.5 min / 60 min x 2000µA + 24 hrs x 0.75µA) / 24.025 hrs = 2.8uA <br />
<br />
If you look at the graphs in the Maxell datasheet you'll see that the discharge capacity declines as the discharge current increases. Let's be conservative and take the 2.0mA as our discharge current. According to the graph discharge capacity will now only be around 200mAh. Thus the discharge duration of the liquid level indicator given our hypothetical conditions is:<br />
<br />
200mAh / 2.8µA = 8 years. <br />
<br />
Not bad. Even derating that by 50% still gives a very good battery lifespan. <br />
<br />
----<br />
<br />
With MCLR enabled and BOR disabled I proceeded to comment out <code>void main()</code> and used the following instead:<br />
<br />
<pre>void main()
{
IniReg();
WDTCON = wdt512ms;
while(1)
{
asm sleep
}
}
</pre>
<br />
With the voltage comparator, DAC, and weak pull-ups out of the picture average current dropped to 0.48µA.<br />
<br />
Using the same <code>main()</code> I then disabled WDT in the configuration word. Average current fell to a mere 0.02µA (pretty much just as Microchip boasts). 20nA is practically the threshhold of the Fluke 87V's measurement capability!<br />
<br />
<br />
Still with MCLR enabled and BOR disabled I tried the following to determine the amount of current the LFINTOSC uses while the MCU is continually awake. <br />
<br />
<pre>void main()
{
IniReg();
WDTCON = wdt512ms;
while(1)
asm clrwdt;
}</pre>
<br />
Average current was 4.11µA with WDT enabled. This agrees well with the datasheet spec for LFINTOSC current. Disabling WDT brought the average current marginally down to 4.03µA.<br />
<br />
From the above measurements I gather that the LFINTOSC current of 4µA in the datasheet refers to when the MCU is awake. However, during sleep and if WDT is running this 4µA no longer applies. As we saw above it was a mere 0.48µA--the WDT current as per datasheet spec.erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com0tag:blogger.com,1999:blog-4092193417928690079.post-81637667266563648572012-04-29T22:17:00.001+08:002012-05-18T21:38:53.147+08:00Liquid level indicator for the visually impairedMy dad has had glaucoma for about a decade now and in the last 7 or so years has been nearly completely blind. He's been able to cope to a large extent, including making coffee (instant) on his own. Lately, however, he's been having difficulty judging whether his cup is nearly full. On several occasions he's overfilled his cup with piping hot water. Fortunately he hasn't scalded himself (yet). At over 80 years old and given his disability, we can expect things to get worse.<br />
<br />
So to at least aid him in making his drinks I decided to build him a liquid level indicator (LLI) similar to the following.<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="480" src="http://www.youtube.com/embed/GlFGlJQDyD8" width="640"></iframe><br />
<br />
I could've bought him any one of the various commercially available units out there, but hey this is such a simple circuit and I couldn't resist the challenge of designing a circuit for which a single coin battery would last for months. But not being the creative sort I've patterned the look of my gizmo after those on the market.<br />
<br />
The principle behind detecting water or water-based liquids using metal probes is pretty straightforward. Since ordinary water is partially conductive we can set up a voltage divider using the liquid and a pull-up resistor of fixed value. (We can reverse the set up using a pull down resistor instead but as we'll see in a moment the former has an advantage given the microcontroller I'm using). A voltage comparator can then be used to detect the change in voltage when the liquid bridges the fixed resistor to ground.<br />
<br />
<br />
<b>Low power specification</b><br />
<br />
Since this is a battery-operated unit power consumption is a big deal. I'm using a Microchip PIC and in order to have the least consumption possible I had to use an LF version. I opted for an 8-pin <a href="http://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en549758" target="_blank">PIC12LF1840</a> (the 12LF1822 would do as well and is slightly cheaper but I haven't used the 1840 and wanted to try it out and up until recently didn't have any known silicon issues; it does now--yeah, drats!). According to the <a href="http://ww1.microchip.com/downloads/en/DeviceDoc/41441B.pdf" target="_blank">datasheet</a> its typical sleep current @3.0V is a mere 20nA with all peripherals shut down and 500nA with the watchdog timer on (the LLI circuit needs WDT to be on at all times). Compare that with the F version of the 1840 which draws 5600nA and 5900nA, respectively, at 3.0V. <br />
<br />
Another way to minimize consumption is by judicious manipulation of oscillator frequency. It's a truism that the higher the clock frequency the higher the power consumption. So whenever possible this circuit uses the the low power 31kHz LFINTOSC (low frequency internal oscillator). The only reason I use the medium frequency MFINTOSC is because the piezoelectric transducer needs a constant 2kHz square wave to make it beep. The typical current consumption at 500kHz MFINTOSC is 124µA versus a mere 4.0µA using the LFINTOSC. <br />
<br />
According to the datasheet the on-board voltage comparator in low power mode draws around 4.9µA. During standby--when no liquid is detected--the circuit could go to sleep with the comparator enabled. The MCU would then wake up when the comparator interrupt flag gets set--liquid has been detected. But given that the watchdog timer uses only about 0.5µA, it's more economical to disable the comparator, put the MCU to sleep, let a WDT timeout (>100ms) wake the MCU, power up the comparator, check the probes, and then go back to sleep if no liquid is detected.<br />
<br />
<br />
<b>Schematic</b><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6b0RNn5pmcLSEeC6E5oAA5bg4I0zas-D1m1xTzwWkNOGAmfo841wuxY-_Vr6ynC43ADCtUzc-qYKB2_8r1knb8yauf1RF5owAeg_ULOfK0QngFwOa0hGYa_fg6BcJFNlQyiTtJpv6TCI/s1600/liquid-level-schematic.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="226" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6b0RNn5pmcLSEeC6E5oAA5bg4I0zas-D1m1xTzwWkNOGAmfo841wuxY-_Vr6ynC43ADCtUzc-qYKB2_8r1knb8yauf1RF5owAeg_ULOfK0QngFwOa0hGYa_fg6BcJFNlQyiTtJpv6TCI/s320/liquid-level-schematic.png" width="320" /></a></div>
I initially used an external pull-up resistor for each for the high
and low probes. It then occurred to me I might be able to use the weak
pull-ups of the microcontroller. Tried it and it worked and so I was
able to dispense with two discretes. Note that the datasheet says the
weak pull-ups typically guzzle a massive 100µA. I'm not sure if this is
true whether or not there is an external path to ground. Just to be
sure, in the firmware, the pull-ups are enabled only when the probes are
being checked.<br />
<br />
The buzzer is not a Sonalert alarm.
It's a simple piezoelectric transducer in a Sonalert-like black plastic
housing. The transducer will not sound if it is simply connected to a
power source. It needs a constant square wave to produce sound. I tested
the transducer using a function generator and to my ear it was loudest
at a frequency of around 2.15kHz. So this is the frequency of the square
wave I feeding it in the LLI. <br />
Connecting the transducer directly to the MCU pin does work but the sound level is very low. Increasing the value of C3 more than 2µF does not make the sound any much louder. 1µF seemed sufficient.<br />
<br />
<b>Firmware</b><br />
<br />
I initially used the following structure to store the values of the instantaneous comparator readings, level, and edge detect.<br />
<br />
<pre>struct
{
val: 5;
edge: 2;
level: 1;
} PLO, PHI;
</pre>
<br />
But the amount of compiled assembly instructions was scandalously high compared to having separate 8-bit and one-bit variables. Since every instruction adds to processing time and therefore time when MCU is awake instead of asleep using the least current, it simply was unthinkable to employ structures, however more elegant they may be from a programming standpoint. (It can be argued, rather successfully, that given my goal of maximum power efficiency, I should've written the firmware in pure assembly rather than C. But my skill in assembly is rusty and right now I'm not ready for a grueling punishment.)<br />
<br />
The MCU regularly checks the status of the probes (time between checks depends on whether no liquid has been detected--standby mode--or if liquid has already been sensed) with the instantaneous one-bit readings stored in a 5-bit variable. The bits in this variable are left shifted each time the probes are read. Thus it stores the five most recent readings. A debounce routine then simply checks if the variable contains all ones or all zeroes. If it's all ones then level = 1, and if it's all zeroes level = 0. A rising edge is defined as the level transition from 0 to 1, and a falling edge the level transition from 1 to 0. Rising and falling edges are not used so the edge variables and statements have been commented out to reduce the size of the assembly and therefore reduce time when the MCU is awake when in standby mode. Again, slashing power consumption is good.<br />
<br />
A probe error is defined as the state where the high probe detects liquid but the low probe doesn't. It may be that the probes have shorted out in his pocket for instance. When this happens the a characteristic audible pattern is sounded to alert the user. Shorting of the high and low probes does not require any alarms to go off.<br />
<br />
The reference voltage for the comparator is derived from the on-board digital-to-analog converter (DAC). I initially set the DAC output voltage to 50% of V<sub>DD</sub>, but increased to 75% in the hopes that even a liquid with an unexpectedly high resistance will be detected. Thus, if the voltage detected by the comparator is <i>less</i> than 0.75V<sub>DD</sub> this is taken to mean that liquid has been detected.<br />
<br />
Global weak pull-up (found in the OPTION register) is always enabled. However, individual weak pull-ups for the two probes are enabled only just after the DAC and comparator are turned on. The pull-ups are turned off right after the particular probe has been read. The comparator and DAC are disabled right after reading both probes.<br />
<br />
Timer 0 is used to set the interval between probe reads when the 500kHz MFINTOSC is being used. When the LFINTOSC is active timer0 is ignored.<br />
<br />
Timer 1 is used to create the three different audible alert patterns. When liquid has been detected at the low probe the pattern is a continuous loop of 500ms beep --> 500ms silence. When liquid has also been detected at the high probe the pattern becomes a continuous loop of 120ms beep --> 120ms silence. When a probe error is sensed the alarm pattern is a continuous loop of 250ms beep --> 150ms silence --> 250ms beep --> 1000ms silence.<br />
<br />
Timer 2 is dedicated to producing the 2.15kHz square wave for the piezoelectric transducer.<br />
<br />
When in standby mode the watchdog timeout is 512ms, meaning the MCU wakes up every half second and checks the status of the probes then goes back to sleep for another half a second. Assuming the current mode is standby (ie., variables PXXlevel for both probes = 0) then once the <i>instantaneous</i> reading of either the low or high probe is high (meaning liquid has been sensed), the WDT timeout is changed to 8ms with the MCU going back to sleep and waking up 8ms later. If the instantaneous reading now becomes low (zero) then WDT timeout reverts back to 512ms, else WDT remains at 8ms. If five consecutive instantaneous readings are all high then PXXlevel goes high, the MCU remains awake, begins using the 500kHz clock, and the appropriate audible alert is sounded.<br />
<br />
Once PLOlevel and PHIlevel both go low and instantaneous readings are zero, the MCU returns to standby mode and reverts to the 31kHz clock.<br />
<br />
<pre>/*
Liquid Level Indicator
April 2012
processor = PIC12LF1840
compiler = mikroC v5.6.0
configuration word:
INTOSC with I/O on clk pin
enabled: power up timer, brownout reset low trip point, WDT via SWDTEN, stack over/underflow
all else disabled
CONFIG1 :$8007 : 0x0F8C
CONFIG2 :$8008 : 0x1613
*/
#define int1 bit
#define int8 unsigned char
#define int16 unsigned int
#define int32 unsigned long
#define on 1
#define off 0
#define input 1 // for TRISx
#define output 0 // for TRISx
#define analog 1 // for ANSELx
#define digital 0 // for ANSELx
#define hi 1 // switch level high
#define lo 0 // switch level low
#define buzz LATA.f2
#define an_plo ANSELA.f1
#define an_phi ANSELA.f4
#define tris_plo TRISA.f1
#define tris_phi TRISA.f4
#define wpu_plo WPUA.f1
#define wpu_phi WPUA.f4
#define ch_plo 0 // comparator input channel
#define ch_phi 1 // comparator input channel
#define t1h_fill 256-30 // TMR1H initial value, for audible indicator when low probe submerged or low and high probe submerged
#define t1h_beep1 256-31 // TMR1H initial value, for audible indicator when probe error
#define t1h_pause1 256-19 // TMR1H initial value, for audible indicator when probe error
#define t1h_beep2 256-31 // TMR1H initial value, for audible indicator when probe error
#define t1h_pause2 256-122 // TMR1H initial value, for audible indicator when probe error
#define osc500khz 0b111000 // 500kHz MFINTOSC, for use with OSCCON
#define osc31khz 0b0 // 31kHz LFINTOSC, for use with OSCCON
#define wdt8ms 0b111 // value for WDTCON
#define wdt16ms 0b1001 // value for WDTCON
#define wdt32ms 0b1011 // value for WDTCON
#define wdt128ms 0b1111 // value for WDTCON
#define wdt256ms 0b10001 // value for WDTCON
#define wdt512ms 0b10011 // value for WDTCON
#define rising 1 // rising edge detected.
#define falling 2 // falling edge detected.
#define none 0 // no edge.
int8 PLOval; // values of last five low probe readings
int8 PHIval; // values of last five high probe readings
//int8 PLOedge; // edge detected?, 0 = no edge detect, 1 = rising edge, 2 = falling edge; other values = Not Used / Undefined for now
//int8 PHIedge;
bit PLOlevel; // voltage level of probe when not bouncing (hi = 1, lo = 0)
bit PHIlevel;
bit plo; // comparator reading of low probe. 1 = liquid detected, 0 = no detection
bit phi; // comparator reading of high probe. 1 = liquid detected, 0 = no detection
bit buzzing; // buzzer status flag; 1 = buzzer is on, 0 = buzzer off
enum {_begin, _standby, _insta_plo, _immersed_plo, _insta_phi, _immersed_phi, _probe_error}
STATE = _begin, // current state
PREVSTATE = _begin; // previous state
enum {_beep1, _pause1, _beep2, _pause2} STATEPERROR;
// ===========================================================================================
// Functions
// ===========================================================================================
void IniReg()
{
ANSELA = digital;
TRISA = output;
PORTA = 0;
an_plo = analog;
an_phi = analog;
tris_plo = input;
tris_phi = input;
PLOval = 0;
PHIval = 0;
PLOlevel = lo;
PHIlevel = lo;
DACCON0 = 0; // DAC off, DAC is not output on DACOUT pin, Vdd as positive source
//DACCON1 = 0b10000; // 0x10, Vref = 16/32 = 50% of Vdd
DACCON1 = 0b11000; // 0x18, Vref = 24/32 = 75% of Vdd
CM1CON0 = 0b10; // comparator off, comp output polarity not inverted, comp output internal only,
// comparator in low power low speed mode, hysteresis enabled
CM1CON1 = 0b10000; // comparator interrupts disabled, C1VP connected to DAC, C1VN connected to C1N0-
WPUA = 0; // disable individual pull ups
OPTION_REG = 0b00000010; // global pull ups enabled, timer0 prescale = 1:8
// given clock = 500khz, TMR0 increments from zero to 255, prescale = 1:8, timer0 tick = 256*8 / (500kHz/4) = 16.384ms
// timer2 initialize
// piezoelectric buzzer toggled on and off at 2.15kHz -- loudest sound is at this frequency (empirically determined)
// with timer2 prescale = 1:1, postscale = 1:1, PR2 = 29, Fosc = 500kHz, timer2 tick = 29 /(500khz/4) = 232us
// buzzer is on 232us and off 232us. Period therefore = 464us. Freq = 2.155khz
PR2 = 29;
T2CON = 0; // prescale = 1:1, postscale = 1:1, timer2 off
buzzing = 0;
INTCON.GIE = 0;
INTCON.PEIE = 1;
OSCCON = osc31khz; // default INTOSC frequency = 500khz. Change to 31kHz LFINTOSC after initialization.
// in all probability when coin battery is inserted and circuit powered up, the probes are not immersed in liquid and so the MCU will be put to sleep
} // void IniReg()
// turn on buzzer
// piezoelectric buzzer needs constant square wave to produce sound
// timer2 provides this square wave with a frequency of 2.15kHz
void BuzzOn()
{
buzz = on;
buzzing = 1;
TMR2 = 0;
PIR1.TMR2IF = 0;
PIE1.TMR2IE = 1;
T2CON.TMR2ON = 1;
}
// turn off buzzer
void BuzzOff()
{
buzz = off;
buzzing = 0;
T2CON.TMR2ON = 0;
PIE1.TMR2IE = 0;
}
// audible indicator is armed and the specific sequence of sounds emitted depends on
// whether liquid has reached low probe or high probe or whether a probe error has occurred.
void EnableAudible()
{
TMR1L = 0;
PIR1.TMR1IF = 0;
PIE1.TMR1IE = 1;
INTCON.GIE = 1;
BuzzOn();
}
void DisableAudible()
{
BuzzOff();
T1CON.TMR1ON = 0;
PIE1.TMR1IE = 0;
INTCON.GIE = 0;
}
// voltage comparator reads the probes
// DAC, weak pull ups, and comparator are turned on before reading and then turned off afterwards to minimize power consumption
void ReadProbes()
{
// OPTION_REG.NOT_WPUEN = 0;
DACCON0.DACEN = 1; // turn on DAC
CM1CON0.C1ON = 1; // turn on comparator
wpu_plo = 1; // enable low probe weak pull up
CM1CON1.C1NCH = ch_plo; // PIC12F1840
if (CMOUT)
plo = 1;
else
plo = 0;
wpu_plo = 0; // disable low probe weak pull up
wpu_phi = 1; // enable high probe weak pull up
CM1CON1.C1NCH = ch_phi; // PIC12F1840
if (CMOUT)
phi = 1;
else
phi = 0;
wpu_phi = 0; // disable high probe weak pull up
CM1CON0.C1ON = 0; // turn off comparator
DACCON0.DACEN = 0; // turn off DAC
}
void Debounce()
{
// shift all bits to the left
// if probe reading is high then PXXval bit 0 = 1
PLOval <<= 1;
if (plo)
++PLOval;
PLOval.f5 = 0; // only bits 0 to 4 (5 least significant bits) are used so bits 5 to 7 must be cleared
// need only clear bit 5 because this zero will eventually be left shifted into bits 6 and 7
PHIval <<= 1;
if (phi)
++PHIval;
PHIval.f5 = 0; // only bits 0 to 4 (5 least significant bits) are used so bits 5 to 7 must be cleared
//PLOedge = none;
//PHIedge = none;
// if level is low and all bits of PXXval are now high then
// a rising edge has been detected
// switch is considered just released when rising edge is detected
// switch level is now high
if ((!PLOlevel) && (PLOval == 0b11111))
{
PLOlevel = hi;
//PLOedge = rising;
}
if ((!PHIlevel) && (PHIval == 0b11111))
{
PHIlevel = hi;
//PHIedge = rising;
}
// if level is high and all bits of PXXval are now low then
// a falling edge has been detected
// switch is considered just pressed when falling edge is detected
// switch level is now low
if ((PLOlevel) && (!PLOval))
{
PLOlevel = lo;
//PLOedge = falling;
}
if ((PHIlevel) && (!PHIval))
{
PHIlevel = lo;
//PHIedge = falling;
}
} // void DebounceSwitch()
void Standby()
{
if (PREVSTATE != _standby)
{
STATE = _standby;
OSCCON = osc31khz; // 31kHz LFINTOSC
DisableAudible();
WDTCON = wdt512ms;
}
asm sleep
}
void InstaPLO()
{
if (PREVSTATE != _insta_plo)
{
STATE = _insta_plo;
OSCCON = osc31khz; // 31kHz LFINTOSC
DisableAudible();
}
asm clrwdt // this clrwdt is absolutely necessary or there will be a wdt timeout every time plo is high
WDTCON = wdt8ms; // since plo is high (liquid has been detected during this pass) shorten sleep and check plo more often than during standby mode
asm sleep
WDTCON = wdt128ms; // return wdt period to at least 32ms-- one loop through void main() at clock = 31khz LFINTOSC takes around 10 to 14ms
}
void InstaPHI()
{
if (PREVSTATE != _insta_phi)
{
STATE = _insta_phi;
OSCCON = osc31khz; // 31kHz LFINTOSC
DisableAudible();
}
asm clrwdt // this clrwdt is absolutely necessary or there will be a wdt timeout every time phi is high
WDTCON = wdt8ms; // since phi is high (liquid has been detected during this pass) shorten sleep and check phi more often than during standby mode
asm sleep
WDTCON = wdt128ms; // return wdt period to at least 32ms-- instructions in void main() during 31khz LFINTOSC take around 10 to 14ms
}
void ImmersedPLO()
{
if (PREVSTATE != _immersed_plo)
{
OSCCON = osc500khz; // 500kHz MFINTOSC
STATE = _immersed_plo;
INTCON.TMR0IF = 0;
TMR0 = 0;
WDTCON = wdt128ms;
TMR1H = t1h_fill;
T1CON = 0b110001; // Timer1 clock source is instruction clock (FOSC/4), prescale = 1:8, timer1 oscillator off, timer1 on
EnableAudible();
}
}
void ImmersedPHI()
{
if (PREVSTATE != _immersed_phi)
{
OSCCON = osc500khz; // 500kHz MFINTOSC
STATE = _immersed_phi;
INTCON.TMR0IF = 0;
TMR0 = 0;
WDTCON = wdt128ms;
TMR1H = t1h_fill;
T1CON = 0b10001; // Timer1 clock source is instruction clock (FOSC/4), prescale = 1:2, timer1 oscillator off, timer1 on
EnableAudible();
}
}
void ProbeError()
{
if (PREVSTATE != _probe_error)
{
OSCCON = osc500khz; // 500kHz MFINTOSC
STATE = _probe_error;
INTCON.TMR0IF = 0;
TMR0 = 0;
WDTCON = wdt128ms;
TMR1H = t1h_beep1;
T1CON = 0b100001; // Timer1 clock source is instruction clock (FOSC/4), prescale = 1:4, timer1 oscillator off, timer1 on
EnableAudible();
STATEPERROR = _beep1;
}
}
void Status()
{
if (!PHIlevel)
{
if (!PLOlevel)
{
if (plo) // current pass through comparator has detected liquid at low probe but there is no confirmation of submersion yet (PLOlevel is low), and hi probe not submerged
InstaPLO();
else if (phi)
InstaPHI();
else
Standby();
}
else
ImmersedPLO();
}
else // if (PHIlevel)
{
if (PLOlevel)
ImmersedPHI();
else
ProbeError();
}
PREVSTATE = STATE;
}
void interrupt()
{
if (PIE1.TMR2IE && PIR1.TMR2IF)
{
if (buzz)
buzz = off;
else
buzz = on;
PIR1.TMR2IF = 0;
} // if (PIE1.TMR2IE && PIR1.TMR2IF)
else if (PIE1.TMR1IE && PIR1.TMR1IF)
{
if (STATE == _probe_error)
{
if (++STATEPERROR > _pause2)
STATEPERROR = _beep1;
switch (STATEPERROR)
{
default:
case _beep1:
BuzzOn();
TMR1H = t1h_beep1;
break;
case _pause1:
BuzzOff();
TMR1H = t1h_pause1;
break;
case _beep2:
BuzzOn();
TMR1H = t1h_beep2;
break;
case _pause2:
BuzzOff();
TMR1H = t1h_pause2;
break;
} // switch
} // if (STATE == _probe_error)
else // if (STATE == _immersed_plo || STATE == _immersed_phi)
{
TMR1H = t1h_fill;
if (buzzing)
BuzzOff();
else
BuzzOn();
}
PIR1.TMR1IF = 0;
} // if (PIE1.TMR1IE && PIR1.TMR1IF)
} // void interrupt()
void main()
{
IniReg();
while(1)
{
if (OSCCON != osc31khz) // this conditional is equivalent to "if (STATE == _immersed_plo || STATE == _immersed_phi || STATE == _probe_error)" since these states run at 500kHz
{
while (!INTCON.TMR0IF) ; // do nothing until one timer0 tick has elapsed
INTCON.TMR0IF = 0;
}
asm clrwdt
ReadProbes();
if (STATE == _standby) // during standby there is no need to run the debounce routine. Just record the instantaneous value of the probes
{
PLOval.f0 = plo;
PHIval.f0 = phi;
}
else
Debounce();
Status();
}
}
</pre>
<br />
<b>The build</b> <br />
<br />
Wanted to make the pcb as small as possible and so I used an SOIC
package for the MCU. Unfortunately I don't keep surface mount caps and
resistors (cost is prohibitive for me at the moment) and so I had to use
throughholes. It seemed like a grievous sin not to place the MCU in the
large unused space on the copper side of the board beneath the battery holder and so
that's just what I did even if I had already drawn a previous layout that situates the MCU
partially underneath the terminal block.<br />
<br />
I scoured shops for a plastic box that would house the circuit board and found this near perfect cream-colored plastic box that's in fact a portable USB power supply that uses 2 x AA batteries. I simply removed the small DC-to-DC converter pcb and the metal spring contacts for the batteries. Having found a suitable container I proceeded to finalize the PCB size and layout. The biggest component is the CR2032 battery holder, taking up half the real estate! I intentionally did not include a polarity reversal protection Schottky diode since that immediately slashes 0.3V off the power 3-volt supply. User will just have to be extra careful when changing batteries. Yeah, relying on user competence is a recipe for disaster. Hey, I'm talking about myself too you know. <br />
<br />
<br />
Below is the freshly etched panel which I scored and snapped into two. You will notice the track on the right side of the board is right at the border. The track is a ground trace from the negative terminal of the battery holder to one of the pins of the terminal block as well as to one of the header pins. Had little choice since I didn't have enough board width. Of course I made sure I didn't commit the same booboo that caused a <a href="http://electromotiveforces.blogspot.com/2011/12/devils-in-details.html" target="_blank">short between the power supply rails</a>. I a<span class="hasCaption">dded the triangles just
to keep more copper on the board and to complement the diagonal circuit
tracks. It kinda looks good I think.</span><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwInHuB-HaSVCU3BcSQSiPpKLh8h8mz3VmEoSAeXB6Vme3BL0E7K4eh70Yf8wTT4OAZnPMBino8Q3rHVIPdibNxiggh73vTROh_5kLMM4iIhK3DAn-tbsVxNm4PZcb7wjZPaBoSdNhTqQ/s1600/liquid-level-indicator-boar.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwInHuB-HaSVCU3BcSQSiPpKLh8h8mz3VmEoSAeXB6Vme3BL0E7K4eh70Yf8wTT4OAZnPMBino8Q3rHVIPdibNxiggh73vTROh_5kLMM4iIhK3DAn-tbsVxNm4PZcb7wjZPaBoSdNhTqQ/s320/liquid-level-indicator-boar.png" width="320" /></a></div>
<br />
Even while laying out the board I knew the headers wouldn't have enough leeway on either side. The battery holder on one side and the terminal on the other would prevent the PICkit2/3 programmer from plugging in properly. So I bent the header pin by almost 30 degrees. Simple fix. I wanted to use a right angle header but the pins would end up extending beyond the board perimeter and the board wouldn't fit the plastic case. <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJ9blHEFTDim1PZ-Fuvto3XhDMHnTYhMtvApiBsQ5cQLiwdILeILBNvpu_gkKr-u84eQ8vhOCRKZ9dW3V1EqKCo9oU_yuJNx0D7c-SMZINz6T7OTcxYREUwWY24dKSbERRiIGpwmynoXw/s1600/liquid-level-2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJ9blHEFTDim1PZ-Fuvto3XhDMHnTYhMtvApiBsQ5cQLiwdILeILBNvpu_gkKr-u84eQ8vhOCRKZ9dW3V1EqKCo9oU_yuJNx0D7c-SMZINz6T7OTcxYREUwWY24dKSbERRiIGpwmynoXw/s320/liquid-level-2.png" width="320" /></a></div>
<br />
I had to sand down the top of the (green) terminal block because it was sticking out about a millimeter preventing the plastic cover from sliding in and locking. <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIFJO-o_nXMOxgl-0IAAV4uabMILHqkArvmo7RZUK4rPCQFJt7HHljRukxGAFdZnzcFC8Bn4MtyWRCL0NMbv3csTBXHV8HwCFo6rhYrIJ8oGNRmL2lGapvom5DPM1v-br8FYsn8ziSvW8/s1600/liquid-level-3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIFJO-o_nXMOxgl-0IAAV4uabMILHqkArvmo7RZUK4rPCQFJt7HHljRukxGAFdZnzcFC8Bn4MtyWRCL0NMbv3csTBXHV8HwCFo6rhYrIJ8oGNRmL2lGapvom5DPM1v-br8FYsn8ziSvW8/s320/liquid-level-3.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhmim_VmxL_iZv6MHB8t_wThZmL3-rvbJW5jmH6Wraa-bqjfP5DmdasxQDE6Iqpp_vkWX3YUMSf42LSORNBENyGxNG32_4Un4lvDJXs5Hj5Ej4PJpb4SHVw3zOLyXxVTEL5Uq5XQi_hdI/s1600/liquid-level-4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhmim_VmxL_iZv6MHB8t_wThZmL3-rvbJW5jmH6Wraa-bqjfP5DmdasxQDE6Iqpp_vkWX3YUMSf42LSORNBENyGxNG32_4Un4lvDJXs5Hj5Ej4PJpb4SHVw3zOLyXxVTEL5Uq5XQi_hdI/s320/liquid-level-4.png" width="320" /></a></div>
<br />
For the SOIC pads and those alone I used a <a href="http://uk.rs-online.com/web/p/soldering-consumables/4259379/" target="_blank">Multicore "VOC-free No-clean" flux pen</a>. The usual flux paste I use leaves a messy unsightly residue which is hard to clean off an SMD and could cause a high resistance bridging between the closely spaced pins--something that could contribute to unnecessary battery drain. Nevertheless, after all the soldering was completed, I used isopropanol and acetone to clean the solder joints of all the components including the MCU. <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpYlEIE_bfl7dpHxeqPu6VYBCApI9iTA13xFQzIDyEazKdfKnaxsLNHwTWfBYnm5tkGx1Zk-8KA-lABJypcerI1fZcrromKyD4YCaTn_QH2LKFMSQPCr5hehgjim4vEhg8tFUlucn7gkY/s1600/liquid-level-5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpYlEIE_bfl7dpHxeqPu6VYBCApI9iTA13xFQzIDyEazKdfKnaxsLNHwTWfBYnm5tkGx1Zk-8KA-lABJypcerI1fZcrromKyD4YCaTn_QH2LKFMSQPCr5hehgjim4vEhg8tFUlucn7gkY/s320/liquid-level-5.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvnjm1y_W8L0H1sr0MDtVxnbDA3cT3230BUNxvJInJ19fVIqeUzCyUGDDPIB8tnzboIE_KgPt8MaJ1E3gsYJDG5kIhzBcfC02XZVm1gxNypHsMPeq5XKcoSUk1_QHPovAQMzuvtyqv5qg/s1600/liquid-level-1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvnjm1y_W8L0H1sr0MDtVxnbDA3cT3230BUNxvJInJ19fVIqeUzCyUGDDPIB8tnzboIE_KgPt8MaJ1E3gsYJDG5kIhzBcfC02XZVm1gxNypHsMPeq5XKcoSUk1_QHPovAQMzuvtyqv5qg/s320/liquid-level-1.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhj4qUeTSgWsPSEE4bnm09P7GQgmx8_zGwN4vtthD4VSJ5D6KrNiP4Zi7xThaqTxUO-gaOEwvZM_nVHTdhaNWocVMFaw_6AXwhSSxFfHFwrXGccUc8W-3o31MwLazqLtPvtD3UxKgukmBQ/s1600/liquid-level-6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhj4qUeTSgWsPSEE4bnm09P7GQgmx8_zGwN4vtthD4VSJ5D6KrNiP4Zi7xThaqTxUO-gaOEwvZM_nVHTdhaNWocVMFaw_6AXwhSSxFfHFwrXGccUc8W-3o31MwLazqLtPvtD3UxKgukmBQ/s320/liquid-level-6.png" width="320" /></a></div>
<br />
<span class="hasCaption">The probes (metal wires) are just
nickel-plated paper clips. For now these will have to do since I haven't
found any stainless steel wires yet. </span>I don't have a photo of it, but after screwing down the probes onto the terminal block, I encased the three in hot melt glue to give the set of probes some rigidity. <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZBigUIc4Gx48Bpy0iu5ktSB8IsSiA0nqz17EMwDiGdBTJaaa9fWHexEzRxuFcT-i20ARD81ubxjW4dgXEzNn3acyiwqyJivMBKmhajiNQ_If8R4-B59uyLYDMhHUdr3hfedyAFU8BUD4/s1600/liquid-level-7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZBigUIc4Gx48Bpy0iu5ktSB8IsSiA0nqz17EMwDiGdBTJaaa9fWHexEzRxuFcT-i20ARD81ubxjW4dgXEzNn3acyiwqyJivMBKmhajiNQ_If8R4-B59uyLYDMhHUdr3hfedyAFU8BUD4/s320/liquid-level-7.png" width="320" /></a></div>
<br />
<span class="hasCaption">The liquid level indicator at work. Just hang it over the lip of the cup, mug or tumbler. No switches to flip, no buttons to push.</span><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhP8GwbWvVVDaYrFD5IK_hQgp5A5C7Z4opkkvH3z2KxXmcH9uVXjX4gJZ_W7t_29TrqrB48XcZabGLJobL6JDWgKL1qXYX_0Fh4Td8rraJcQKZ7bilDomp1X0jkfhgOcsi7LqXREQczZ6c/s1600/liquid-level-8.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhP8GwbWvVVDaYrFD5IK_hQgp5A5C7Z4opkkvH3z2KxXmcH9uVXjX4gJZ_W7t_29TrqrB48XcZabGLJobL6JDWgKL1qXYX_0Fh4Td8rraJcQKZ7bilDomp1X0jkfhgOcsi7LqXREQczZ6c/s320/liquid-level-8.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisgfAk4oXfrgLVzDQ4TI_poA10s1kGq4rY7CwGPpIqvSgD54Op17i4ZNHZw5WANl9FzqCq9fynu5vNbxbySqRp59djSdiflts6AUQ8eIzo62OKeTr7QUyWM7GSV_RQv3HekCXidSw1BrY/s1600/liquid-level-9.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisgfAk4oXfrgLVzDQ4TI_poA10s1kGq4rY7CwGPpIqvSgD54Op17i4ZNHZw5WANl9FzqCq9fynu5vNbxbySqRp59djSdiflts6AUQ8eIzo62OKeTr7QUyWM7GSV_RQv3HekCXidSw1BrY/s320/liquid-level-9.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyKtdC_iQyqIkEy64_VkmNKwIw8Dy42-9img9K03k0YMQTxgsJtf9eNZUB4goXEibMvlNvXtD-5AMWgaGHSaa6IYbsVkiB5puPS9c-Lis2vnpUEhROF4EgRcbg1ioEf-M0nvwRV9iWR_o/s1600/liquid-level-10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhyKtdC_iQyqIkEy64_VkmNKwIw8Dy42-9img9K03k0YMQTxgsJtf9eNZUB4goXEibMvlNvXtD-5AMWgaGHSaa6IYbsVkiB5puPS9c-Lis2vnpUEhROF4EgRcbg1ioEf-M0nvwRV9iWR_o/s320/liquid-level-10.png" width="320" /></a></div>
<br />
<br />
Even during the breadboard stage of design the liquid level indicator was showing signs of having some difficulty detecting water. Reason? I thought it might be the nickel plated probes. But ohmmeter readings show their resistance to be in the
milliohm range. And more importantly because the LLI detected coffee, chocolate and juice the instant these liquids reached the tips of the probes, those metal wires can't be the problem.<br />
<br />
Now I'm inclined to believe it may be due to the fact that Microchip explicitly recommends an input signal impedance of less than 10 kilohms when using the analog peripherals such as the voltage comparator. Coffee et al. of course have a relatively low resistance because of the solutes, and that translates to, I reckon, a liquid resistance of less than 3K. Plain potable water has a higher resistance which could be anywhere along the spectrum depending on how pure it is or how much minerals are dissolved.
To make the battery last as long as possible and because of printed circuit board real estate constraints, I intentionally decided not to design in a unity gain buffer to provide the comparator a low impedance signal which I definitely would in a circuit that had little power consumption concerns. Will have to test a breadboarded version of the LLI and see how the inclusion of the buffer will affect water-detection performance.<br />
<br />
If the voltage follower licks the problem then a future version 2.0 of the gizmo will include a dual op amp which gets powered up only when probe readings are being taken. Power consumption is still my major concern given that the circuit is using a single CR2032 3-volt lithium cell which I want to last for months and hopefully--<a href="http://www.venganza.org/" target="_blank">FSM</a> willing--years.<br />
<br />
<br />
Dad has already used the gizmo and he seems to like it. Told him to inform me immediately if the unit starts acting up.erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com1tag:blogger.com,1999:blog-4092193417928690079.post-62794917549246412312012-04-02T01:10:00.040+08:002012-04-03T22:09:18.925+08:00Equation for determining belt size of a three-pulley system<b><i>April 3 Erratum: Thanks to rickets007 I spotted several egregious errors! I had used pulley diameter in some parts when clearly I was intending to use radius. Utter carelessness on my part. I have already corrected the blunders. The equations below are now (hopefully!) free of mistakes. </i></b><br />
<br />
<br />
This is off-topic, but I've been googling and thus far haven't found any page that has the equation for computing the belt circumference given pulley diameters and distance between three pulleys. So I'm posting mine. I've tested my equation against an online <a href="http://www.roadlessgear.com/html/techarticles/beltlength/index.php3">3-pulley calculator</a> and while they don't give exactly the same numbers for some input values, they're close enough (there might be rounding errors in the webpage or my spreadsheet). You will note that the calculator necessitates the user inputting three angles. My equation below has no such requirements. <br />
<br />
When I'm looking for formulas online I frequently just want a plug and play equation without any of the mathematical esoterica and history. So for those who couldn't give a rat's bleep about the derivation here's the formula up front. <br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAm-oJO9P1HKJnVtVl7qGFR0N04FSpp3fUlbYRBUnU3Nu9VuvHceo0jA3Z3zlwSV05KZcSBJCdZYkp4Qx6CdmtNqeNui1nrRh_KBaBx8g8KSepescHZFrYfgPjrCx3KG54iWgUrpW1ar4/s1600/ThreePulleyBeltEquation.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="346" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAm-oJO9P1HKJnVtVl7qGFR0N04FSpp3fUlbYRBUnU3Nu9VuvHceo0jA3Z3zlwSV05KZcSBJCdZYkp4Qx6CdmtNqeNui1nrRh_KBaBx8g8KSepescHZFrYfgPjrCx3KG54iWgUrpW1ar4/s400/ThreePulleyBeltEquation.gif" width="400" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfq696PA4boNM62oLMuSHLryt9_oJxIHBTO6vaPUMFd4cBruelqmpXFBBJb1p-KxXxRmVwDs1VDt0hTNnqKbcBX5nkCv3ybz6FQkjSp0EaSu3F29ABTCc0RhyphenhyphenUCfBd62lWCyIckQy31qU/s1600/ThreePulleyBeltEquationUsingRadius.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="169" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfq696PA4boNM62oLMuSHLryt9_oJxIHBTO6vaPUMFd4cBruelqmpXFBBJb1p-KxXxRmVwDs1VDt0hTNnqKbcBX5nkCv3ybz6FQkjSp0EaSu3F29ABTCc0RhyphenhyphenUCfBd62lWCyIckQy31qU/s400/ThreePulleyBeltEquationUsingRadius.gif" width="400" /></a></div><br />
For my sake (so I don't have to do it all over again!) and for skeptics who want to make sure I didn't make any booboos here's the derivation.<br />
<br />
In Fig.1 we have three circles representing three pulleys. Pulley A has its center marked as point A. Pulley B has its center marked as point B. Pulley C has its center marked as point C. <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKp7pfZVJo8kGfIpPx0he4ufTSrjZVytzZL6xP621czGUDzhfLnATDahVx0oBFSdf8tDd8scWgYrzJlKzkrDkvUWDK7NhlNZOxxIsXqndi2oUFMo1Hm0RmX0iVomrsBtxBRFLM9xAl1I4/s1600/threepulley1.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="290" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKp7pfZVJo8kGfIpPx0he4ufTSrjZVytzZL6xP621czGUDzhfLnATDahVx0oBFSdf8tDd8scWgYrzJlKzkrDkvUWDK7NhlNZOxxIsXqndi2oUFMo1Hm0RmX0iVomrsBtxBRFLM9xAl1I4/s320/threepulley1.gif" width="320" /></a></div><br />
We are given the diameters of each of the three pulleys and the distance between their centers. We need to find the belt length. <br />
<br />
Let <br />
D<sub>A</sub> = diameter of pulley A<br />
D<sub>B</sub> = diameter of pulley B<br />
D<sub>C</sub> = diameter of pulley C<br />
<br />
Let<br />
a = distance between centers of pulleys B and C = segment BC<br />
b = distance between centers of pulleys C and A = segment CA<br />
c = distance between centers of pulleys A and B = segment AB<br />
<br />
L = belt length = segments HJ + DE + FG + arc lengths FE + GH + DJ<br />
<br />
Let<br />
a' = segment HJ<br />
b' = segment DE<br />
c' = segment FG<br />
<br />
Let<br />
α = ∠CAB<br />
β = ∠ABC<br />
γ = ∠BCA<br />
<br />
Let<br />
α' = ∠FAE<br />
β' = ∠GBH<br />
γ' = ∠DCJ<br />
<br />
Let<br />
l<sub>α'</sub> = arc length of the belt looping around pulley A = arc FE<br />
l<sub>β'</sub> = arc length of the belt looping around pulley B = arc GH<br />
l<sub>γ'</sub> = arc length of the belt looping around pulley C = arc DJ<br />
<br />
Note: All angles and arcs are less than π radians (180 degrees). <br />
<br />
Rewriting the equation for belt length we have:<br />
<br />
L = a' + b' +c' + l<sub>α' + </sub>l<sub>β'</sub> + l<sub>γ'</sub> <br />
<br />
<br />
In order to find the arc lengths we need to first determine the angles α', β', and γ'. To achieve this we shall, for each pulley, find the values of all the other angles in the pulley and then subtract them from 2π radians (360 degrees). <br />
<br />
For triangle ABC we are given the values of all the sides (a, b, c). Therefore, we can determine all three angles of the triangle using cosine law:<br />
<br />
α = arccos[(b<sup>2</sup>+c<sup>2</sup>-a<sup>2</sup>)/(2bc)]<br />
β = arccos[(c<sup>2</sup>+a<sup>2</sup>-b<sup>2</sup>)/(2ca)]<br />
γ = arccos[(a<sup>2</sup>+b<sup>2</sup>-c<sup>2</sup>)/(2ab)]<br />
<br />
<br />
As we know if a line is tangent to a circle then a radius of the circle drawn to the point of contact of the tangent line with the circle will be perpendicular to the tangent line. Thus, ∠AFG, ∠BGF, ∠BHJ, ∠CJH, ∠CDE, ∠AED are all right angles.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgx6t3yeiXQ-EeXALYDa38Ew2oEmXEUhonBTjIsUazEdsaC8nR__wTC2d0fCgbi0ETo2vwg37qHIB4JSyHC-cQMcbfdNE8tXQuZ4EQgZkDguLXheQYGPlocvh43g_6y1NgNjhJUs1f4o6Y/s1600/threepulley2.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="279" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgx6t3yeiXQ-EeXALYDa38Ew2oEmXEUhonBTjIsUazEdsaC8nR__wTC2d0fCgbi0ETo2vwg37qHIB4JSyHC-cQMcbfdNE8tXQuZ4EQgZkDguLXheQYGPlocvh43g_6y1NgNjhJUs1f4o6Y/s320/threepulley2.gif" width="320" /></a></div><br />
In Fig.2 we have segment BK drawn parallel to FG. Since ∠AFG is a right angle, therefore, ∠AKB is also a right angle. It follows that triangle AKB is a right triangle. Because BK is parallel to FG and FK is parallel to GB, segment AK = difference in the radii of pulleys A and B = D<sub>A</sub>/2 - D<sub>B</sub>/2.<br />
<br />
Likewise, we draw segment CL parallel to DE and obtain right triangle ALC. LE = CD and so AL = D<sub>A</sub>/2 - D<sub>C</sub>/2. <br />
<br />
Our objective is to find the value of ∠KAB and ∠LAC. Because we know the values of the hypotenuse and the adjacent side of both right triangles we can use the cosine function for right triangles:<br />
<br />
∠KAB = arccos[(D<sub>A</sub>/2-D<sub>B</sub>/2)/c]<br />
∠LAC = arccos[(D<sub>A</sub>/2-D<sub>C</sub>/2)/b]<br />
<br />
We now have all the angles to compute for α'.<br />
<br />
α' = 2π - arccos[(D<sub>A</sub>/2-D<sub>B</sub>/2)/c] - arccos[(D<sub>A</sub>/2-D<sub>C</sub>/2)/b] - α<br />
<br />
We use the same method above for pulleys B and C to obtain:<br />
<br />
β' = 2π - arccos[(D<sub>B</sub>/2-D<sub>C</sub>/2)/a] - arccos[(D<sub>B</sub>/2-D<sub>A</sub>/2)/c] - β <br />
γ' = 2π - arccos[(D<sub>C</sub>/2-D<sub>A</sub>/2)/b] - arccos[(D<sub>C</sub>/2-D<sub>B</sub>/2)/a] - γ <br />
<br />
Note that the fact that (D<sub>C</sub>/2 - D<sub>A</sub>/2) is negative is not an error. In fact it provides the correct answer--an obtuse angle which is the ∠ACD in this case. We don't need to know which pulleys are bigger and which are smaller. The equations will always give us the correct values.<br />
<br />
We now turn to finding the arc lengths. The arc length of the belt looping around a pulley is the angle subtended by the belt divided by the angle measure of an entire circle (2π) multiplied by the circumference of the pulley:<br />
<br />
l<sub>α'</sub> = (α'/2π)(πD<sub>A</sub>) = α'D<sub>A</sub>/2<br />
l<sub>β'</sub> = (β'/2π)(πD<sub>B</sub>) = β'D<sub>B</sub>/2<br />
l<sub>γ'</sub> = (γ'/2π)(πD<sub>C</sub>) = γ'D<sub>C</sub>/2<br />
<br />
Let's return to triangle AKB. Notice that segment FG and KB are not only parallel but also equal in length (because quadrilateral BKFG is a rectangle). Above we let c' = segment FG. It follows that c' is also = segment KB. We can get the value of KB using the sine or tangent function, but to minimize trigonometric functions we will apply the Pythagorean theorem, with c (segment AB) as the hypotenuse:<br />
<br />
c' = √[c<sup>2</sup>-(D<sub>A</sub>/2-D<sub>B</sub>/2)<sup>2</sup>]<br />
<br />
The same applies to the other two segments of the belt.<br />
<br />
a' = √[a<sup>2</sup>-(D<sub>B</sub>/2-D<sub>C</sub>/2)<sup>2</sup>]<br />
b' = √[b<sup>2</sup>-(D<sub>C</sub>/2-D<sub>A</sub>/2)<sup>2</sup>]<br />
<br />
And at last we have all the values needed to compute for belt length.erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com15tag:blogger.com,1999:blog-4092193417928690079.post-83017086355228286652012-03-12T22:50:00.008+08:002012-03-14T01:18:56.764+08:00I2C dual voltage mystery solvedRemember that <a href="http://electromotiveforces.blogspot.com/2012/03/quirks-on-dual-voltage-i2c-bus-using-5v.html">strange phenomenon on the dual voltage I<sup>2</sup>C bus</a> where I was getting 3.4V on the 2.8V bus and 4V on the 5V bus? Well, I finally found the culprit. Turns out that I had, with breathtaking unparalleled stupidity, plugged the two TO-92 2N7000 MOSFETs the wrong way around! The drain and source pins got reversed! So all the while when the bus was idle, current was flowing from the 5V rail to the 2.8V I<sup>2</sup>C bus (due to the inherent diode in MOSFETs). Almost certainly protection diodes on the MMA7660FC accelerometer chip were conducting, thus giving that extra 0.6 to 0.7V excess voltage reading. Because the chip is still ticking the current flowing through it must've been within limits. Had I decreased the pull-up resistors to below 1kohm, it (and therefore I) may not have been so lucky.<br />
<br />
Interestingly, as we've seen, I<sup>2</sup>C communication works even with this reversed set up--with the lower voltage on the MOSFET drain side and the higher voltage bus on the source side. Let's analyze the circuit to see how and why it works. (We'll consider only one line since the explanation applies to both SDA and SCL.) <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_XtdbgI8TLxb-3EQD4p9lnZxqPRU-Mw0wBZnPhd0iNcjifwa95fyZdR1RgrYxJfhGOwAHm7nNHf1NbHxVsqm4FAhXpkrw8vmnwgJtZ7KQw_Sh_n2EutGfNpNN7KAhw4LV8F_udgP5I6c/s1600/acceler-reverse-mosfet-sche.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="197" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_XtdbgI8TLxb-3EQD4p9lnZxqPRU-Mw0wBZnPhd0iNcjifwa95fyZdR1RgrYxJfhGOwAHm7nNHf1NbHxVsqm4FAhXpkrw8vmnwgJtZ7KQw_Sh_n2EutGfNpNN7KAhw4LV8F_udgP5I6c/s320/acceler-reverse-mosfet-sche.png" width="320" /></a></div><br />
A. When neither the 7660 nor the MCU is pulling the line low--i.e., Q1 and Q2 are off--then MOSFET Q0 is off--because its source pin is at a higher voltage than the gate. Points V1 and V2 ought to be pulled up to VDD1 and VDD2, respectively. But due to Q0's internal diode--with the anode at VDD2 (5-volt) side--current flows from VDD2 toward VDD1 side. Since VDD2 > (VDD1 + diode voltage drop), the accelerometer's protection diode D1 conducts and so at point V1 we get a voltage less than VDD2 but greater than VDD1. We expect V1 = VDD1 + V<sub>F<sub>D1</sub></sub>, where V<sub>F<sub>D1</sub></sub> = D1's forward voltage. <br />
<br />
Because of Q0's diode we expect the voltage at point V2 = V1 + V<sub>F<sub>D0</sub></sub>, where V<sub>F<sub>D0</sub></sub> = forward voltage of Q0's diode. End result is that both V1 and V2 are at logic high. Note that V2 may not reach a logic high if the voltage difference between VDD1 and VDD2 is large. For instance if VDD1 = 2.8V and VDD2 = 15V then, assuming the accelerometer hasn't been fried, the voltage at V2 would still be around 4V which may not be sufficient to meet the minimum voltage required for a logic high. <br />
<br />
B. When Q2 turns on V2 is pulled to ground. Q0 source is likewise pulled to ground. Because the voltage difference between Q0's gate and source exceeds the gate threshold voltage V<sub>GS<sub>(th)</sub></sub>, Q0 switches on causing V1 to be pulled to nearly to ground as well. So both V1 and V2 are at logic low.<br />
<br />
C. When Q1 turns on, V1 is pulled to ground. Because of Q0's internal diode V2 gets pulled down close to ground as well. (V2 will be approximately 0.7V, with the exact value depending on the voltage across Q1's drain and source). V1 and V2 are at logic low. [<i>March 13 edit: V2 will actually be very close to ground because as in B above the voltage difference between Q0's gate and source exceeds the gate threshold voltage V<sub>GS<sub>(th)</sub></sub> and thus Q0 turns on. With less than a couple of milliamps of current Q0 drain-source voltage is in the order of millivolts.</i>]<br />
<br />
<br />
I want to post the screenshots of the oscilloscope readings of the dual voltage I<sup>2</sup>C with the error corrected, but I still don't have my USB flash drive so I'll put the images up when I finally get it back.erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com0tag:blogger.com,1999:blog-4092193417928690079.post-81919146011751782452012-03-12T01:51:00.009+08:002012-03-14T01:22:19.178+08:00Quirks on dual voltage I2C bus / Using a 5V LCD with an MCU at less than 5V[<i>Update: I know what's causing the overvoltage condition on the I<sup>2</sup>C. Find out over <a href="http://electromotiveforces.blogspot.com/2012/03/i2c-dual-voltage-mystery-solved.html">here</a>.</i>] <br />
<br />
<a href="http://electromotiveforces.blogspot.com/2012/03/mma7660fc-accelerometer-on-dual-voltage.html">Previously</a> I talked about how a MMA7660FC accelerometer was supplied with 2.8V while the microcontroller was run at 5.2V. Because the two chips communicate via I<sup>2</sup>C a voltage level shifter using MOSFETs was employed. To make sure the voltage levels on the bus were within specs I took some oscilloscope readings .Unfortunately, unlike the scope readings in the <a href="http://ics.nxp.com/support/documents/interface/pdf/an97055.pdf">AN97055 Bi-directional level shifter for I²C-bus and other systems</a>, mine show that the 2.8V side of the I<sup>2</sup>C is at around 3.4V while the 5V bus is at 4.0V. This is strange and I have yet to find an explanation for this. Intriguingly the voltage difference between 2.8V and 3.4V is the forward voltage of a diode. <br />
<br />
Here are screenshots of the readings. I've also taken shots of the rise/fall times. <br />
<br />
A. 2.8-volt I²C<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUah_RZWTi0WstUgg1KJR1k2lN4tauSkXhTSe7HS-RaAJsAD91NSyjlWnYBALYatKTOLUmRmZMZz7NmZ9s69I9IA6ORq7NpEG0gpDhpVe2_IquUwQ8fZd4y8JLE1L8OWP7vJcI9sk-9CE/s1600/I2C+2.8V+SCL.bmp" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="234" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUah_RZWTi0WstUgg1KJR1k2lN4tauSkXhTSe7HS-RaAJsAD91NSyjlWnYBALYatKTOLUmRmZMZz7NmZ9s69I9IA6ORq7NpEG0gpDhpVe2_IquUwQ8fZd4y8JLE1L8OWP7vJcI9sk-9CE/s320/I2C+2.8V+SCL.bmp" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQwBUggS3br7jBtDxAjRNoNoqWukOk-CAsgS2kPBf77bnaeDOlcSeQiV4UzsQTDpVA0mIZpeGsUFATPvBRQUSVml2nrFU-GPDjCAeBq39dw-0vZ3Y7vE6CRE5jItoQrm7HYn8SwJ6Odns/s1600/I2C+2.8V+SCL+zoom.bmp" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="234" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQwBUggS3br7jBtDxAjRNoNoqWukOk-CAsgS2kPBf77bnaeDOlcSeQiV4UzsQTDpVA0mIZpeGsUFATPvBRQUSVml2nrFU-GPDjCAeBq39dw-0vZ3Y7vE6CRE5jItoQrm7HYn8SwJ6Odns/s320/I2C+2.8V+SCL+zoom.bmp" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDn69JV3nmAsVg22HlO3x6_IUU5xGWAvbSaqOzQPj9pLtTFScpqpocqI5yaHAWCjqytVNj8jX-dN0mRACAlqeXNYSROErUAcm1VaSGdBJsgHjSocNX3Eef1IiN1mHdNiYAvrIdPE_IV8s/s1600/I2C+2.8V+rise-fall+time.bmp" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="234" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDn69JV3nmAsVg22HlO3x6_IUU5xGWAvbSaqOzQPj9pLtTFScpqpocqI5yaHAWCjqytVNj8jX-dN0mRACAlqeXNYSROErUAcm1VaSGdBJsgHjSocNX3Eef1IiN1mHdNiYAvrIdPE_IV8s/s320/I2C+2.8V+rise-fall+time.bmp" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLOi2qVgx0jLugqH1rajd7RdJyPny7UGDEO3PLGzBYOHaQHj_onXXYntqS4KmRZWa7qKtCfcntjbWyEYDA1wcFduugRbxLUqS7VO6rFAfG5Nqun36vcBkMCeXDhTvZkUBmzs6uSxb1zJs/s1600/I2C+2.8V+rise+time.bmp" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="234" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLOi2qVgx0jLugqH1rajd7RdJyPny7UGDEO3PLGzBYOHaQHj_onXXYntqS4KmRZWa7qKtCfcntjbWyEYDA1wcFduugRbxLUqS7VO6rFAfG5Nqun36vcBkMCeXDhTvZkUBmzs6uSxb1zJs/s320/I2C+2.8V+rise+time.bmp" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEWmIiWGoJBccW4uk_ZGYn9NHq_btmbzxVvP3HWeSNp8QzJ3HDXw9S1wUNYhW7Pye7BSwLYd_FepOAH0_ZfFacwhqzntLgEZN1E2OvHw6CG6qQ1p2qIyksfTpanBIVFcPYvvH1vDpWB3Y/s1600/I2C+2.8V+fall+time.bmp" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="234" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEWmIiWGoJBccW4uk_ZGYn9NHq_btmbzxVvP3HWeSNp8QzJ3HDXw9S1wUNYhW7Pye7BSwLYd_FepOAH0_ZfFacwhqzntLgEZN1E2OvHw6CG6qQ1p2qIyksfTpanBIVFcPYvvH1vDpWB3Y/s320/I2C+2.8V+fall+time.bmp" width="320" /></a></div><br />
B. 5-volt I²C.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcIoxee7vSr3vVGjxVrj5ecpgJrwz_Yxw1lfiduEXxmqgI1J5rWYJucuMsoZl_TUbW2sX6zIj1B5lMtgq9w3zOdVa0m_wvwlxpiNdJllwqAmr-h6sdvgVuWrkdeU9RcWsokjy12vzZYco/s1600/I2C+5V+SCL.bmp" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="234" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcIoxee7vSr3vVGjxVrj5ecpgJrwz_Yxw1lfiduEXxmqgI1J5rWYJucuMsoZl_TUbW2sX6zIj1B5lMtgq9w3zOdVa0m_wvwlxpiNdJllwqAmr-h6sdvgVuWrkdeU9RcWsokjy12vzZYco/s320/I2C+5V+SCL.bmp" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpTExDFsv_664kZIwBl4777GgIN0FYswWEItqvPQueW08MhNcKNCp0B-mgdAsDEOBzVlhy7e-OgYbSY4UiKwHqqOrEn4LhRaoQNUc5WkhYOe3klSXn36DFlDYFbKKmKgAspLKKXqFAtXQ/s1600/I2C+5V+SCL+zoom.bmp" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="234" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpTExDFsv_664kZIwBl4777GgIN0FYswWEItqvPQueW08MhNcKNCp0B-mgdAsDEOBzVlhy7e-OgYbSY4UiKwHqqOrEn4LhRaoQNUc5WkhYOe3klSXn36DFlDYFbKKmKgAspLKKXqFAtXQ/s320/I2C+5V+SCL+zoom.bmp" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8zLOzYVUWLl-zmdgu45_0PSq7I1yYRwXCv41sHUuxEA6WOOvmexgWJBo26ys_Ed3pwznjIw_SfnnkGJZphpc2703hqPXkMz0-xn3H4UfMc1pOta3w-ZtirI2PW0-mCU1drmlusHBMGno/s1600/I2C+5V+rise-fall+time.bmp" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="234" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh8zLOzYVUWLl-zmdgu45_0PSq7I1yYRwXCv41sHUuxEA6WOOvmexgWJBo26ys_Ed3pwznjIw_SfnnkGJZphpc2703hqPXkMz0-xn3H4UfMc1pOta3w-ZtirI2PW0-mCU1drmlusHBMGno/s320/I2C+5V+rise-fall+time.bmp" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlMKBeu89his5bKjndOXzbKRkDoAbL6JdCLLEN0cqaf_ocyn3XkCjNNTdM1CH6NyNVDKPDWE0mbcAB_GYSsWMY4bsdF4yLe2Dz5XqKRyztod8GF1rIU-fHtCfPRENezUs_Z04Gzwt4i1s/s1600/I2C+5V+rise+time.bmp" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="234" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlMKBeu89his5bKjndOXzbKRkDoAbL6JdCLLEN0cqaf_ocyn3XkCjNNTdM1CH6NyNVDKPDWE0mbcAB_GYSsWMY4bsdF4yLe2Dz5XqKRyztod8GF1rIU-fHtCfPRENezUs_Z04Gzwt4i1s/s320/I2C+5V+rise+time.bmp" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4aPXsHAQQozrv4CBF4HPyBLt-YSuBDW1HGlXJJbnck2MCcjh_adPfYPhL8HDpkq8UzF_xjQggMoll62Hrn9aVz9WDamBZWq4wBp3rXWaliktlvneqka5dpo317r9ttyrT22_MrUDxMgc/s1600/I2C+5V+fall+time.bmp" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="234" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4aPXsHAQQozrv4CBF4HPyBLt-YSuBDW1HGlXJJbnck2MCcjh_adPfYPhL8HDpkq8UzF_xjQggMoll62Hrn9aVz9WDamBZWq4wBp3rXWaliktlvneqka5dpo317r9ttyrT22_MrUDxMgc/s320/I2C+5V+fall+time.bmp" width="320" /></a></div><br />
Pull resistors are 4.7K on both the 2.8V and 5V bus. I also tested 10K pull ups. As expected rise time increased.<br />
<br />
As stated in the previous blog entry the reason I ran the MCU at 5V is that I'm using a 5V LCD. It turns out, however, that the LCD is fine with signals way below its minimum required V<sub>DD</sub> of 4.5V. According to the <a href="http://www.displaytech.com.hk/upload/product/attachment/5284-162F.pdf">Displaytech 162F datasheet</a> the minimum voltage for a logic high (V<sub>IH</sub>) is 2.2V. So running the MCU at 2.8V and feeding the LCD a signal of 2.5-2.8V would be acceptable. Thus, if there are no other hardware on the circuit which requires the MCU to run at a higher voltage than 2.8-3.0V then the following simpler circuit (than the one with MOSFETs and additional pull ups) can be used. <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNRrLv4HOWM2tSWaafzf8_Mtoxu7MEB5uOrRPjzuXwwfLK1aN3NZTrowLUdYXeVv50vBQhbBx0oBeYDhDM2ND-tHDXefi80OA23ePEtktN5DXZUgz2WPBmv2jM35QTCf8Ly9Lm7WO57L8/s1600/acceler-mcu2.8v-schematic.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="197" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNRrLv4HOWM2tSWaafzf8_Mtoxu7MEB5uOrRPjzuXwwfLK1aN3NZTrowLUdYXeVv50vBQhbBx0oBeYDhDM2ND-tHDXefi80OA23ePEtktN5DXZUgz2WPBmv2jM35QTCf8Ly9Lm7WO57L8/s320/acceler-mcu2.8v-schematic.png" width="320" /></a></div><br />
Notice that the LCD's RW pin is connected to ground. RW is kept at logic low when writing to the LCD or issuing commands to it. It is only brought high when reading from the LCD. The most important use of the read operation is to check whether the LCD is busy--we cannot write or issue a command while it's still processing. To read the LCD, the MCU pins connected to DB4 to DB7 must first be tristated--the pins configured as inputs. We then poll DB7 until it goes low--signifying the LCD is ready. However, in lieu of reading the LCD, we can just wait a few milliseconds so the LCD can finish whatever it's doing. This is the method the mikroC compiler employs. It does not tristate the PIC pins. It just issues a delay of 5.5ms. Apparently this is the maximum amount of time generic LCDs need. So with RW tied to ground, this is the only method of ensuring we don't write to the LCD while it's still busy. <br />
<br />
Besides freeing up one MCU pin, using this delay method ensures no overvoltage condition will exist on the MCU pins. Remember that in our circuit MCU V<sub>DD</sub> < LCD V<sub>DD</sub>. When the MCU pins are tristated and the LCD is read from the signals from the LCD are close to LCD V<sub>DD</sub> of 5V. As per datasheet PIC pins are not supposed to have voltages > MCU V<sub>DD</sub>.<br />
<br />
So what happens if we have signal voltages on the PIC pins greater than its V<sub>DD</sub>? Well the protection diodes start conducting. The following schematic is from the PIC16F1828 datasheet and clearly shows the diodes on the I/O pins. <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgb5faH_vHm-XswIqGHj5Nuk9ueP-nJMV14DdFWHNKKRRd89DpF-ZDyDcGc4X3D62MEyH5ukvY2bryy-jBy9_PdlJ5RQDQjfzHZgIntHZvUDGLae1dZF53Fbh493r0WFNLEXN2WDJvfBDc/s1600/16F1828+port+schematic.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="296" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgb5faH_vHm-XswIqGHj5Nuk9ueP-nJMV14DdFWHNKKRRd89DpF-ZDyDcGc4X3D62MEyH5ukvY2bryy-jBy9_PdlJ5RQDQjfzHZgIntHZvUDGLae1dZF53Fbh493r0WFNLEXN2WDJvfBDc/s320/16F1828+port+schematic.png" width="320" /></a></div><br />
Actually the voltage on the MCU pins can exceed MCU V<sub>DD</sub>. But the current through the diodes has to be limited so as not to fry the chip. In fact even mains voltages of 110 or 220V can be applied to the MCU so long as a resistor of at least 1Mohm is placed in series to severely limit the current flowing into the chip.<br />
<br />
To test how the MCU will stand up to the 5V signal voltages from the LCD--without any series current-limiting resistor--I had the MCU use the polling method for determining the busy status of the LCD. The RW pin is connected to the MCU of course. Firmware is as follows.<br />
<br />
<pre>#define en PORTA.f2
#define rs PORTC.f1
#define rw PORTC.f0
#define db7 PORTC.f4
#define db6 PORTC.f3
#define db5 PORTC.f6
#define db4 PORTC.f7
#define trisdb7 TRISC.f4
#define trisdb6 TRISC.f3
#define trisdb5 TRISC.f6
#define trisdb4 TRISC.f7
#define clearlcd 0x1
#define int1 bit
#define int8 unsigned char
#define int16 unsigned int
#define int32 unsigned long
#define on 1
#define off 0
#define hi 1
#define lo 0
#define input 1 // for TRISx
#define output 0 // for TRISx
#define analog 1 // for ANSELx
#define digital 0 // for ANSELx
//************************************************************************************************
// LCD subroutines
//************************************************************************************************
void DelayMillisec(int8 ms)
{
int8 i;
for (i=0; i<=ms; i++)
Delay_ms(1);
}
void PulseEN()
{
en = hi;
asm {nop} // one cycle delay
en = lo;
}
void CheckLCDbusy()
{
bit lcdbusy;
en = lo;
rs = lo;
rw = hi;
// change data bits to input
trisdb7 = input;
trisdb6 = input;
trisdb5 = input;
trisdb4 = input;
lcdbusy = 1;
while(lcdbusy)
{
en = hi;
if (!db7)
lcdbusy = 0;
en = lo;
PulseEN(); // dummy read lower nibble; this is necessary since both nibbles must be read
}
// change data bits back to output
trisdb7 = output;
trisdb6 = output;
trisdb5 = output;
trisdb4 = output;
rw = lo;
}
void SendNibbles(int8 text)
{
db7 = text.f7;
db6 = text.f6;
db5 = text.f5;
db4 = text.f4;
PulseEN();
db7 = text.f3;
db6 = text.f2;
db5 = text.f1;
db4 = text.f0;
PulseEN();
}
void WriteTextLCD(int8 text)
{
CheckLCDbusy();
en = lo;
rs = hi;
SendNibbles(text);
}
void SendCommandLCD(int8 text)
{
CheckLCDbusy();
en = lo;
rs = lo;
SendNibbles(text);
}
void IniLCD()
{
DelayMillisec(15);
en = 0;
rs = 0;
rw = 0;
db7 = 0;
db6 = 0;
db5 = 1;
db4 = 1;
PulseEN();
Delay_ms(5);
PulseEN();
Delay_us(160);
PulseEN();
Delay_us(160);
db4 = 0;
PulseEN();
// lcd now in 4-bit operation mode
// the SendCommandLCD() function can now be used to send all other initialization instructions
SendCommandLCD(0b101000); // Function Set: 4-bit data bus, 2-line display, 5x7 dots
SendCommandLCD(0b1000); // Display: display off
SendCommandLCD(clearlcd); // clear lcd screen-- clears DDRAM
SendCommandLCD(0b110); // Entry Mode: increment, display shift off
SendCommandLCD(0b1100); // Display: display on, cursor off, cursor blink off
asm {clrwdt}
}
//************************************************************************************************
void IniReg()
{
// set internal clock frequency to 4MHz
OSCCON = 0b1101000;
TRISA = output;
TRISB = output;
TRISC = output;
ANSELA = digital;
ANSELB = digital;
ANSELC = digital;
PORTA = 0;
PORTB = 0;
PORTC = 0;
} // void IniReg()
void main()
{
IniReg();
IniLCD();
while(1)
{
WriteTextLCD('T');
WriteTextLCD('e');
WriteTextLCD('s');
WriteTextLCD('t');
WriteTextLCD(' ');
WriteTextLCD('P');
WriteTextLCD('I');
WriteTextLCD('C');
WriteTextLCD(' ');
WriteTextLCD('p');
WriteTextLCD('i');
WriteTextLCD('n');
WriteTextLCD('s');
DelayMillisec(200);
SendCommandLCD(clearlcd);
}
} // void main()
</pre><br />
With the circuit powered up I probed DB4 to DB7 using a Fluke 87V with its PEAK MIN MAX voltage detect enabled. As expected the voltage was significantly above its V<sub>DD</sub> of 2.81V. I recorded a maximum of 3.53V. The difference between V<sub>DD</sub> and the peak voltage is the voltage drop across the protection diode which is conducting due to the excess voltage on the I/O pin. I didn't bother measuring the current. I just left the circuit running for several minutes and from the looks of it, the MCU can take it. <br />
<br />
I've also used the oscilloscope to measure the voltages and the waveforms are rather intriguing. Unfortunately I don't have my USB flash drive right now. I'll post screenshots when I get it back.erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com0tag:blogger.com,1999:blog-4092193417928690079.post-58481654319799864822012-03-11T22:02:00.000+08:002012-03-11T22:02:12.770+08:00MMA7660FC Accelerometer on dual voltage I2C busI recently got a Freescale <a href="http://www.freescale.com/webapp/sps/site/prod_summary.jsp?code=MMA7660FC">MMA7660FC</a> accelerometer already soldered on a breakout board. It's made by made by <a href="http://store.tautic.com/">Tautic</a> and comes complete with decoupling capacitors. I soldered a header to the board and plugged in a 5-pin connector. The connector wires plug into the breadboard. This way I can freely rotate the accelerometer.<br />
<br />
To make sure the communications between the microcontroller and the accelerometer was going well and to view the values of X, Y, Z axes, I initially relied on the <a href="http://www.saleae.com/Logic">Saleae Logic</a>. After the bugs had been ferreted out I wired in an LCD so I can get a real time feedback as I rotated the board. <br />
<br />
Note that Tautic has opted to tie the analog (AVDD) and digital (DVDD) supply together. This means the chip cannot be set to OFF MODE wherein only the digital part of the circuit is powered. According to the <a href="http://cache.freescale.com/files/sensors/doc/data_sheet/MMA7660FC.pdf">datasheet</a> in this mode the "MMA7660FC will not respond, but I2C bus can be used for other devices (MMA7660FC does not load I2C bus)." This mode has the least power consumption and would be useful in handheld devices which need to conserve as much power as possible.<br />
<br />
While I'm quoting the datasheet, a caveat is in order. To have absolute faith in the 7660 datasheet<a href="http://cache.freescale.com/files/sensors/doc/data_sheet/MMA7660FC.pdf"></a> is to court misery--as I learned. Even as it's already in its 7th revision it still has at least two egregious errors which caused me confusion, not to mention wasted time in getting this chip up and running. <br />
<br />
The first brick wall I crashed into: "During the Active Mode, continuous measurement on all three axes is enabled. In addition, the user can choose to enable: Shake Detection, Tap Detection, Orientation Detection, and/or Auto-Wake/Sleep Feature..." Well I tried to change a few registers in Active Mode and it wouldn't let me. So I did it in Standby Mode. I'm not sure if none of the registers--save for MODE register 0x07--can be edited in Active Mode. <br />
<br />
Second Freescale booboo: "In order to enable Tap detection in the device the user must enable the Tap Interrupt in the INTSU (0x06) register...." No! I disabled all interrupts and tap detection still works. Of course there won't be any interrupts sent via INT. But the tap detection bit in TILT register 0x03 still gets set when the chip is tapped. <br />
<br />
Can Freescale engineers please get their documentation right? This isn't a 300-page doc. It shouldn't be that hard to finally--after so many revisions--get rid of all the errors. <br />
<br />
The MMA7660 has a maximum V<sub>DD</sub> of 3.6V and a recommended V<sub>DD</sub> of 2.8V. Maximum voltage on any other pin should not exceed V<sub>DD</sub>. Since it communicates with the microcontroller via I<sup>2</sup>C the MCU must either run at the same voltage or if the MCU runs at a higher voltage then voltage level shifting hardware has to be present on the I<sup>2</sup>C bus.<br />
<br />
The microcontroller I chose is the 20-pin <a href="http://www.microchip.com/wwwproducts/Devices.aspx?dDocName=en546918">PIC16F1828</a>. Because I'm using a 5V <a href="http://www.displaytech.com.hk/product.php?id=11">Displaytech 162F LCD</a> I wanted the MCU to run at 5V. This meant I had to find a way to have a dual voltage I<sup>2</sup>C bus. Fortunately, the engineers at Philips devised a very simple way to realize this years ago. Check out the following application notes:<br />
<br />
<a href="http://ics.nxp.com/support/documents/interface/pdf/an97055.pdf">AN97055 Bi-directional level shifter for I²C-bus and other systems</a> <br />
<a href="http://www.nxp.com/documents/application_note/AN10441.pdf">AN10441 Level shifting techniques in I2C-bus design</a><br />
<br />
We need only connect two MOSFETS to the bus and we automatically have a bidirectional voltage translator, with electrical isolation between the two voltage levels. In the breadboarded circuit I had an LM317 voltage regulator supply 2.8V to the 7660 while the MCU and LCD were run at 5.2V. The MOSFETs were 2N7000.<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisQrJ7kiRoqtrIDpA_tpG9HAo4JRxLmhXeTLjgI5-H0s_eGgkScfpfIGSY5QAoNnDAArPjKU68FK3PqIrbasoVBYjw23wstFl4qJdXN6iL_00gdY9yH7BozR8WQtiIihMVpo41I0lix4s/s1600/acceler-schematic.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="197" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisQrJ7kiRoqtrIDpA_tpG9HAo4JRxLmhXeTLjgI5-H0s_eGgkScfpfIGSY5QAoNnDAArPjKU68FK3PqIrbasoVBYjw23wstFl4qJdXN6iL_00gdY9yH7BozR8WQtiIihMVpo41I0lix4s/s320/acceler-schematic.png" width="320" /></a></div>The comparator isolates the INT pin from the MCU. It also provides either 0V or 5V signal to the MCU. Note that the accelerometer's INT pin is configurable either as open drain or push-pull. In this set up it should be set as push-pull. In the test circuit I didn't use a comparator. Instead I employed a simple diode to isolate and protect the accelerometer. The pull down resistor prevents the MCU input pin from floating when INT is low. The voltage output at the INT pin is actually above the minimum of 2.0V required by the TTL input of the MCU. Note that given an INT max output of 2.8V this diode method will not work with Schmitt trigger inputs on the microcontroller since they require a logic high to be at least 80% of V<sub>DD</sub>. <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiew8lH63xvyzEouClA4pdxV-UB3MHorZh1FHwG71xrV17QXBgNWcNyMFL9RqDOKO_WXcBeDtRgxS7u3aUuswpOGAoZGes-aO2vwnj23Ap3Y8yz6cqgqK5P_xQy7H1zrFXGy1B4YOFsyfE/s1600/acceler-interrupt-schematic.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="197" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiew8lH63xvyzEouClA4pdxV-UB3MHorZh1FHwG71xrV17QXBgNWcNyMFL9RqDOKO_WXcBeDtRgxS7u3aUuswpOGAoZGes-aO2vwnj23Ap3Y8yz6cqgqK5P_xQy7H1zrFXGy1B4YOFsyfE/s320/acceler-interrupt-schematic.png" width="320" /></a></div>For this test circuit I've added a 3.3V Zener diode and a 27-ohm current limiting resistor to protect the accelerometer from any accidental overvoltages. <br />
<br />
Here are screenshots of samples taken by the Saleae Logic analyzer on both the 5V and 2.8V I<sup>2</sup>C bus. You'll notice that the sampling rate is a mere 500kHz. This is a limitation of my computer. I don't have a USB 2.0 connection and therefor--according Logic--condemns me to this max rate. Else I'd definitely be sampling at >1MHz.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhEtZsrnftGkOolGTiu1kbrNScnAw8yB6MlKBQF8e1IXO9wuFp23MoevJgWLP0VfLGpTjIbW2l8j9ExGnCv_VwQWo-rnXn1R2SHth5JQSwVbZKchNwd4zvDfOHqSMHkgeu_-pBo2QgKNwA/s1600/Saleae+capture+MMA7660+data.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="152" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhEtZsrnftGkOolGTiu1kbrNScnAw8yB6MlKBQF8e1IXO9wuFp23MoevJgWLP0VfLGpTjIbW2l8j9ExGnCv_VwQWo-rnXn1R2SHth5JQSwVbZKchNwd4zvDfOHqSMHkgeu_-pBo2QgKNwA/s320/Saleae+capture+MMA7660+data.png" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_xQlcBiGmLJMF92e3M_bTZyeC4jAAl86ZzKuTaGG9pVibGVMyNeferv4z0LLgyKUMUJF_TyUyPZhHtN2zBIOiCH6ir7RCkUQrYPwO9GFrXrHSyRJEKpJkTNsGQqVyndpm14rj7NZw-1I/s1600/Saleae+capture+MMA7660+data+2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="152" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_xQlcBiGmLJMF92e3M_bTZyeC4jAAl86ZzKuTaGG9pVibGVMyNeferv4z0LLgyKUMUJF_TyUyPZhHtN2zBIOiCH6ir7RCkUQrYPwO9GFrXrHSyRJEKpJkTNsGQqVyndpm14rj7NZw-1I/s320/Saleae+capture+MMA7660+data+2.png" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJBCijCIcM530vVHUZG5e7eItDhN_DkNu9QSxa10pvXtoSkpXwOybSV3uJtStUYW98Ugi-abPsbjiLynC6gIIGbajHfQsgx_Uhmvv39SZFcOFZsHPmZwdjGbPeWnMG2rgBJ9h_NqTNLVE/s1600/Saleae+capture+MMA7660+data+3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="152" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJBCijCIcM530vVHUZG5e7eItDhN_DkNu9QSxa10pvXtoSkpXwOybSV3uJtStUYW98Ugi-abPsbjiLynC6gIIGbajHfQsgx_Uhmvv39SZFcOFZsHPmZwdjGbPeWnMG2rgBJ9h_NqTNLVE/s320/Saleae+capture+MMA7660+data+3.png" width="320" /></a></div><br />
<br />
The following vid shows how the accelerator's measured values change as it's tapped, turned on its sides, and flipped over. The XYZ values shown on the LCD are in the range of -31 to +31. They are not percentages of g. <br />
<br />
<iframe allowfullscreen="" frameborder="0" height="480" src="http://www.youtube.com/embed/ra1bd2eD-oI" width="640"></iframe><br />
<br />
<br />
I've already tested the tap interrupt function but have yet to try the other interrupts as well as shake detection and auto-sleep / auto-wake functions. <br />
<br />
In the firmware I rely on mikroC's built-in functions for accessing the I<sup>2</sup>C bus and the LCD. The splash screen routine wasn't installed in the firmware I used in the vid. <br />
<br />
<pre>/*
Displaying the measurements of the Freescale MMA7660FC Accelerometer on an LCD
Created: March 2012
Processor: PIC 16F1828
Compiler mikroC Pro 5.0.0
Configuration: power up timer, brownout reset (set to 2.5V), stack over/underflow reset -- all enabled, all others disabled
internal oscillator with clk pins as I/O
*/
#define t1h_ini 256 - 122 // value loaded into TMR1H every time it rolls over to zero for timer1 to count 0.25sec
// at a prescale = 1:8 and clock of 4MHz
#define I2Cclock 100000 // mikroC needs to know the desired I2C clock frequency (in Hz) in order to compute baud rate generator values for PIC registers
// MMA7660FC 7-bit address = 0x4C, left shift by one bit to become 0x98 in 8-bit format.
// LSB = 0 to read slave, 1 to write to slave
#define ACCwr 0x98 + 0
#define ACCrd 0x98 + 1
int8 XOUT, YOUT, ZOUT; // X,Y,Z axis values from accelerometer - register addresses 0x00, 0x01, 0x02 respectively
// values range from -31 to +32, with negative values in 2's complement
// if bit 5 = 1 then number is negative. To retrieve the value, get the complement and then add 1
// if bit 6 = 1 then value is not accurate because accelerometer was writing to register when it was read
int8 TILT; // contains the value of register address 0x03 of accelerometer
bit tap; // flag for tap detect
// following are needed by mikroC for built-in LCD functions
// Lcd pinout settings
sbit LCD_RS at RC1_bit;
sbit LCD_EN at RA2_bit;
sbit LCD_D7 at RC4_bit;
sbit LCD_D6 at RC3_bit;
sbit LCD_D5 at RC6_bit;
sbit LCD_D4 at RC7_bit;
// Pin direction
sbit LCD_RS_Direction at TRISC1_bit;
sbit LCD_EN_Direction at TRISA2_bit;
sbit LCD_D7_Direction at TRISC4_bit;
sbit LCD_D6_Direction at TRISC3_bit;
sbit LCD_D5_Direction at TRISC6_bit;
sbit LCD_D4_Direction at TRISC7_bit;
// **************************************************************************
/*
for LCD with Hitachi HD44780 (or compatible) controller chip
c1 = is the address of line 1 column 1 of the LCD
l2c1 = line 2 column 1
*/
// **************************************************************************
#define c1 0x00 + 0x80
#define c2 0x01 + 0x80
#define c3 0x02 + 0x80
#define c4 0x03 + 0x80
#define c5 0x04 + 0x80
#define c6 0x05 + 0x80
#define c7 0x06 + 0x80
#define c8 0x07 + 0x80
#define c9 0x08 + 0x80
#define c10 0x09 + 0x80
#define c11 0x0a + 0x80
#define c12 0x0b + 0x80
#define c13 0x0c + 0x80
#define c14 0x0d + 0x80
#define c15 0x0e + 0x80
#define c16 0x0f + 0x80
#define c17 0x10 + 0x80
#define c18 0x11 + 0x80
#define c19 0x12 + 0x80
#define c20 0x13 + 0x80
#define c21 0x14 + 0x80
#define c22 0x15 + 0x80
#define c23 0x16 + 0x80
#define c24 0x17 + 0x80
#define c25 0x18 + 0x80
#define c26 0x19 + 0x80
#define c27 0x1a + 0x80
#define c28 0x1b + 0x80
#define c29 0x1c + 0x80
#define c30 0x1d + 0x80
#define c31 0x1e + 0x80
#define c32 0x1f + 0x80
#define c33 0x20 + 0x80
#define c34 0x21 + 0x80
#define c35 0x22 + 0x80
#define c36 0x23 + 0x80
#define c37 0x24 + 0x80
#define c38 0x25 + 0x80
#define c39 0x26 + 0x80
#define c40 0x27 + 0x80
#define l2c1 0x40 + 0x80
#define l2c2 0x41 + 0x80
#define l2c3 0x42 + 0x80
#define l2c4 0x43 + 0x80
#define l2c5 0x44 + 0x80
#define l2c6 0x45 + 0x80
#define l2c7 0x46 + 0x80
#define l2c8 0x47 + 0x80
#define l2c9 0x48 + 0x80
#define l2c10 0x49 + 0x80
#define l2c11 0x4a + 0x80
#define l2c12 0x4b + 0x80
#define l2c13 0x4c + 0x80
#define l2c14 0x4d + 0x80
#define l2c15 0x4e + 0x80
#define l2c16 0x4f + 0x80
#define l2c17 0x50 + 0x80
#define l2c18 0x51 + 0x80
#define l2c19 0x52 + 0x80
#define l2c20 0x53 + 0x80
#define l2c21 0x54 + 0x80
#define l2c22 0x55 + 0x80
#define l2c23 0x56 + 0x80
#define l2c24 0x57 + 0x80
#define l2c25 0x58 + 0x80
#define l2c26 0x59 + 0x80
#define l2c27 0x5a + 0x80
#define l2c28 0x5b + 0x80
#define l2c29 0x5c + 0x80
#define l2c30 0x5d + 0x80
#define l2c31 0x5e + 0x80
#define l2c32 0x5f + 0x80
#define l2c33 0x60 + 0x80
#define l2c34 0x61 + 0x80
#define l2c35 0x62 + 0x80
#define l2c36 0x63 + 0x80
#define l2c37 0x64 + 0x80
#define l2c38 0x65 + 0x80
#define l2c39 0x66 + 0x80
#define l2c40 0x67 + 0x80
#define int1 bit
#define int8 unsigned char
#define int16 unsigned int
#define int32 unsigned long
#define on 1
#define off 0
#define input 1 // for TRISx
#define output 0 // for TRISx
#define analog 1 // for ANSELx
#define digital 0 // for ANSELx
// ************************************************************************************************
// I2C
// ************************************************************************************************
// initialize the accelerometer
void ACCini()
{
// upon power up the MMA7660 defaults to Standby Mode
// apparently the MMA7660 registers can only be changed when it's in Standby Mode
I2C1_Init(I2Cclock); // mikroC needs to set up PIC I2C registers given the user desired I2C frequency
I2C1_Start();
I2C1_Wr(ACCwr); // transmit MMA7660 address and write command
I2C1_Wr(0x6); // point to register address 0x06
I2C1_Wr(0b0); // [reg addr 0x06] all interrupts disabled
I2C1_Wr(0b0); // [reg addr 0x07]
I2C1_Wr(0b0); // [reg addr 0x08] set to 120 samples/sec -- required for tap detect
I2C1_Wr(0b01101111); // [reg addr 0x09] set
I2C1_Wr(0x0F); // [reg addr 0x0A]
I2C1_Stop();
// after registers have been initialized it can be put in Active Mode where it can start measuring values
I2C1_Start();
I2C1_Wr(ACCwr); // issue MMA7660 address and write command
I2C1_Wr(0x7); // point to register address 0x06
I2C1_Wr(0b11000001); // [reg addr 0x07] put MMA7660 in Active Mode. Upon power up chip enters Standby Mode
// interrupt is push-pull, interrupt is active high
I2C1_Stop();
} // void ACCini()
// VAL contains the value of accelerometer value of the axes
void DispAxis(int8 row, int8 col, int8 VAL)
{
#define pos 1
#define neg 0
bit sign; // 1 = number is positive; 0 = number is negative
sign = pos;
if (VAL.f5) // if bit5 is high then axis value is negative -- in two's complement
{
VAL.f6 = 1; // this bit is not part of the axis value so make it high so that when it's compelemented it'll be zero
VAL.f7 = 1; // this bit is not part of the axis value so make it high so that when it's compelemented it'll be zero
VAL = ~VAL + 1; // get the absolute value from the twos complement
sign = neg;
}
if (sign == pos)
Lcd_Chr(row,col, '+');
else
Lcd_Chr(row,col, '-');
// display the tens and ones digit of the axis value
Lcd_Chr_CP(VAL/10 + 0x30); // the ASCII code of a number is the number + 0x30
Lcd_Chr_CP(VAL%10 + 0x30); // the ASCII code of a number is the number + 0x30
}
// read and display measurements from the accelerometer
void ACCread()
{
int8 VAL;
do
{
I2C1_Start();
I2C1_Wr(ACCwr); // issue MMA7660 adddress and write command
I2C1_Wr(0x0); // point to desired register adddress
I2C1_Repeated_Start();
I2C1_Wr(ACCrd); // issue MMA7660 adddress and read command
XOUT = I2C1_Rd(1);
YOUT = I2C1_Rd(1);
ZOUT = I2C1_Rd(1);
TILT = I2C1_Rd(0);
I2C1_Stop();
} while (XOUT.f6 || YOUT.f6 || ZOUT.f6 || TILT.f6); // if bit6 of these registers is high then the registers were read while the chip was updating them
// read the registers again since the values will be unreliable
// X Y Z axes output
DispAxis(1, 3, XOUT);
DispAxis(1, 9, YOUT);
DispAxis(1, 14, ZOUT);
// Back / Front orientation
VAL = TILT & 0b11;
Lcd_Cmd(l2c1);
if (!VAL)
Lcd_Out_CP(" ? ");
else if (VAL == 1)
Lcd_Out_CP("FRONT");
else if (VAL == 2)
Lcd_Out_CP("BACK ");
else
Lcd_Out_CP("error");
// Portrait / Landscape orientation
Lcd_Cmd(l2c7);
VAL = (TILT & 0b11100) >> 2;
if (!VAL)
Lcd_Out_CP(" ? ");
else if (VAL == 1)
Lcd_Out_CP("LEFT ");
else if (VAL == 2)
Lcd_Out_CP("RIGHT");
else if (VAL == 5)
Lcd_Out_CP("DOWN ");
else if (VAL == 6)
Lcd_Out_CP("UP ");
else
Lcd_Out_CP("error");
// check for tap detect
// single tap = (a tap is detected in the current I2C read) && (no tap was detected during the last I2C read || a double tap was detected during the previous read )
// double tap = a tap is detected in the current read && a single tap was detected in the last I2C read
Lcd_Cmd(l2c13);
if (TILT.f5)
{
if (!tap)
{
tap = 1;
Lcd_Chr_CP('1');
}
else
{
tap = 0;
Lcd_Chr_CP('2');
}
}
else
{
Lcd_Chr_CP('-');
tap = 0;
}
} // void ACCread()
// Splash screen and permanent text on LCD
void DispIniText()
{
Lcd_Cmd(_LCD_CLEAR); // Clear display
Lcd_Cmd(_LCD_CURSOR_OFF); // Cursor off
// splash screen upon power up
Lcd_Out(1, 1, "FreescaleMMA7660");
Lcd_Out(2, 2, "Accelerometer");
Delay_ms(3000);
Lcd_Cmd(_LCD_CLEAR); // Clear display
// Text that will always remain on screen
Lcd_Out(1, 1, "X=");
Lcd_Out(1, 7, "Y=");
Lcd_Chr(1, 13, 'Z');
Lcd_Out(2, 14, "TAP");
}
// ************************************************************************************************
// other functions
// ************************************************************************************************
void IniReg()
{
// set internal clock frequency to 4MHz
OSCCON = 0b1101000;
TRISA = output;
TRISB = output;
TRISC = output;
ANSELA = digital;
ANSELB = digital;
ANSELC = digital;
PORTA = 0;
PORTB = 0;
PORTC = 0;
TRISB.f7 = input;
IOCBP.IOCBP7 = 1;
IOCBF = 0;
// Timer 1
TMR1L = 0;
TMR1H = t1h_ini;
PIR1.TMR1IF = 0;
T1CON = 0b110001; // prescale = 1:8, timer1 clock = Fosc/4, timer1 oscillator off, timer1 on
// Timer2 is used for state machine and switch debouncing timing tick
// with clock = 2MHz, PR2 = 125, prescale = 1:16, postscale = 1:1
// TMR2 will count from zero to PR2 and timer2 interrupt occurs every 125*16 / (2MHz / 4) = 4ms = timer2 tick
// T2CON = 0b110; // postscaler = 1:1, prescaler = 1:16, timer2 on
// TMR2 = 0;
// PR2 = 125;
WDTCON = 0b1000; // prescale = 1:512 (16ms typical)
// WDTE in configuration word is configured so that WDT enabled when MCU awake and disabled when MCU asleep
tap = 0;
} // void IniReg()
// ************************************************************************************************
// main
// ************************************************************************************************
void main()
{
IniReg();
Lcd_Init();
DispIniText();
ACCini();
while(1)
{
if (PIR1.TMR1IF)
{
TMR1H = t1h_ini;
ACCread();
PIR1.TMR1IF = 0;
}
}
} // void main()
</pre>erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com2tag:blogger.com,1999:blog-4092193417928690079.post-4091010505732927572012-03-01T14:30:00.000+08:002012-03-01T14:30:39.482+08:00TNG traffic light - epilogueEach of the two LED boards on top of the <a href="http://electromotiveforces.blogspot.com/2012/02/toy-traffic-light-next-gen-is-here.html">signal tower traffic light</a> are joined to the "platform board" via 9 solder joints. I certainly would not rely on them to hold the boards together nor am I deluded enough to think the corners of the boards won't chip off when the kids start playing with the toy and quite predictably knock it over ... and over. Those boards simply aren't going to survive this school of--literally--hard knocks. So as was planned I've potted the copper side of the boards with clear epoxy. Not only will the plastic make the whole three-board structure rigid and strong, the epoxy will also seal the copper tracks, solder connections and component leads and protect them from corrosion as well as minimize if not prevent possible injury to the kids if they go about running their fingers on the LED pins.<br />
<br />
The two sort of cream colored rings are more than conspicuous. For aesthetic reasons I actually would rather not have them except that they're absolutely necessary to soften the impact of any falls. Without them the canister and its cap and maybe even the colored acrylic(?) lenses will surely crack/break/shatter. The rings are made of soft rubber tubing (silicone I think) with an outer diameter of 10mm. To build and install the rings I first cut one piece equal to the circumference of the canister--50mm. I then glued the ends with cyanoacrylate superglue. After letting the glue set for a couple of minutes I coated the sides of the canister where the ring will be positioned with superglue. I slipped the ring on the canister and pressed the ring inwards to make sure there's good contact between the ring and canister. After it was securely bonded, I coated the top portion of that ring with superglue and placed the second ring on top of it. Hence, the upper ring is bonded only to the lower ring, not to the canister. This way I can push that top ring down when the canister cap has to be screwed off. The rings have to be as close to the cap as possible to make sure the face of the 7-segment LED does not hit the ground when the tower tips over. <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhb7yrmuokuc22YeEwBq2bW9uT-10nEo2Q97X47VFYKIYvN2Hb4u6p3HZGb6Q7nNwfy8lDmFNa3bRLAKcTZnLn0kA6mnnrKCsMmiDKhnWqZtWFFzsNebYkUUPSsANWcntrdZhyt-BRGE9c/s1600/traffic-tubing-epoxy1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhb7yrmuokuc22YeEwBq2bW9uT-10nEo2Q97X47VFYKIYvN2Hb4u6p3HZGb6Q7nNwfy8lDmFNa3bRLAKcTZnLn0kA6mnnrKCsMmiDKhnWqZtWFFzsNebYkUUPSsANWcntrdZhyt-BRGE9c/s320/traffic-tubing-epoxy1.png" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRAT5nntCHRgFRPhcBnKyI4ZmXjX96opWp_01FPeP3g4tASVQhOx7Yk1FRSVvyv67j3sS_M21rzmIBxhSQ854lxmu6q6Rrlp-BtCzcXycdZD1wMHbGC6NgQsQ-4fAZdag4OTp0k-cjO1U/s1600/traffic-tubing-epoxy2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRAT5nntCHRgFRPhcBnKyI4ZmXjX96opWp_01FPeP3g4tASVQhOx7Yk1FRSVvyv67j3sS_M21rzmIBxhSQ854lxmu6q6Rrlp-BtCzcXycdZD1wMHbGC6NgQsQ-4fAZdag4OTp0k-cjO1U/s320/traffic-tubing-epoxy2.png" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVN_hEseSvvvUrpjImxUooVWD9W_ykpnbyrJCuOYzZmhH0A-qFSMnW1jqOe7mhffRVJp8l0uLnyCZ86MaW23LwGW6nzYryP-DshUEcYS-1U57WBwNKXU8cIGAicHAocx4yKYw8tbOMO6w/s1600/traffic-tubing-epoxy3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVN_hEseSvvvUrpjImxUooVWD9W_ykpnbyrJCuOYzZmhH0A-qFSMnW1jqOe7mhffRVJp8l0uLnyCZ86MaW23LwGW6nzYryP-DshUEcYS-1U57WBwNKXU8cIGAicHAocx4yKYw8tbOMO6w/s320/traffic-tubing-epoxy3.png" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhowwIZUpsb2gD_2byYkSxNwYw_2cQKOLKowfajLA9rq7m9wrL-2cXGRJmrfXVZ87xXzDFTc7T_rhAT5FZm_sWc4GMGbsNPSM0HbM4RI4SacRxLW_MH1ZgYiVvBF3XU-e3LXv4nYTBkNr0/s1600/traffic-tubing-epoxy4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhowwIZUpsb2gD_2byYkSxNwYw_2cQKOLKowfajLA9rq7m9wrL-2cXGRJmrfXVZ87xXzDFTc7T_rhAT5FZm_sWc4GMGbsNPSM0HbM4RI4SacRxLW_MH1ZgYiVvBF3XU-e3LXv4nYTBkNr0/s320/traffic-tubing-epoxy4.png" width="114" /></a></div><br />
I've tested the light with power on and knocked it down a couple of times and for now at least it can take a lickin' but keeps on tickin. <br />
<br />
Just a couple of facts I failed to mention in the <a href="http://electromotiveforces.blogspot.com/2012/02/toy-traffic-light-next-gen-is-here.html">previous blog entry</a>.<br />
<br />
The green/ed on-time can be changed any time--in any mode. However, after doing so the light will immediately enter normal traffic light mode. <br />
<br />
You'll notice the 5-pin header around the center of the main board. That's for the ICSP connection to a PICkit 2 or 3. I foresee the kids wanting some changes to how the light works--a max green/red on-time of 90 seconds perhaps, or different non numeric characters when the amber/red lights are flashing. The ICSP connection will make firmware updates a breeze. I've actually used it already. The firmware I initially uploaded to the MCU (whilst on the breadboard) had a tiny bug--upon power-up the bulbs aren't all off and so--since the circuit defaults to a flashing amber upon turn-on--either or both the red and green lights are also on. Fail! After inserting a call to the function turning all bulbs off, I used the PICkit 2's Programmer-To-Go feature by uploading the firmware to the PICkit 2 and then plugging it into the board's ICSP headers and pressing the PICkit's red button to upload to the MCU. Note the board has to be powered up when uploading. <br />
<br />
While breadboarding the circuit I discovered that the resistors pulling the bases of the PNPs to the 12VDC supply were actually unnecessary. The LED display works fine without them. But I retained them anyway just to make sure the transistor bases aren't floating.erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com1tag:blogger.com,1999:blog-4092193417928690079.post-90732483800516524162012-02-27T17:41:00.028+08:002012-02-27T19:06:25.060+08:00Toy traffic light - the next gen is hereAfter so much anxiety over how to build and secure the 7-segment LEDs to top of the signal tower--and at the same time permit the cap to be removed in case the incandescent bulbs need to be replaced--I've finally come up with a workable design and modified the <a href="http://electromotiveforces.blogspot.com/2012/01/toy-traffic-light-using-signal-tower.html">original signal tower toy traffic light</a>. I've also slightly revised the <a href="http://electromotiveforces.blogspot.com/2012/01/toy-traffic-light-next-generation.html">draft schematic and firmware</a> (do check out that page for a primer on the circuit). Here's a summary--in images--of the build: <br />
<br />
PCB artwork for the main board and the LED boards. Main board is 3 x 5". The three LED boards are all 1.25 x 1.5".<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnmRjP-p-baCR9AJBetJefSBZacwFeXEIOgxdNn5FG4cO4twIfG8XDMQ_jklJWfCYKJLN7VElpPk6jBznUsVbWqRosN2CIKRcdmYCXRdcc8oe5m-jWIx9hpK9n5LjMaDKW2w0UuPiQswI/s1600/traffic-light-with-timer-main+board.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="198" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnmRjP-p-baCR9AJBetJefSBZacwFeXEIOgxdNn5FG4cO4twIfG8XDMQ_jklJWfCYKJLN7VElpPk6jBznUsVbWqRosN2CIKRcdmYCXRdcc8oe5m-jWIx9hpK9n5LjMaDKW2w0UuPiQswI/s320/traffic-light-with-timer-main+board.png" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtUm2lMOXM-ZeVCZiEXBsx_704NfbAQoocCGp4oBJG2Q2WtiN4KJ8CYsIdEyENYdg1FB6kGaP0Hm-Mc9yortHB-OXKCBhAZeTbkUwEkqMz7j-fu0mrRTOVHfFwmmCT31YcA196hruM8wE/s1600/traffic-light-with-timer-LED-boards.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="264" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtUm2lMOXM-ZeVCZiEXBsx_704NfbAQoocCGp4oBJG2Q2WtiN4KJ8CYsIdEyENYdg1FB6kGaP0Hm-Mc9yortHB-OXKCBhAZeTbkUwEkqMz7j-fu0mrRTOVHfFwmmCT31YcA196hruM8wE/s320/traffic-light-with-timer-LED-boards.png" width="320" /></a></div><br />
Freshly etched boards. I made an extra of what I call the "platform"--the board onto which the two 7-segment LED boards named ALPHA and BETA are soldered to at right angles. <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2ywbfMyKu85QvDUNOnHmD0B_M_xFNtCO8HaswsZFbtJUKxGoJbSKU95CQ_NhDVo9sgqzDdGlSAuosn7xVvktePNQkKvJaB5g2cF1IlD2DuZtXNvEiblVaKQ7UpMAUn4j-koOR6cfsrLU/s1600/traffic-board-main.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2ywbfMyKu85QvDUNOnHmD0B_M_xFNtCO8HaswsZFbtJUKxGoJbSKU95CQ_NhDVo9sgqzDdGlSAuosn7xVvktePNQkKvJaB5g2cF1IlD2DuZtXNvEiblVaKQ7UpMAUn4j-koOR6cfsrLU/s320/traffic-board-main.png" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwqx42PkKLO3HOYb_gd6CYcED1p4l4lzxhPvSq4bPOVPfA6fO0RG7Vao_r014ECoVhKEs5hLhBF1t3FL4Ou8xdiDbp6IIQ2QX2exWDAmRKZu49eqmuCvRFGlm_YlbXp_-X62H9c4fB68k/s1600/traffic-board-LED-panel.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwqx42PkKLO3HOYb_gd6CYcED1p4l4lzxhPvSq4bPOVPfA6fO0RG7Vao_r014ECoVhKEs5hLhBF1t3FL4Ou8xdiDbp6IIQ2QX2exWDAmRKZu49eqmuCvRFGlm_YlbXp_-X62H9c4fB68k/s320/traffic-board-LED-panel.png" width="320" /></a></div><br />
Below is the panel broken into four boards using the <a href="http://electromotiveforces.blogspot.com/2011/04/making-pcbs-using-presensitized-boards.html">score and snap method</a>. These have already been drilled (except for the extra board). <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrsMT_M2JT5hTKgq6mCy1M1ZJcEY070K379ubLLZZO5UyTvQ6q4NzYhqllZaxRpyhLvkNNs7vPF2hrCGxx1sEq8GRTavvTKpdyyAJa3Xdgo-ijh0JNfkeqJhSbUB46ieq2Kejk4GTBTuA/s1600/traffic-board-LED-boards-dr.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrsMT_M2JT5hTKgq6mCy1M1ZJcEY070K379ubLLZZO5UyTvQ6q4NzYhqllZaxRpyhLvkNNs7vPF2hrCGxx1sEq8GRTavvTKpdyyAJa3Xdgo-ijh0JNfkeqJhSbUB46ieq2Kejk4GTBTuA/s320/traffic-board-LED-boards-dr.png" width="320" /></a></div><br />
A 9-pin connector runs from the main board to the platform board. I soldered two cables back-to-back. You can see the white female plugs at the top and bottom of the photo. The other bundle of wires is the original cable for the bulbs.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYnPRlvhNJddwPkWobDXy_sv0Uk4SAE2L9DMcnOMJ880PfUT45EFd0Yv4PugVX8Vm8I0WiRZULO0QV7KrpSY3mpFBUJztae4DpY_AtwdpvAxTkeHfc-EEVMLn4CpN_WmaTRrJ0BgsU_NY/s1600/traffic-signal-tower-wires5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYnPRlvhNJddwPkWobDXy_sv0Uk4SAE2L9DMcnOMJ880PfUT45EFd0Yv4PugVX8Vm8I0WiRZULO0QV7KrpSY3mpFBUJztae4DpY_AtwdpvAxTkeHfc-EEVMLn4CpN_WmaTRrJ0BgsU_NY/s320/traffic-signal-tower-wires5.png" width="153" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhu1vkmS0ArmpuzMu0HWb3xmU58y3cnmdeO31IlgGwT3WX96kq0OwbniYjQ8tc14zW_YeOy6sBK3EFZds_QU7DI1OLuLIXleeiiNEGk0wU5fW7z5gW_6BWuUMafYy7XALezfB1nfKYhQlY/s1600/traffic-signal-tower-wires1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhu1vkmS0ArmpuzMu0HWb3xmU58y3cnmdeO31IlgGwT3WX96kq0OwbniYjQ8tc14zW_YeOy6sBK3EFZds_QU7DI1OLuLIXleeiiNEGk0wU5fW7z5gW_6BWuUMafYy7XALezfB1nfKYhQlY/s320/traffic-signal-tower-wires1.png" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjq7yyVjd3JKwTLvZFMxaa2KdiJf-mBSEnV-DTq4IqHspo10T7ek1XwcGN8L9jTL0Y7hScI9cNm4ijE9zei2FwCx86XhB3TL_-9_2r1l_V5bZcEz3AauyCJx38D4czrMmaBppEgabXPcRo/s1600/traffic-signal-tower-wires2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjq7yyVjd3JKwTLvZFMxaa2KdiJf-mBSEnV-DTq4IqHspo10T7ek1XwcGN8L9jTL0Y7hScI9cNm4ijE9zei2FwCx86XhB3TL_-9_2r1l_V5bZcEz3AauyCJx38D4czrMmaBppEgabXPcRo/s320/traffic-signal-tower-wires2.png" width="240" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDl6DWZnRuFXwbniY3Su0lB8_wWMC4VoG7YNuhXSPjZ_bg3Lkejfdyo7IOOs0YQMPvI4EyQqo_gK_QtGflI2dEhyphenhyphena35wkprNZc0-6U7UEb4hoWfNb1yF3BdvdgruBFjZO411LDVByUQ_Q/s1600/traffic-signal-tower-wires3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDl6DWZnRuFXwbniY3Su0lB8_wWMC4VoG7YNuhXSPjZ_bg3Lkejfdyo7IOOs0YQMPvI4EyQqo_gK_QtGflI2dEhyphenhyphena35wkprNZc0-6U7UEb4hoWfNb1yF3BdvdgruBFjZO411LDVByUQ_Q/s320/traffic-signal-tower-wires3.png" width="240" /></a></div><br />
<br />
That round yellow thing is a rubber pad inserted so that the signal tower and the plastic box are not rigidly connected to one another. From the looks of it the box is made of (high impact) polystyrene and is brittle. Sharp knocks and blows will crack it. The rubber acts to partly absorb shocks and prevent breakage. The three screws are tightened only to the point that the rubber is compressed a little bit. <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgI_-nF9Y6hxjh72-eivRt3bFB35ppD96JQm9azj99jKf-iOybrfmGQMogcaRcex3C6oBqJ-9Vw-aMNcsSEPvQtb5pUzQ9tFwZm7lnqPL16zAGueifu8Qua_SCBd_Io0WTGOuHzUqLacCo/s1600/traffic-signal-tower-wires6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgI_-nF9Y6hxjh72-eivRt3bFB35ppD96JQm9azj99jKf-iOybrfmGQMogcaRcex3C6oBqJ-9Vw-aMNcsSEPvQtb5pUzQ9tFwZm7lnqPL16zAGueifu8Qua_SCBd_Io0WTGOuHzUqLacCo/s320/traffic-signal-tower-wires6.png" width="315" /></a></div><br />
<br />
The biggest headache for me was finding a way to mount the LED display on top of the signal tower. Fortunately I found this plastic pill and trinket storage set. The individual canisters screw on top of one another with a final screw-on cap on top. Its diameter is 50mm--exactly the same as the signal tower's. I took the cap and one of the canisters and drilled the appropriate holes. I don't have a coping saw or router so I just used the drill bit to cut out the shapes for the connectors. Messy and crude but it worked. <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvdh4RppkG1AUkJclQXnf2e7RADQvjHed2dX0G2GUrdypL-cM1WeGS4xLrPmPujBPy62Dbm8kyPzEnQzu7_LbeKFN6xJYzaZ_brLXIw46l96xlQVnE-utAiPE9kRBaLO0Lb920PSKo6Hc/s1600/canister3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvdh4RppkG1AUkJclQXnf2e7RADQvjHed2dX0G2GUrdypL-cM1WeGS4xLrPmPujBPy62Dbm8kyPzEnQzu7_LbeKFN6xJYzaZ_brLXIw46l96xlQVnE-utAiPE9kRBaLO0Lb920PSKo6Hc/s320/canister3.png" width="238" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipBtnYL2zh_7FJa4fMbuc18jb0jqDfZo1JDaoAyH7Im7teMiSJiKUnvFD1dXfUAF-URhNd8KiJJ8tz6hbgF8777iv4e86R97OnCGyUWHP2KlKSRZElzqYMdaiWLvBVZJA-loPeOhPjt6c/s1600/traffic-timer3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipBtnYL2zh_7FJa4fMbuc18jb0jqDfZo1JDaoAyH7Im7teMiSJiKUnvFD1dXfUAF-URhNd8KiJJ8tz6hbgF8777iv4e86R97OnCGyUWHP2KlKSRZElzqYMdaiWLvBVZJA-loPeOhPjt6c/s320/traffic-timer3.png" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFCpe_nEBxqhbsWbri7xo5qpuzugX5wnFybPFCLxLe-EcGXev2qQDbbWlB1eOM-78-qLUemcfSUHgUTTSSuKSoz-nKOp9Zjphyn9h1MvL-iBbh-yWUWplLpmrOJIZgnv-XFgDEEilENc4/s1600/traffic-timer2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFCpe_nEBxqhbsWbri7xo5qpuzugX5wnFybPFCLxLe-EcGXev2qQDbbWlB1eOM-78-qLUemcfSUHgUTTSSuKSoz-nKOp9Zjphyn9h1MvL-iBbh-yWUWplLpmrOJIZgnv-XFgDEEilENc4/s320/traffic-timer2.png" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-L36WKI42K6JMudtfjFDJedNPvE8DmhTtpZC9Mr2fLholvji1k-G_EWfVWvfOxjoWE-eA9cK3LjQrmbDiYmWQvFX6kK8RI3v0kEkUmxwhrV1wxLt7FUpPsA6EGy8pTahlj5QbPhgr878/s1600/traffic-timer1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-L36WKI42K6JMudtfjFDJedNPvE8DmhTtpZC9Mr2fLholvji1k-G_EWfVWvfOxjoWE-eA9cK3LjQrmbDiYmWQvFX6kK8RI3v0kEkUmxwhrV1wxLt7FUpPsA6EGy8pTahlj5QbPhgr878/s320/traffic-timer1.png" width="320" /></a></div><br />
<br />
The countdown timer installed on top of the tower. I discarded the original (beige) cap of the signal tower and screwed in the canister instead.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKeYCYFY1zyVJzV2cKIsOHpL3zAGts4UVdBCxxCvAJZaptMaLA2a_PUxApQq_jHIfTsMjrmieVACU43r587x_KySHBUk1Rck25EKxzqq-U4erDzabQv5eBZqVxqAQT3_UyuaNkRyWN-vQ/s1600/traffic-signal-tower-complete1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKeYCYFY1zyVJzV2cKIsOHpL3zAGts4UVdBCxxCvAJZaptMaLA2a_PUxApQq_jHIfTsMjrmieVACU43r587x_KySHBUk1Rck25EKxzqq-U4erDzabQv5eBZqVxqAQT3_UyuaNkRyWN-vQ/s320/traffic-signal-tower-complete1.png" width="114" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9M3Yjv_AopE6cMgirk4ND97mHsc44ye1aHC2DZISxbJzGVI0tOuBsbWufhDs4cteIo9inYkKhQmNefzx5aYSlU-kwHBdUIgen8AaJVtufaWQwz5RuKAVCB3VsQ9OZs6P9rFxO99SjzWU/s1600/traffic-signal-tower-complete2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9M3Yjv_AopE6cMgirk4ND97mHsc44ye1aHC2DZISxbJzGVI0tOuBsbWufhDs4cteIo9inYkKhQmNefzx5aYSlU-kwHBdUIgen8AaJVtufaWQwz5RuKAVCB3VsQ9OZs6P9rFxO99SjzWU/s320/traffic-signal-tower-complete2.png" width="210" /></a></div><br />
<br />
The main board before and after the various wires were connected. You will notice the chopstick extension piece glued to the yellow push button. As in the previous model that stick pokes through a hole on top half of the plastic box. The board is secured to the base of the box using hot melt glue. <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEja5U4hHp8WMxkXWu59ErDYM-LQS78fjg_PhXb91Fq5qqyhAwuyQhzmelfPfNY0B79gB6EXD0WEPqFzW8J758aEQ1_zMvpKkAEubb6KaXIv8biGFWLttNuwfwqplW1Xg_93a11bjVTNHsg/s1600/traffic-board-complete1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEja5U4hHp8WMxkXWu59ErDYM-LQS78fjg_PhXb91Fq5qqyhAwuyQhzmelfPfNY0B79gB6EXD0WEPqFzW8J758aEQ1_zMvpKkAEubb6KaXIv8biGFWLttNuwfwqplW1Xg_93a11bjVTNHsg/s320/traffic-board-complete1.png" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHt_VYEHUrWaTs3dta5d6LCI1AD_Kbs2WRS7-ejsE3rThPHxgvz8lvpUmcGRXB_NhbgzFxmUcG71BgOV-_mtBKn4VwhjdJUVHL2XLEkj5VRaDRwct4B3-s21A8kkjSIrYp8Wu0TOFWNtY/s1600/traffic-board-complete3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHt_VYEHUrWaTs3dta5d6LCI1AD_Kbs2WRS7-ejsE3rThPHxgvz8lvpUmcGRXB_NhbgzFxmUcG71BgOV-_mtBKn4VwhjdJUVHL2XLEkj5VRaDRwct4B3-s21A8kkjSIrYp8Wu0TOFWNtY/s320/traffic-board-complete3.png" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoE34T03QrT4cctzD-Pge11ggKM9JY_aRcVr54DmxJASZqixJYCqLmHbJtFif-NxCulSAfY6YqF1-heNFzp5A0ZNB8zP5D1bAcuZtoNZMkmwuPkCKKLpphWroOzlBqrjkLCGvy2F42Wgo/s1600/traffic-board-complete2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="226" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoE34T03QrT4cctzD-Pge11ggKM9JY_aRcVr54DmxJASZqixJYCqLmHbJtFif-NxCulSAfY6YqF1-heNFzp5A0ZNB8zP5D1bAcuZtoNZMkmwuPkCKKLpphWroOzlBqrjkLCGvy2F42Wgo/s320/traffic-board-complete2.png" width="320" /></a></div><br />
<br />
Here's a vid showing how the traffic light works.<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="480" src="http://www.youtube.com/embed/n5g7tI2WRVU" width="640"></iframe><br />
<br />
<br />
As I mentioned above I've made some revisions to the <a href="http://electromotiveforces.blogspot.com/2012/01/toy-traffic-light-next-generation.html">drafts</a> of the schematic and firmware. Here are the final versions.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTFsJFkS7jpxrvJ-mHcuvlkDKsdO_eX6TGv1pVzI_m2ptLt67EVXoWUHXl_OWIcqvQM8oQ5FlCFlKtYYb1W9iPbSwk2oXCRpHq8-uhnrFsgBI9ZriAzPoZm4Sb3cGL6CjyOMg7WNHPWY0/s1600/traffic-schem1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTFsJFkS7jpxrvJ-mHcuvlkDKsdO_eX6TGv1pVzI_m2ptLt67EVXoWUHXl_OWIcqvQM8oQ5FlCFlKtYYb1W9iPbSwk2oXCRpHq8-uhnrFsgBI9ZriAzPoZm4Sb3cGL6CjyOMg7WNHPWY0/s320/traffic-schem1.png" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVGL4KbJa3NKvgfkupOCMKle3XKSdqzs8dMGiiQ0dX0CqsEczdpgGnFUDGZ1vGsTbdZ1PV1mWo9QeWiHElYMB8Nw3CdZLd3ywGk1CR7llqLTJgL_DIlfELbtG_zVc_g4hq0OeSk5hgOEg/s1600/traffic-schem2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="274" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVGL4KbJa3NKvgfkupOCMKle3XKSdqzs8dMGiiQ0dX0CqsEczdpgGnFUDGZ1vGsTbdZ1PV1mWo9QeWiHElYMB8Nw3CdZLd3ywGk1CR7llqLTJgL_DIlfELbtG_zVc_g4hq0OeSk5hgOEg/s320/traffic-schem2.png" width="320" /></a></div><br />
MCU = Microchip PIC16F1827 microcontroller<br />
D1 = 1N5822 Schottky diode<br />
VR = 78L05 +5V voltage regulator<br />
7SEG1, 7SEG2 = 306IDB common anode 2-digit 7-segment LED<br />
Q1, Q2, Q3 = TIP102 NPN Darlington transistors<br />
Q4, Q5 = S9012 PNP transistors<br />
Q6, Q7 = 2N7000 MOSFET transistors<br />
Q8 = ULN2003 transistor array<br />
LR, LR, LG = incandescent lamps<br />
<br />
<br />
<pre>/*
Kids' Traffic Light with Countdown Timer Using an Industrial Signal Tower
Created: January 2012
Processor: PIC 16F1827
Compiler mikroC Pro 5.0.0
Remarks: see "kids traffic light.dwg" for schematic
Configuration: power up timer, brownout reset (set to 2.5V), WDT, stack over/underflow reset -- all enabled, all others disabled
* Uses a 12VDC wall wart as power supply
* Tower light 12VDC red, amber, green incandescent lamps are switched by NPN Darlington transistors
* pushbutton:
* when momentarily pressed: cycles through different possible modes:
1. flashing amber
2. flashing red
3. alternating amber and red
4. normal traffic light: green -> amber -> red
* when pressed and kept depressed for over a couple of seconds LED display shows 1,2,3,4,5,6 in sequence corresponding to following go/stop on-time:
1. 5sec
2. 10sec
3. 15sec
4. 20sec
5. 25sec
6. 30sec
* although LED readout can be made to show number of real-time seconds when selecting go/stop time
showing it in the manner above provides an opportunity to teach the children how to multiply--in this case multiply by 5
* go/stop on-time is stored in EEPROM
* has 7-segment LED display
* shows the go/stop on-time when selecting it
* shows the remaining time before light changes
* shows various non-numeric characters in other modes
*/
// ************************************************************************************************
// input / output
// ************************************************************************************************
#define lred LATB.f3 // NPN darlington switches 12VDC incandescent lamp
#define lamber LATB.f2 // NPN darlington switches 12VDC incandescent lamp
#define lgreen LATB.f1 // NPN darlington switches 12VDC incandescent lamp
#define pb PORTB.f6 // momentary contact push button
#define tris_pb TRISB.f6 // for setting pb pin as input
#define wpu_pb WPUB.f6 // for enabling weak pullup
#define anode_ones LATB.f5 // S9012 PNP transistor
#define anode_tens LATB.f4 // S9012 PNP transistor
#define seg_a LATA.f4 // segment a of seven segment LED common anode
#define seg_b LATA.f3 // segment b of seven segment LED common anode
#define seg_c LATA.f0 // segment c of seven segment LED common anode
#define seg_d LATA.f6 // segment d of seven segment LED common anode
#define seg_e LATA.f7 // segment e of seven segment LED common anode
#define seg_f LATA.f1 // segment f of seven segment LED common anode
#define seg_g LATA.f2 // segment g of seven segment LED common anode
// ************************************************************************************************
// traffic light time duration
// ************************************************************************************************
#define count_ini 250 // number of timer2 ticks to make one second
#define ambertime 500 // in normal mode -- amount of time for amber light to be on after green and before red -- time in terms of timer2 ticks
#define flashambertime 200 // in flashing amber mode -- amount of time for amber light to be on and amount of time to be off -- time in terms of timer2 ticks
#define flashredtime 200 // in flashing red mode -- amount of time for red light to be on and amount of time to be off -- time in terms of timer2 ticks
#define flashredambtime 200 // in red/amber alternate flashing mode -- amount of time for red light to be on and amount of time amber light to be on -- time in terms of timer2 ticks
#define ledflashtime 25 // in normal mode during yield (amber light) -- amount of time the character displayed on 7-seg is flashed on and off
#define changergtime 375 // minimum amount of time for button to be kept pressed before cycling through the green light on time -- time in terms of timer2 ticks
// given 4ms timer2 tick, changergtime of 250 = 1sec real time
#define prechangergtime changergtime - 250 // amount of time after button is held down when all lights are turned off in preparation for possible change of green/red light on time
#define rgtime_increm 1250 // amount of time for green/red light to be on, and multiples thereof upon continual button press -- value in terms of timer2 ticks.
#define maxrgtime 6 // maximum allowed multiple of rgtime_increm
int16 REDGREENTIME; // amount of time for green/red light to be on -- time in terms of timer2 ticks
int8 RGTIME; // multiples of rgtime_increm such that RGTIME*rgtime_increm = REDGREENTIME
// ************************************************************************************************
// for pushbutton
// ************************************************************************************************
#define rising 1 // rising edge detected. used by PBedge
#define released 1 // rising edge detected. used by PBedge
#define falling 2 // falling edge detected. used by PBedge
#define pressed 2 // falling edge detected. used by PBedge
#define none 0 // no edge. used by PBedge
int8 PBval; // last eight values of the switch upon reading it
int8 PBedge; // edge detected?, 0 = no edge detect, 1 = rising edge, 2 = falling edge; other values = Not Used / Undefined for now
bit PBlevel; // voltage level of switch when not bouncing (hi = 1, lo = 0)
// ************************************************************************************************
// general defines and variables
// ************************************************************************************************
#define int1 bit
#define int8 unsigned char
#define int16 unsigned int
#define int32 unsigned long
#define on 1
#define off 0
#define input 1 // for TRISx
#define output 0 // for TRISx
#define analog 1 // for ANSELx
#define digital 0 // for ANSELx
#define addr_rgtime 0x10 // eeprom address for user selected green/red light on time
#define disp_dash 111 // code to display a dash, for use with UpdateDisp()
#define disp_3horiz 120 // code to display three horizontal segments, for use with UpdateDisp()
#define disp_tophoriz 121 // code to display top horizontal segment, for use with SegmentAssign()
#define disp_midhoriz 122 // code to display middle horizontal segment, for use with SegmentAssign()
#define disp_botthoriz 123 // code to display bottom horizontal segment, for use with SegmentAssign()
#define disp_brackets 130 // code to display brackets, for use with UpdateDisp()
#define disp_left_bracket 131 // code to display left bracket, for use with SegmentAssign()
#define disp_right_bracket 132 // code to display right bracket, for use with SegmentAssign()
#define disp_topsquare 141 // code to display top square, for use with UpdateDisp()
#define disp_bottsquare 142 // code to display bottom square, for use with UpdateDisp()
#define disp_blank 255 // code to display nothing, for use with UpdateDisp()
int16 TIME = 0; // records how long a light has been on -- in terms of timer2 ticks
int8 VALUE = disp_blank; // number or character to be displayed on 7-seg LED
int8 RGSECONDS; // user-selected red/green on-time in real-time seconds
// ************************************************************************************************
// for state machines
// ************************************************************************************************
// !! A C H T U N G !!
// make sure all non user selectable modes such as _standby and _selectrgtime come AFTER user selectable
// and that _normal" is the last item in the valid user selectable modes because it is used as the max value in ProcessKey()
enum {_flashingamber, _flashingred, _flashingredamber, _normal,
/* the modes that follow are non-user selectable --> */ _standby, _selectrgtime} STATEMODE = _flashingamber;
enum {_stop, _yield_ini, _yield, _go} STATENORMAL = _stop;
enum {_init, _flash} STATEFLASHGREEN = _init;
// ===========================================================================================
// LED 7-Segment Display
// ===========================================================================================
void SegmentAssign(int8 num)
{
seg_a = 0;
seg_b = 0;
seg_c = 0;
seg_d = 0;
seg_e = 0;
seg_f = 0;
seg_g = 0;
switch (num)
{
case 1:
seg_b = 1;
seg_c = 1;
break;
case 2:
seg_a = 1;
seg_b = 1;
seg_d = 1;
seg_e = 1;
seg_g = 1;
break;
case 3:
seg_a = 1;
seg_b = 1;
seg_c = 1;
seg_d = 1;
seg_g = 1;
break;
case 4:
seg_b = 1;
seg_c = 1;
seg_f = 1;
seg_g = 1;
break;
case 5:
seg_a = 1;
seg_c = 1;
seg_d = 1;
seg_f = 1;
seg_g = 1;
break;
case 6:
seg_a = 1;
seg_c = 1;
seg_d = 1;
seg_e = 1;
seg_f = 1;
seg_g = 1;
break;
case 7:
seg_a = 1;
seg_b = 1;
seg_c = 1;
break;
case 8:
seg_a = 1;
seg_b = 1;
seg_c = 1;
seg_d = 1;
seg_e = 1;
seg_f = 1;
seg_g = 1;
break;
case 9:
seg_a = 1;
seg_b = 1;
seg_c = 1;
seg_d = 1;
seg_f = 1;
seg_g = 1;
break;
case 0:
seg_a = 1;
seg_b = 1;
seg_c = 1;
seg_d = 1;
seg_e = 1;
seg_f = 1;
break;
case disp_dash: // displays a dash - middle horizontal segment
seg_g = 1;
break;
case disp_3horiz: // displays three horizontal segments
seg_a = 1;
seg_d = 1;
seg_g = 1;
break;
case disp_tophoriz: // displays top horizontal segment
seg_a = 1;
break;
case disp_botthoriz: // displays bottom horizontal segment
seg_d = 1;
break;
case disp_left_bracket: // displays opening bracket
seg_a = 1;
seg_d = 1;
seg_e = 1;
seg_f = 1;
break;
case disp_right_bracket: // displays closing bracket
seg_a = 1;
seg_b = 1;
seg_c = 1;
seg_d = 1;
break;
case disp_topsquare: // displays a square on upper half of LED
seg_a = 1;
seg_b = 1;
seg_f = 1;
seg_g = 1;
break;
case disp_bottsquare: // displays a square on the lower half of the LED
seg_c = 1;
seg_d = 1;
seg_e = 1;
seg_g = 1;
break;
default: // if invalid value then display blank
case disp_blank: // segments have already been turned off at the start of this function so do nothing
break;
} // switch (num)
} // void SegmentAssign(int8 num)
void UpdateDisp()
{
static bit anode; // flag bit multiplexing display between tens and ones place
int8 TENS, ONES; // contains the digit or code of the non-numeric character to be displayed in the tens and ones place
// turn off power to 7-segment LED display.
anode_ones = off;
anode_tens = off;
if (STATEMODE != _standby) // turn on LED display only when not in _standby mode
{
if (VALUE < 100) // values >= 100 are codes for non-numeric characters
{
TENS = VALUE/10;
ONES = VALUE%10;
} // if (VALUE < 100)
else
{
switch (VALUE)
{
case disp_dash:
TENS = disp_dash;
ONES = disp_dash;
break;
case disp_3horiz:
TENS = disp_3horiz;
ONES = disp_3horiz;
break;
case disp_tophoriz:
TENS = disp_tophoriz;
ONES = disp_tophoriz;
break;
case disp_botthoriz:
TENS = disp_botthoriz;
ONES = disp_botthoriz;
break;
case disp_brackets:
TENS = disp_left_bracket;
ONES = disp_right_bracket;
break;
case disp_topsquare:
TENS = disp_topsquare;
ONES = disp_topsquare;
break;
case disp_bottsquare:
TENS = disp_bottsquare;
ONES = disp_bottsquare;
break;
default: // if invalid value then display blank
case disp_blank:
TENS = disp_blank;
ONES = disp_blank;
break;
} // switch (VALUE)
} // else if (VALUE >= 100)
if (anode)
{
if (TENS != 0) // do not display tens place if it's zero
{
SegmentAssign(TENS);
anode_tens = on;
}
anode = 0;
}
else
{
SegmentAssign(ONES);
anode_ones = on;
anode = 1;
}
} // if (display)
} // void UpdateDisp()
// ===========================================================================================
// functions
// ===========================================================================================
void ComputeRedGreenTime()
{
REDGREENTIME = RGTIME*rgtime_increm;
RGSECONDS = RGTIME*5;
}
void AllLightsOff()
{
lred = off;
lamber = off;
lgreen = off;
}
void IniReg()
{
// set internal clock frequency to 2MHz
OSCCON = 0b1100000;
TRISA = output;
TRISB = output;
ANSELA = digital;
ANSELB = digital;
tris_pb = input;
// enable weak pull up for pushbutton
OPTION_REG.NOT_WPUEN = 0;
wpu_pb = 1;
AllLightsOff(); // necessary to make sure incandescent lamps which should be off upon start up are off
// Timer2 is used for state machine and switch debouncing timing tick
// with clock = 2MHz, PR2 = 125, prescale = 1:16, postscale = 1:1
// TMR2 will count from zero to PR2 and timer2 interrupt occurs every 125*16 / (2MHz / 4) = 4ms = timer2 tick
T2CON = 0b110; // postscaler = 1:1, prescaler = 1:16, timer2 on
TMR2 = 0;
PR2 = 125;
WDTCON = 0b1000; // prescale = 1:512 (16ms typical)
// WDTE in configuration word is configured so that WDT enabled when MCU awake and disabled when MCU asleep
// retrieve stored green/red light on time value.
// If eeprom-stored value is out of valid range then set it to minimum and store this value in eeprom
// stored values are in terms of multiples of rgtime_increm such that stored value multiplied by rgtime_increm = time in terms of TMR2 ticks
RGTIME = EEPROM_Read(addr_rgtime);
if (RGTIME == 0 || RGTIME > maxrgtime)
{
RGTIME = 1;
EEPROM_Write(addr_rgtime, RGTIME);
}
ComputeRedGreenTime(); // REDGREENTIME = RGTIME*rgtime_increm;
// initialize push button
PBval = 0xFF;
PBlevel = hi;
} // void InitRegisters()
// amber flashes on and off at a rate determined by flashambertime
// LED display shows the character stored in VALUE
void FlashingAmber()
{
static bit flag;
if (++TIME < flashambertime)
{
if (flag)
{
lamber = on;
VALUE = disp_brackets;
}
else
{
lamber = off;
VALUE = disp_blank;
}
}
else
{
if (flag)
flag = 0;
else
flag = 1;
TIME = 0;
}
} // void FlashingAmber()
// red flashes on and off at a rate determined by flashredtime
// LED display shows the character stored in VALUE
void FlashingRed()
{
static bit flag;
if (++TIME < flashredtime)
{
if (flag)
{
lred = on;
VALUE = disp_brackets;
}
else
{
lred = off;
VALUE = disp_blank;
}
}
else
{
if (flag)
flag = 0;
else
flag = 1;
TIME = 0;
}
} // void FlashingRed()
// red and amber turn on alternately at a rate determined by flashredambertime
// LED display shows the character stored in VALUE
void FlashingRedAmber()
{
static bit flag;
if (++TIME < flashredambtime)
{
if (flag)
{
lamber = off;
lred = on;
VALUE = disp_topsquare;
}
else
{
lred = off;
lamber = on;
VALUE = disp_bottsquare;
}
}
else
{
if (flag)
flag = 0;
else
flag = 1;
TIME = 0;
}
} // void FlashingRedAmber()
// normal traffic light operation: green --> amber --> red
// red/green light on-time is user selectable
// LED display shows the countdown time in seconds during red and green light
// LED display shows a flashing "--" during amber light, blink rate determined by ledbflashtime
void Normal()
{
static int8 COUNTER;
static bit toggle;
switch (STATENORMAL)
{
case _stop:
if (++TIME < REDGREENTIME)
{
lred = on;
if (--COUNTER == 0)
{
--VALUE;
COUNTER = count_ini;
}
}
else
{
STATENORMAL = _go;
TIME = 0;
COUNTER = count_ini;
VALUE = RGSECONDS;
lred = off;
}
break;
case _yield_ini:
toggle = 1;
VALUE = disp_dash;
COUNTER = ledflashtime;
TIME = 0;
STATENORMAL = _yield;
break;
case _yield:
if (++TIME < ambertime)
{
lamber = on;
if (--COUNTER == 0)
{
if (toggle)
{
VALUE = disp_blank;
toggle = 0;
}
else
{
VALUE = disp_dash;
toggle = 1;
}
COUNTER = ledflashtime;
}
}
else
{
STATENORMAL = _stop;
TIME = 0;
COUNTER = count_ini;
VALUE = RGSECONDS;
lamber = off;
}
break;
case _go:
if (++TIME < REDGREENTIME)
{
lgreen = on;
if (--COUNTER == 0)
{
--VALUE;
COUNTER = count_ini;
}
}
else
{
STATENORMAL = _yield_ini;
lgreen = off;
}
break;
default:
TIME = 0;
STATENORMAL = _yield;
} // switch (STATENORMAL)
} // void StateMachNormal()
void StateMachMain()
{
switch (STATEMODE)
{
case _normal:
Normal();
break;
case _flashingamber:
FlashingAmber();
break;
case _flashingred:
FlashingRed();
break;
case _flashingredamber:
FlashingRedAmber();
break;
case _standby: // do nothing mode where all bulbs are off. used when changing red/green on-time
break;
case _selectrgtime: // er uhhh, I guess we do nothing as well
break;
default:
STATEMODE = _normal;
}
} // void StateMachMain()
void DebounceSwitch()
{
// shift all bits to the left
// if switch reading is hi then let pb_val bit 0 = 1
PBval <<= 1;
if (pb)
++PBval;
PBedge = none;
// if level is lo and all bits of pb_val are now hi then
// a rising edge has been detected
// switch is considered just released when rising edge is detected
// switch level is now hi
if ((!PBlevel) && (PBval == 0xFF))
{
PBlevel = hi;
PBedge = rising;
}
// if level is hi and all bits of pb_val are now low then
// a falling edge has been detected
// switch is considered just pressed when falling edge is detected
// switch level is now lo
if ((PBlevel) && (!PBval))
{
PBlevel = lo;
PBedge = falling;
}
} // void DebounceSwitch()
void ProcessKey()
{
int8 MODE; // temporary storage of current STATEMODE
static int16 PBPRESSTIMETOTAL = 0; // Keeps track of how long PB is depressed in terms of timer2 ticks
// Keeps track of total time from falling edge (switched pressed) to rising edge (switch released).
// if less than changergtime then we know the keypress is momentary and user wants to change modes
static int16 PBPRESSTIME = 0; // Keeps track of how long PB is depressed in terms of timer2 ticks
// this variable is reset every time it exceeds changergtime
// every time it exceeds changergtime red/green on-time is incremented until masrgtime and then it cycles back to one
DebounceSwitch();
MODE = STATEMODE;
if (PBlevel == lo)
{
if (++PBPRESSTIMETOTAL == prechangergtime)
{
AllLightsOff();
STATEMODE = _standby;
}
if (++PBPRESSTIME >= changergtime)
{
PBPRESSTIME = 0;
if (++RGTIME > maxrgtime)
RGTIME = 1;
// EEPROM_Write(addr_rgtime, RGTIME);
// ComputeRedGreenTime(); // REDGREENTIME = RGTIME*rgtime_increm;
STATEMODE = _selectrgtime;
VALUE = RGTIME; // RGTIME will be displayed on the LED readout
} // if (++PBPRESSTIME >= changergtime)
} // if (PBlevel == lo)
if (PBedge == released)
{
if (PBPRESSTIMETOTAL < changergtime) // if pushbutton was only momentarily pressed then change to the next mode
{
STATEMODE = ++MODE;
if (STATEMODE == _normal)
STATENORMAL = _yield_ini; // this is necessary to start normal traffic light mode properly
else if (STATEMODE > _normal)
STATEMODE = 0;
} // if (PBPRESSTIMETOTAL < changergtime)
else // green/red light on time has been changed so go to normal traffic light mode and begin with amber light
{
EEPROM_Write(addr_rgtime, RGTIME);
ComputeRedGreenTime(); // REDGREENTIME = RGTIME*rgtime_increm;
STATEMODE = _normal;
STATENORMAL = _yield_ini;
}
AllLightsOff(); // turn off all bulbs and let state machine take care of which ones to turn on
TIME = 0; // reset timer so that whatever mode has been selected, light will go through full time allotted
PBPRESSTIMETOTAL = 0;
PBPRESSTIME = 0;
} // if (PBedge == released)
} // void ProcessKey()
void main()
{
IniReg();
while(1)
{
if (PIR1.TMR2IF)
{
PIR1.TMR2IF = 0;
asm{clrwdt}
UpdateDisp();
ProcessKey();
StateMachMain();
} // if (PIR1.TMR2IF)
} // while(1)
} // void main()
</pre>erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com0tag:blogger.com,1999:blog-4092193417928690079.post-4613683428202908682012-02-20T21:14:00.003+08:002012-02-20T21:39:18.077+08:00AC load switching via infrared remote controlLaziness is the mother of invention. Not wanting to get out of bed to turn off the room lights, it occurred to me to build a circuit--one that I may end up using or not--which would switch the lights off/on when it receives the proper signals from a TV remote control. Here's the original design:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGeGGPd3cXT9d4IUnDSBqDvzVla5Mkg5wvdvBVDRk3OnB-0gJ7FN4z9CvSXYYfwmHCwftBr-BNt0yOhYfyw1RHmg8bc4c1HSGBADWrCdGDOfcBfl45TvYDNz0jcT4Kz7sSm2c8xX2ONGI/s1600/IR-remote-AC-load-switching.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGeGGPd3cXT9d4IUnDSBqDvzVla5Mkg5wvdvBVDRk3OnB-0gJ7FN4z9CvSXYYfwmHCwftBr-BNt0yOhYfyw1RHmg8bc4c1HSGBADWrCdGDOfcBfl45TvYDNz0jcT4Kz7sSm2c8xX2ONGI/s320/IR-remote-AC-load-switching.png" width="320" /></a></div><br />
After breadboarding and testing the above it hit me: Why not add more features such as setting the amount of time the light (or whatever load it may be) will remain on, or time elapsed before it automatically turns on. Adding these capabilities would merely require changes to the software. But because I would need the LEDs as indicator lights for various functions--eg. making them blink as user feedback--they have to be controlled separately from the triac/load. Since the 8-pin MCU has unused pins it was trivial assigning each LED a MCU pin of its own. Here's the final circuit. I've changed the triac gate current limiting resistor value as well: <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYvORL5Q93N3k-hC1hfuBK8sUy0Y5RgBQcjthaITjzJEEFZHROZz8sS58j-QAYj0IV6rMAiZaOQ5B6rI3EBDBYTCrZwcKf2-acJrZJ2y4jmx5FVzOOhcnmziAhetPywRSbZjIOfEhUpN4/s1600/IR-remote-AC-load-switch-v2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="191" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYvORL5Q93N3k-hC1hfuBK8sUy0Y5RgBQcjthaITjzJEEFZHROZz8sS58j-QAYj0IV6rMAiZaOQ5B6rI3EBDBYTCrZwcKf2-acJrZJ2y4jmx5FVzOOhcnmziAhetPywRSbZjIOfEhUpN4/s320/IR-remote-AC-load-switch-v2.png" width="320" /></a></div><br />
MCU = <a href="http://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0CCoQFjAA&url=http%3A%2F%2Fwww.microchip.com%2Fwwwproducts%2FDevices.aspx%3FdDocName%3Den027148&ei=PfZAT7L5A4SfmQWUv6DfBw&usg=AFQjCNHLgwmezgubWFO2OisPiE9PbyAC2w">Microchip PIC12F615</a> microcontroller<br />
IRR =<a href="http://catalog.osram-os.com/catalogue/catalogue.do?favOid=00000002000246c8001c0023&act=showBookmark"> Osram SFH5110-38</a> IR receiver 38kHz<br />
Z1 = 1N4734A 5.6V 1W zener diode<br />
Q1 = 2N7000 n-channel enhancement MOSFET transistor <br />
TRIAC = <a href="http://www.littelfuse.com/data/en/Data_Sheets/Littelfuse_Thyristor_Lx01Ex_LxNx_Qx01Ex_QxNx.pdf">Teccor Q401E3</a> 400V 1A triac <br />
<br />
Z1, C1, C2, and D1 comprise a half-wave transformerless power supply providing approximately 5VDC. With C1 = 1uF the circuit is designed to run off 220VAC 60Hz mains, providing a theoretical maximum current of around 80mA RMS. The above may or may not work with 110VAC since at that voltage the available DC current is halved. For 110VAC 60Hz use, doubling the capacitance to 2uF will halve the capacitive reactance and thus let through twice the current. Capacitive reactance is given by:<br />
<br />
X<sub>C</sub> = (6.28fC)<sup>-1</sup><br />
<br />
where<br />
f = frequency of the AC line in Hertz<br />
C = capacitance in farads <br />
<br />
Therefore, with C = 2uF X<sub>C</sub> = 1327ohms. RMS current is therefore = 110VAC/1327 = 83mA RMS<br />
<br />
One leg of the mains is common to both DC and AC lines. This is necessary for the triac to be triggered by the DC circuit.<br />
<br />
Because the circuit doesn't use a transformer any part of it is potentially lethal to the touch. The net labeled 5V is 5VDC with respect to the circuit ground. But it is 220VAC with respect to the other leg of the AC line and it can be as much as 110VAC with respect to earth ground, so beware! It goes without saying that errors in connecting/ soldering of the components of the power supply could result in unintended and undesirable fireworks and tripping of the building's breakers/fuses. Be damn careful when designing and building circuits involving mains voltage. I come down with OCD and anxiety disorder every time. You can't check and recheck enough times that you've done everything right.<br />
<br />
R1 is an ordinary carbon resistor and functions as a fuse. It's supposed to blow if current through the circuit (but not the load and MT1 to MT2 of the triac) exceeds around 100mA. Given an 18-ohm 250mW resistor, current at which its dissipation is 250mW is = √(0.25/18) = 118mA. So the resistor should <i>theoretically</i> fry if it exceeds this value for over a fraction of a second.<br />
<br />
Note, however, that given 220V RMS, its peak voltage = 220√2 = 311V. With C1 = 1uF X<sub>C</sub> = 2653ohms. Therefore, peak current = 311/2653ohms = 117mA. Thus the resistor experiences instantaneous dissipation of 250mW 120 times a second (two peaks per cycle for a 60Hz line). If this fuse keeps blowing for no apparent reason, lower it to 15ohms. Of course this is a poor man's version of a fuse resistor and is suboptimal and may even fail to protect the components downstream. Better than none though. <br />
<br />
R2 and C3 were added as per suggestion of the <a href="http://catalog.osram-os.com/catalogue/catalogue.do?favOid=00000002000246c8001c0023&act=showBookmark">Osram datasheet</a>. When I was bench testing the breadboarded version which I powered using the PICkit2, I found that adding R2 and C3 did boost the performance. Apparently the PICkit2 wasn't supplying clean or sufficient power. Having used the above transformerless power supply design for about a decade in a good number of circuits I know there are regulated voltage and ripple voltage issues with it (because it's just a half-wave supply). And so adding R2 and C3 is mandatory.<br />
<br />
Triac gate trigger is such that current flows from MT1 to gate (rather than from gate to MT1), hence the triac operates in Quadrants 2 and 3--which require much less gate trigger current. For a discussion of triac quadrants and triggering read the section "<a href="http://electromotiveforces.blogspot.com/2011/05/automatic-night-light-circuits.html">Load Switching</a>."<br />
<br />
Given the zener voltage of 5.6V and D1 forward voltage V<sub>F</sub> of 0.7V @20mA (see graphs for <a href="http://www.diodes.com/datasheets/ds28002.pdf">1N400x</a>), our V<sub>DD</sub> = 5.6 - 0.7 = 4.9VDC. According to the <a href="http://www.littelfuse.com/data/en/Data_Sheets/Littelfuse_Thyristor_Lx01Ex_LxNx_Qx01Ex_QxNx.pdf">Teccor datasheet</a> <i>maximum</i> required gate current for quadrants 2 and 3 operation is 10mA while maximum gate voltage V<sub>GT</sub> is 1.3V. Deducting V<sub>GT</sub> from V<sub>DD</sub> we're left with 4.9 - 1.3 = 3.6V. There's also a voltage drop across Q1. According to the graphs in the <a href="http://www.fairchildsemi.com/ds/2N/2N7000.pdf">2N7000</a> specs sheet the drain-to-source resistance R<sub>DS</sub> at a gate-to-source voltage of 5.0V and gate current of 10mA is almost 1.5ohms. Using Ohm's Law the drain-to-source voltage V<sub>DS</sub> = 1.5ohms x 10mA = 15mV. This is negligible, and even if quadrupled V<sub>DS</sub> would still be just around 60mV. So we can disregard the Q1 voltage drop and take 3.6V as the voltage across triac gate resistor R<sub>T</sub>. To obtain 10mA of gate current we apply good ol' Ohm's Law again: 3.6V / 10mA = 360ohms. Closest standard 5% resistor value is either 390 or 330ohms. We pick the lower value to make sure there's enough current to meet the maximum requirement of 10mA. With 330ohms the triac gate current = 3.6 / 330 = 10.9mA. Resistor power dissipation = I<sup>2</sup>R = 0.0109<sup>2</sup>(330) =39mW. Ostensibly this means we can use a 1/8-watt resistor. But the 10mA current we obtained is just a theoretical value. Its true value depends largely on the triac's V<sub>GT</sub> which will probably be much less than the maximum quoted 1.3V. Now supposing that V<sub>GT </sub>= 0 and our 5% tolerance 330-ohm resistor has in fact a value = 330*95% = 313 ohms. Gate current would then be 4.9V / 313ohms = 16mA. Resistor power dissipation would thus be = 0.016<sup>2</sup>313 = 80mW. This is still some 35% less than 1/8W. Therefore, we can in fact use a 1/8-watt resistor.<br />
<br />
Currently the only buttons on the TV remote control which will make the circuit do anything are the keys for digits 1 and 0. Pressing "1" turns load on, while "0" switches it off. Upon power up the load is forced to turn on. In the case of the room light, this means that flipping the wall switch on will turn the light on--as we would want it to. The light can then be turned off via this switch or through the use of the remote control. <br />
<br />
The firmware I have thus far is tailored for the <a href="http://electromotiveforces.blogspot.com/2011/02/philips-rc-5-infrared-protocol-decoding.html">Philips RC-5</a> infrared communications protocol (visit that link for an explanation for the rationale/basis of the signal decoding firmware below). I'm planning to make the circuit respond to both the RC-5 and <a href="http://electromotiveforces.blogspot.com/2009/05/sircs-real-score.html">Sony SIRC</a> protocol, letting the firmware determine which of the protocols it's receiving (and rejecting other protocols) and deciding of course whether the received codes correspond to any of those which it should respond to and take action. As I said above, I want to extend the range of capabilities of this circuit and add stuff like timer features. So it's back to the drawing board deciding which buttons will serve as timer functions and writing the appropriate software. And as has probably already popped into your head, we wouldn't want our lights or whatever load to turn on/off when we intended to change channels on the TV! Such techniques as using two-button codes (e.g. the "Menu" and "1" buttons need to be pressed in rapid succession--i.e., within a prescribed time frame--to turn the load on) or keeping the button pressed for one second or so (e.g., command is received by the circuit, say, 5 to 10 times before action is taken) may be employed. <br />
<br />
<pre>/*
AC LOAD SWITCHING USING A TV REMOTE CONTROL
February 2012
processor = PIC12F615
compiler = mikroC v5.0.0
configuration word = power up timer, brownout reset, and WDT enabled; all else disabled. Internal Oscillator = 4MHz
*/
#define load GPIO.f2 // 2N7000 sinks triac gate current
#define ledg GPIO.f4 // green led -- multifunction status led, not just on indicator
#define ledr GPIO.f1 // red led -- multifunction status led, not just off indicator
#define irr GPIO.f5 // Osram SFH5110-38 infrared receiver
#define tris_irr TRISIO.f5 // for use with setting the appropriate pin as input
#define ioc_irr IOC.f5 // for use with enable interrupt on change for irr
// RC5 decoding defines
#define _full 1
#define _half 0
// one full bit period for RC5 = 1.778ms; one half bit period = 0.889ms
// given clock = 4MHz and TMR0 initial value = 0 and timer0 prescale = 1:16, TMR0 overflows every 256 x 16 / 1MHz = 4.096ms
// 4.096ms / TMR0 count = 4.096 / 256 = 0.016ms per TMR0 count
// 1.778 / 0.016ms = 111 this is the TMR0 count when time elapsed is 1.778ms
#define _fullbit 111 // full bit period of RC5
#define _tolerance 25 // tolerance in percent, to be added/subtracted to/from half and full bit periods to create range of acceptable pulse widths
// !! THIS HAS TO BE INTEGER NOT FLOATING POINT !!
// !! MAXIMUM VALUE = 33%, ELSE halfbit_uplim WILL BE GREATER THAN fullbit_uplim
#define _halfbit _fullbit / 2 // half bit period of RC5
#define halfbit_lolim _halfbit - (_halfbit * _tolerance / 100) // minimum pulse duration for half bit period
#define halfbit_uplim _halfbit + (_halfbit * _tolerance / 100) // maximum pulse duration for half bit period
#define fullbit_lolim _fullbit - (_fullbit * _tolerance / 100) // minimum pulse duration for full bit period
#define fullbit_uplim _fullbit + (_fullbit * _tolerance / 100) // maximum pulse duration for full bit period
#define int1 bit
#define int8 unsigned char
#define int16 unsigned int
#define int32 unsigned long
#define on 1
#define off 0
#define input 1 // for TRISx
#define output 0 // for TRISx
#define analog 1 // for ANSELx
#define digital 0 // for ANSELx
// ===========================================================================================
// Global Variables
// ===========================================================================================
int8 PULSEWIDTH; // contains the measured pulse width (to get the real value in millisec multiply by 16)
int8 RC5BITS; // contains the bit number (excluding the start bit) of the RC5 word currently being received
int8 RC5FCSYS; // contains the RC5 Field bit, Control Bit, and the 5-bit System Address
int8 RC5COMM; // contains the RC5 6-bit Command code
int8 RC5COMMPREV; // contains the previous RC5 6-bit Command code
int8 TEMP; // temporary memory
bit rc5_on; // 1 = irr output has been high for >4millisec and then goes low; this indicates a RC5 start bit
// 0 = irr output has been low for >4millisec and then goes high. This is an abnormal state and is an error
// rc5_on is also reset to 0 when all bits of word have been received
bit halfper_prev; // 1 = the last IOC was a half bit period, 0 = the last IOC occured after a full bit period
bit key_pressed; // 1 = button has been pressed and valid RC5 word stored
bit rc5_read_error; // 1 = read error, pulse width of either a zero or one bit is outside the acceptable limits; the data packet should be discarded
bit bit_period; // _full = 1 = one full bit period, _half = 0 = half bit period
bit curr_bit; // contains the latest decoded value of the bit of the RC5 word being received
bit con_bit_prev; // contains the control bit of the previous decoded RC5 word
// used to monitor if a key has been pressed and if yes is the key being held down or is a new press
// _key_none = no key pressed
// _key_new = new key pressed. It may be the same button but it was released and pressed again, ie., the Control bit has toggled
// _key_same = IR signal from the same key. Button is depressed and has not been released.
enum {_key_none, _key_new, _key_same} KEY = _key_none;
// ===========================================================================================
// Functions
// ===========================================================================================
void IniReg()
{
ANSEL = digital;
TRISIO = output;
GPIO = 0;
tris_irr = input;
OPTION_REG = 0b10000011; // prescaler assigned to timer0
// prescale = 1:16
// timer0 uses internal clock instruction cycle
// weak pull ups disabled
// enable interrupt on change
// intialize pin connnected to ir receiver to interrupt on input change
INTCON.GPIE = on;
ioc_irr = on;
INTCON.GIE = 1;
INTCON.PEIE = 1;
key_pressed = 0;
load = on;
ledg = on;
ledr = off;
} // void IniReg()
// this function determines whether the key pressed is a:
// 1. _key_none = no key press has been detected
// 2. _key_new = a different key has been pressed or the same key has been released and pressed again
// 3. _key_same = the same key is still held down
// If no key is press is detected for > _idle_time then firmware goes into standby mode and display is turned off
void ProcessKey()
{
if (key_pressed)
{
key_pressed = 0;
if ((RC5COMM == RC5COMMPREV) && (RC5FCSYS.f5 == con_bit_prev))
KEY = _key_same;
else // if current key is not the same as previous key or if current control bit not the same as previous
KEY = _key_new;
RC5COMMPREV = RC5COMM;
con_bit_prev = RC5FCSYS.f5;
} // if (key_pressed)
else
KEY = _key_none;
} // void ProcessKey()
void LoadControl()
{
switch (KEY)
{
case _key_new:
switch (RC5COMM)
{
case 0:
load = 0;
ledg = off;
ledr = on;
break;
case 1:
load = 1;
ledg = on;
ledr = off;
break;
default:
break;
} // switch (RC5COMM)
break;
case _key_same:
break;
case _key_none:
break;
} // switch (KEY)
} // void LoadControl()
void interrupt()
{
// interrupt on change
if (INTCON.GPIF)
{
PULSEWIDTH = TMR0;
TMR0 = 0;
TEMP = GPIO; // a read of GPIO is necessary before GPIF can be cleared
INTCON.GPIF = 0;
if (rc5_on && !rc5_read_error)
{
if (PULSEWIDTH >= halfbit_lolim && PULSEWIDTH <= halfbit_uplim)
bit_period = _half;
else if (PULSEWIDTH >= fullbit_lolim && PULSEWIDTH <= fullbit_uplim)
bit_period = _full;
else
rc5_read_error = 1;
if (bit_period == _half && halfper_prev == 0)
{
halfper_prev = 1; // last IOC occured after a full bit period, so this current half bit period needs to be paird with the half bit period
}
else // if (bit_period == _full || halfper_prev == 1)
{
halfper_prev = 0;
if (irr) // rising edge, indicates logic zero
curr_bit = 0;
else // falling edge, indicates logic one
curr_bit = 1;
if (RC5BITS <= 6)
{
RC5FCSYS <<= 1;
RC5FCSYS.f0 = curr_bit;
}
else
{
RC5COMM <<= 1;
RC5COMM.f0 = curr_bit;
}
if (++RC5BITS >=13)
{
rc5_on = 0;
key_pressed = 1;
}
} // else
} // if (rc5_on && !rc5_read_error)
// if timer0 interrupt flag is set then ir_rx output was low/high for >4ms before the IOC that just occurred
if (INTCON.T0IF)
{
INTCON.T0IF = 0;
RC5BITS = 0;
RC5FCSYS = 0;
RC5COMM = 0;
rc5_read_error = 0;
rc5_on = 0;
if (!irr) // falling edge; pulse was high for >4ms so this is considered the start bit of RC5 word
rc5_on = 1;
} // if (INTCON.T0IF)
} // if (INTCON.GPIF)
} // void interrupt()
void main()
{
IniReg();
while(1)
{
asm{clrwdt}
ProcessKey();
LoadControl();
}
}
</pre><br />
<br />
Decided to print a dual board artwork--just copied and pasted the original pcb layout. Each board is 2x2". Since the effort and time that goes into developing and etching one presensitized panel is practically the same whether it's 1x1" or 4x6" might as well make a dual board. Going for four is too much, specially since this is a prototype. I scored and snapped the panel into two boards after drilling all the holes. <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyobYa9ysQoOmGAlwRp16dTb7uvx2MMUQaytMOjpzlK0SAd9cemUDMbVXoe7T85Ku9pTD_GsU8daLHyIX6nP-i2OMyvUvUCfXsiNAtPCrPkyDzXNKwHqVj1chOlIZNguPUZ2ULxqDOrFw/s1600/IR-remote-AC-load-switch-board.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyobYa9ysQoOmGAlwRp16dTb7uvx2MMUQaytMOjpzlK0SAd9cemUDMbVXoe7T85Ku9pTD_GsU8daLHyIX6nP-i2OMyvUvUCfXsiNAtPCrPkyDzXNKwHqVj1chOlIZNguPUZ2ULxqDOrFw/s320/IR-remote-AC-load-switch-board.png" width="320" /></a></div><br />
The following pcb board has been tested to work. I used a 23-watt compact fluorescent lamp as the load. Unfortunately, it doesn't perform as flawlessly as the breadboarded version. Commands sent to the board are not always picked up properly and so the load doesn't get switched immediately upon key press. I suspect a power supply issue. I'm going to use a 220VAC-to-220VAC isolation transformer to power the board and probe the circuit with a DMM. <br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9lR81Xgv1RK3xowCROOxY5FTGWPY9fJNHunhyphenhyphenlI5Q5hyphenhyphenrZlz7XFKFLysFho0gCzR5rwoYNINdfJ87ZdVAFH6LwfT_tx1ENlGBBE16_hhC13ybZuRtiTRpYcYt5z2ByO5BUC6khneLH0I/s1600/IR-remote-soldered-board1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh9lR81Xgv1RK3xowCROOxY5FTGWPY9fJNHunhyphenhyphenlI5Q5hyphenhyphenrZlz7XFKFLysFho0gCzR5rwoYNINdfJ87ZdVAFH6LwfT_tx1ENlGBBE16_hhC13ybZuRtiTRpYcYt5z2ByO5BUC6khneLH0I/s320/IR-remote-soldered-board1.png" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgixOqJtUBJs1N0v5OTISeHytrIC0Qd3rAOCrF4aaOouvAJEnfFXL0V3gxYMySoXVOI8FwFkNy4UH1mSs8gAD1hgdWxIKe7aQL4thGtUAyaYNGwiZUiWk-E3y1rKE_3HUfMpeAn4B4YiK0/s1600/IR-remote-soldered-board2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgixOqJtUBJs1N0v5OTISeHytrIC0Qd3rAOCrF4aaOouvAJEnfFXL0V3gxYMySoXVOI8FwFkNy4UH1mSs8gAD1hgdWxIKe7aQL4thGtUAyaYNGwiZUiWk-E3y1rKE_3HUfMpeAn4B4YiK0/s320/IR-remote-soldered-board2.png" width="320" /></a></div><br />
You will notice that the LEDs are pointing up while the adjacent infrared receiver is facing 90 degrees away. I'll bend either the sensor or the LEDs to make them face the same way after I've decided which way this board is going to be mounted and where the IR receiver needs to point.erebusnyxhttp://www.blogger.com/profile/11988464368427565221noreply@blogger.com2