Scaling analog values
Most products reading analog values return a 16-bit integer in a limited range defined by the hardware. For example, most Digi products return a value from 0 to 1023 which represents a reading within 0 to 10.25v or 0 to 23.5mA. Yet knowing that your room temperature is 675 binary or 4.56 volts or 17.2mA is not very useful.
You will want to SCALE this value to something more useful like 76.3 degrees Fahrenheit or 297.6 degrees Kelvin.
While this conversion is not difficult, it is complex to envision since you need to fit two distinct line equations to obtain a result. Below is a simple example which does this in two steps. First it converts the raw binary input into a percentage range 0-100% represented as 0.0 to 1.0 appropriate for the sensor (this might go below 0% or above 100%). Second, it converts the range percentage into the actual published sensor value.
An example
The sensor
A Minco model TT321PD1M precision RTD temperature probe with 4-20mA interface defines 4mA as -58.0 DegF and 20mA as 122.0 DegF. So the Digi product is placed into Current Loop mode and returns a value like 17.3mA - how is this converted to DegF?
The first step is to stop thinking of the input as 17.3mA, but to instead think of it as a percentage of the vendor-defined range. In this example the range is from a 0% signal representing -58.0 DegF, while a 100% signal represents 122.0 DegF. The table below shows the theoretical relationship of 4-20mA on a Digi product which converts 0-23.5mA to binary 0-1023.
| mA | percent of 0-23.5mA | percent of 4-20mA | binary in 0 to 1023 | Example DegF |
|---|---|---|---|---|
| 0 mA | 0% | -25% | 0 | -103 DegF |
| 4 mA | 17% | 0% | 174 | -58 DegF |
| 8 mA | 34% | 25% | 348 | -13 DegF |
| 12 mA | 51% | 50% | 522 | +32 DegF |
| 16 mA | 68% | 75% | 697 | +77 DegF |
| 20 mA | 85% | 100% | 871 | +122 DegF |
| 24 mA | 102% | 125% | 1045 | +167 DegF |
Note While this allows defining temperatures 'out-of-range' such as -103 DegF at 0mA, users should never make active use of sensor data when outside of their published range, which in this example is 4-20mA and -58.0 to +122 DegF.
Example 'Terms' for the scaling
It is convenient to think of the conversion as using a five-element tuple of the form (raw_zero, raw_span, user_zero, user_span, units_str), where:
- raw_zero is the binary input which is declared 0.00 percent of range
- raw_full is the binary input which is declared 100.00 percent of range (is not in the tuple)
- raw_span is the binary delta (the 'span') between raw_zero and raw_full, so (raw_full - raw_zero)
- user_zero is the user-desired unit-of-measure to return at 0.00 percent of range
- user_full is the user-desired unit-of-measure to return at 100.00 percent of range (is not in the tuple)
- user_span is the delta (the 'span') between user_zero and user_full, so (user_full - user_zero)
- units_str is the string for the user-desired unit-of-measure
Some example term tuples used in the code sample below:
(0, (1023-0), 0, (10.25-0), 'V')
Binary 0 is called 0% while binary 1023 is called 100%. 0-100% is returned as 0.0V to 10.25V
(0, (1023-0), 0, (23.5-0), 'mA')
Binary 0 is called 0% while binary 1023 is called 100%. 0-100% is returned as 0.0mA to 23.5mA
(174, (871-174), 0, (100-0), 'prc')
Binary 174 is called 0% while binary 871 is called 100%. 0-100% is returned as 0prc to 100prc
(174, (871-174), -58.0, (122.0-(-58.0)), 'DegF')
Binary 174 is called 0% while binary 871 is called 100%. 0-100% is returned as -58.0DegF to 122.0DegF
Implementation Note: It may be easier to allow customers to configure the 'zero' and 'full' values, then calculate the 'span' during start-up. However, recalculating the 'span' as '(full-zero)' every processing cycle is a waste of CPU resources, so retain the calculated span for reuse. The Python code sample included here assumes the 2nd and 4th terms are the span, not the full.
The Python scale code
def sample():
# Note: the 'get_raw_analog(x)' call is imaginary
# Substitute in the call required to obtain a raw binary number
# Report Analog One as 0-10.25v
trm_1 = (0, 1023, 0, 10.25, 'V')
ain_1 = scale_value(get_raw_analog(0), trm_1)
print 'Analog 1 was %0.2f %s' % (ain_1, trm1[4])
# Report Analog Two as 0-23.5mA
trm_2 = (0, 1023, 0, 23.5, 'mA')
ain_2 = scale_value(get_raw_analog(1), trm_2)
print 'Analog 2 was %0.2f %s' % (ain_2, trm2[4])
# Report Analog Three as 0-100% for 4-20mA (might be < 0% or > 100%)
# Note that we shift the expected raw range away from 0-1023 to 174-871
trm_3 = (174, (871-174), 0, 100, 'prc')
ain_3 = scale_value(get_raw_analog(2), trm_3)
print 'Analog 3 was %0.2f %s' % (ain_3, trm3[4])
# Put it all togather
# Report Analog Four as DegF for the example RTD 4-20mA sensor
trm_4 = (174, (871-174), -58.0, (122.0-(-58.0)), 'DegF')
ain_4 = scale_value(get_raw_analog(3), trm_4)
print 'Analog 4 was %0.2f %s' % (ain_4, trm4[4])
return
def scale_value(raw_in, terms):
"""Scale the raw_in value (assumed raw int) to user-units float
terms = (raw_zero, raw_span, user_zero, user_span, units_str)"""
# msg = ' >> terms = %s' % terms
scaled = None
if len(terms) > 2 and terms[1] != 0:
# first we calculate 0-100% as 0.0 to 1.0
# so y = (raw_in - raw_zero) / raw_span
scaled = float(raw_in - terms[0]) / terms[1]
# msg += ' prc=%0.4f' % scaled
# second we optionally change to user units
if len(terms) > 4 and terms[2] is not None:
scaled = terms[2] + (scaled * terms[3])
# msg += ' usr=%0.4f%s' % (scaled,terms[4])
# print msg
return scaled
PDF
