Another 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 is a once-in-a-lifetime event, you know.
Camera used was an el cheapo Canon Powershot A480 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.
Monday, June 18, 2012
Sunday, June 17, 2012
Braking distance and time on level road and on an incline
Off 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.
Let
vi = initial velocity, m/s
vf = final velocity, m/s
v = average velocity, m/s
a = deceleration rate, m/s2
t = time to stop the vehicle, s
d = braking distance, m
μ = coefficient of friction, unitless
Ff = force of friction, kg·m/s2
Fn = normal force (force perpendicular to surface), kg·m/s2
Fg = force due gravity pulling the vehicle down a slope, kg·m/s2
m = mass, kg
g = gravitational acceleration, 9.8 m/s2
θ = angle of inclination (positive if vehicle going uphill, negative if going downhill)
Final velocity is given by the equation:

If vf = 0 then

Distance is given by:

Average velocity is:

Given vf = 0 and substituting we have:

Alternatively we can derive the distance formula using kinetic energy and work equations:



Equating work and energy we have:

Solving for d we obtain:

The force of friction is given by the following formula:

If the vehicle is on a level surface then normal force is just its weight. Therefore,

Since

then

Therefore,

Substituting the above into the distance and time equations we get:


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.
Now let's tackle the situation when the vehicle is braking on a slope.
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 Fg and is derived via basic vector analysis. Refer to the free-body diagram. On the left of the diagram is an object on an incline with an angle of θ. Its weight is labeled as mg. Normal force Fn is the component of mg that is perpendicular to the incline. Fg 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:


An aside: Since the above are the legs of the right triangle it follows from the Pythagorean theorem that

When on a slope with the brakes engaged so that the vehicle is at a standstill Ff is the force that keeps the vehicle from slipping and sliding down and acts in the opposite direction as Fg which is pulling it down the incline. The net force holding the vehicle in place is therefore Ff - Fg. It stands to reason that when Fg becomes >= Ff 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:





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.
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 Fg will be negative. When going uphill part of gravitational force adds to the force of friction thereby decreasing braking distance.
The sum of forces acting on the vehicle parallel to its direction of motion is given by:

Since

Therefore,


Since



Substituting into our distance and time equations we obtain:


The above are exact equations in contrast to the approximate formula using road grade. The gradient is equal to the rise over run, or tangent of θ. The arctangent of the gradient = angle θ.
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 Fg is effectively subtracted from Ff. When going uphill and θ is positive then Ff is effectively increased by amount Fg. 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.
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 around half a second 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.
The distance traveled by the vehicle during this time is simply

Here are a couple of online braking distance calculators which don't, however, have provisions for vehicles on an incline:
http://www.csgnetwork.com/stopdistcalc.html
http://forensicdynamics.com/stopping-distance-calculator
Tables of coefficients of friction:
http://www.engineeringtoolbox.com/friction-coefficients-d_778.html
http://www.roymech.co.uk/Useful_Tables/Tribology/co_of_frict.htm
Summary of the relevant data from the above tables:
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 some slip occurs between tire and road. 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.
The take home lessons from the above formulas:
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)2 = 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.
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.
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 two-second rule. Although as weather and road conditions worsen this should be increased to three or four seconds.
Let
vi = initial velocity, m/s
vf = final velocity, m/s
v = average velocity, m/s
a = deceleration rate, m/s2
t = time to stop the vehicle, s
d = braking distance, m
μ = coefficient of friction, unitless
Ff = force of friction, kg·m/s2
Fn = normal force (force perpendicular to surface), kg·m/s2
Fg = force due gravity pulling the vehicle down a slope, kg·m/s2
m = mass, kg
g = gravitational acceleration, 9.8 m/s2
θ = angle of inclination (positive if vehicle going uphill, negative if going downhill)
Final velocity is given by the equation:
If vf = 0 then
Distance is given by:
Average velocity is:
Given vf = 0 and substituting we have:
Alternatively we can derive the distance formula using kinetic energy and work equations:
Equating work and energy we have:
Solving for d we obtain:
The force of friction is given by the following formula:
If the vehicle is on a level surface then normal force is just its weight. Therefore,
Since
then
Therefore,
Substituting the above into the distance and time equations we get:
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.
Now let's tackle the situation when the vehicle is braking on a slope.
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 Fg and is derived via basic vector analysis. Refer to the free-body diagram. On the left of the diagram is an object on an incline with an angle of θ. Its weight is labeled as mg. Normal force Fn is the component of mg that is perpendicular to the incline. Fg 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:
An aside: Since the above are the legs of the right triangle it follows from the Pythagorean theorem that
When on a slope with the brakes engaged so that the vehicle is at a standstill Ff is the force that keeps the vehicle from slipping and sliding down and acts in the opposite direction as Fg which is pulling it down the incline. The net force holding the vehicle in place is therefore Ff - Fg. It stands to reason that when Fg becomes >= Ff 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:
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.
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 Fg will be negative. When going uphill part of gravitational force adds to the force of friction thereby decreasing braking distance.
The sum of forces acting on the vehicle parallel to its direction of motion is given by:
Since
Therefore,
Since
Substituting into our distance and time equations we obtain:
The above are exact equations in contrast to the approximate formula using road grade. The gradient is equal to the rise over run, or tangent of θ. The arctangent of the gradient = angle θ.
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 Fg is effectively subtracted from Ff. When going uphill and θ is positive then Ff is effectively increased by amount Fg. 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.
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 around half a second 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.
The distance traveled by the vehicle during this time is simply
Here are a couple of online braking distance calculators which don't, however, have provisions for vehicles on an incline:
http://www.csgnetwork.com/stopdistcalc.html
http://forensicdynamics.com/stopping-distance-calculator
Tables of coefficients of friction:
http://www.engineeringtoolbox.com/friction-coefficients-d_778.html
http://www.roymech.co.uk/Useful_Tables/Tribology/co_of_frict.htm
Summary of the relevant data from the above tables:
| tire material | road type and condition | kinetic coefficient of friction |
|---|---|---|
| rubber | dry concrete | 0.6 - 0.85 |
| rubber | wet concrete | 0.45 - 0.75 |
| rubber | dry asphalt | 0.5 - 0.8 |
| rubber | wet asphalt | 0.25 - 0.75 |
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 some slip occurs between tire and road. 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.
The take home lessons from the above formulas:
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)2 = 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.
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.
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 two-second rule. Although as weather and road conditions worsen this should be increased to three or four seconds.
Wednesday, June 13, 2012
Detecting liquified petroleum gas (LPG)
Figaro
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 LPM2610 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.
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.
As per datasheet the dark blue bands on the trimpot should be lined up to ensure it's in the calibrated position.
The Figaro module contains the TGS2610 sensor and various resistors to create two voltage dividers--one for the sensor output and the other for the reference voltage. Whenever sensor voltage VOUT 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 one website puts it at 16,000 ppm or 1.6%).
In Application Note for LP Gas Detectors using TGS2610 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.
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 VDD 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. VOUT and VREF were unity-gain buffered while the current sense voltage was gained by 20. Output of the op amps were hooked up to a Dataq DI-145 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).
In the screenshot below you can see that while the heater is ramping up to its working temperature VOUT gradually settles down (approx 0.3 to 0.4V). This takes >100sec.
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.
Channel 1: VOUT
Ch2: VREF
Ch3: voltage across 2-ohm current sense resistor amplified 20x
Ch4: unused
Vertical: 0.5 volts/div
Horizontal: 5 sec/div
Data acquisition rate: 16 samples/sec
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.
What's interesting in the image below is how VOUT 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 (RL), apparently the semiconductor sensor (RS) which is in series with RL 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 short intro to the construction and operating principles of its sensors
Channel 1: VOUT
Ch2: VREF
Ch3: voltage across 2ohm current sense resistor and then amplified by a nominal gain of 20
Ch4: VDD
Vertical: 0.491 volts/div for Channels 1 to 3, 0.231 V/div for Ch4
Horizontal: 0.33 sec/div
Data acquisition rate: 240 samples/sec
Schematic of a fully working LPG detector:
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 PIC12F615 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.
I've routed VOUT to one of the MCU's comparator inverting input pin (CIN0- / AN1) and VREF 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.
To make sure the firmware works I initially used simple voltage dividers to emulate the outputs of the Figaro module:
Heater failure was simulated by simply disconnecting the 91-ohm resistor from VDD . Potentiometer permits testing the full range of VOUT from 0 to VDD.
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 VOUT 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.
One other revelation of note. LPG is usually a mix of butane and propane. At room temperature butane has a density of ~2.5 kg/m3, 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.
To check the integrity of the sensor the value of VOUT (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.
If heater and sensor are both ok (but not if either is faulty), then VOUT is compared to VREF. If VOUT is > VREF 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.
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:
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
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.
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.
As per datasheet the dark blue bands on the trimpot should be lined up to ensure it's in the calibrated position.
The Figaro module contains the TGS2610 sensor and various resistors to create two voltage dividers--one for the sensor output and the other for the reference voltage. Whenever sensor voltage VOUT 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 one website puts it at 16,000 ppm or 1.6%).
In Application Note for LP Gas Detectors using TGS2610 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.
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 VDD 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. VOUT and VREF were unity-gain buffered while the current sense voltage was gained by 20. Output of the op amps were hooked up to a Dataq DI-145 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).
In the screenshot below you can see that while the heater is ramping up to its working temperature VOUT gradually settles down (approx 0.3 to 0.4V). This takes >100sec.
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.
Channel 1: VOUT
Ch2: VREF
Ch3: voltage across 2-ohm current sense resistor amplified 20x
Ch4: unused
Vertical: 0.5 volts/div
Horizontal: 5 sec/div
Data acquisition rate: 16 samples/sec
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.
What's interesting in the image below is how VOUT 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 (RL), apparently the semiconductor sensor (RS) which is in series with RL 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 short intro to the construction and operating principles of its sensors
Channel 1: VOUT
Ch2: VREF
Ch3: voltage across 2ohm current sense resistor and then amplified by a nominal gain of 20
Ch4: VDD
Vertical: 0.491 volts/div for Channels 1 to 3, 0.231 V/div for Ch4
Horizontal: 0.33 sec/div
Data acquisition rate: 240 samples/sec
Complete Working Circuit
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 (MCP609) I decided to make use of what would be an unused device and connected VOUT 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 VDD. However, when the sensor does fail op amp output will be much closer to ground.Schematic of a fully working LPG detector:
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 PIC12F615 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.
I've routed VOUT to one of the MCU's comparator inverting input pin (CIN0- / AN1) and VREF 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.
To make sure the firmware works I initially used simple voltage dividers to emulate the outputs of the Figaro module:
Heater failure was simulated by simply disconnecting the 91-ohm resistor from VDD . Potentiometer permits testing the full range of VOUT from 0 to VDD.
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 VOUT 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.
One other revelation of note. LPG is usually a mix of butane and propane. At room temperature butane has a density of ~2.5 kg/m3, 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.
Firmware
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.To check the integrity of the sensor the value of VOUT (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.
If heater and sensor are both ok (but not if either is faulty), then VOUT is compared to VREF. If VOUT is > VREF 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.
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:
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
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.
/*
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()
Subscribe to:
Posts (Atom)





















