Wednesday, March 8, 2017

NodeMCU and Analog Sensors: Hardware Diagnostics

This post was originally going to be devoted to how to use an analog temperature sensor with the NodeMCU ESP8266. We will indeed accomplish that goal, but we will first have to take a detour through basic hardware diagnostics!

There are a wide variety of temperature sensors we can use with the NodeMCU; here's three of them:

  • TMP36
  • LM60
  • BME280

The TMP36 and the LM60 are analog sensors, which means they are sensors that, from the point of view of the ESP8266, return data in the form of a continuous value, a voltage.

Of the three, the BME280 returned the most accurate temperature (accuracy determined by my apartment's thermostat). In addition, it also returns barometric pressure and humidity. The BME280 is not an analog sensor, however.

The TMP36 returned fluctuating temperatures, and those temperatures were low.

The LM60's values were far more steady than those returned by the TMP36, but the values were again too low.

We will be using the LM60 for this tutorial. This sensor is made by Texas Instruments, and is repackaged by various vendors, for example MCM Electronics, where it is given a different name: MC16-0362. The part has three pins, VDD for positive power supply, GND for ground, and OUT which is our signal pin.

The ESP8266 has only one analog input pin, called A0. This means that we must connect the signal pin to A0, and the other pins are connected as shown in this diagram:

After reading Texas Instrument's documentation on the LM60, we learn the following:

  1. The LM60 is rated for a temperature range of -40°C to +125°C
  2. It accepts voltage in the range 2.7 V to 10 V
  3. The output voltage is 174 mV at -40°C and 1205 mV at 125°C
  4. The output voltage is linearly proportional to the Celsius temperature
  5. This proportionality is 6.25 mV per degree Celsius - that's the slope of this line
  6. There is an offset of 424 mV - that's the y-intercept

Thus, the LM60 can be powered from the NodeMCU's 3V3 pin, and the return voltage is less than maximum analog-in value of 3.3 V - great!

The documentation also includes the following diagram, which illustrates the linear relationship and values listed above:

The equation describing this line is right on that diagram:

V = 6.25 * T + 424

where:

V is measured in millivolts
T is measured in degrees Celsius

Solving this equation for T gives us:

T = (V - 424)/6.25

Now we need to read the number of millivolts being sent to A0. The A0 pin takes a voltage between 0 and 3.3 V and converts it to the range 0 to 1024, which we'll call the "raw value". A0 is said to have a 10-bit resolution, and 2^10 = 1024. We will assume this is a linear relationship. Thus, to get the value in volts, we multiply by 3.3 and divide by 1024. So:

volts = rawValue * 3.3 / 1024

We combine all this into the following program:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/*
 * LM60TemperatureSensor
 * 
 * By: Mike Klepper
 * Date: 8 March 2017
 * 
 * This program measures ambient temperature using the LM60 analog sensor;
 * it displays the results in the Serial Monitor.
 * 
 * As we'll see in this tutorial, it returns incorrect temperatures!
 */

const int ADC_PIN = A0;
const float ANALOG_TO_VOLTAGE_FACTOR = 3.3/1024.0; //0.00322265625

int rawValue;
float voltage;
float milliVolts;
float tempC;
float tempF;

void setup() 
{
  Serial.begin(115200);
}

void loop() 
{
  rawValue = analogRead(ADC_PIN);
  voltage = rawValue * ANALOG_TO_VOLTAGE_FACTOR;
  milliVolts = 1000.0 * voltage;
  tempC = (milliVolts - 424.0) / 6.25;
  tempF = 9.0 * tempC / 5.0  + 32.0;
  
  Serial.print("raw value: ");
  Serial.print(rawValue);
  Serial.print("  voltage: ");
  Serial.print(voltage);
  Serial.print("  deg C: ");
  Serial.print(tempC);
  Serial.print("  deg F: ");
  Serial.println(tempF);
  
  delay(2000);
  
  yield();
}

Here's the output in the serial monitor:

According to this, the temperature is 57.46°F. According to my thermostat, the temperature is 68°F. What is the source of discrepancy? Here are some theories:

  1. The temperature of the LM60 component itself was changed by an external source
  2. The thermostat in my apartment is wrong
  3. This particular LM60 is defective
  4. The temperature returned by the LM60 varies based on input voltage
  5. There is a problem with the NodeMCU's ADC pin
  6. There is a calibration error in the LM60

Anything that we would normally do to the LM60 component would RAISE the returned temperature - for example, we had to touch it to attach it to the breadboard. To avoid the effects of our own body heat on the sensor, we avoid touching it for a few minutes and wait for the value to stabilize.

I have tried another thermometer, and it is returning 68.0°F. So the problem is not with the thermostat.

Switching to a different LM60 gives the same temperature reading.

According to TI's documentation, the value returned by the LM60 is not determined by input voltage, so long as the voltage is in the range of 2.7 V to 10 V.

Let's investigate the theory that there is a problem with the NodeMCU's ADC pin. To to this, we have to take out a multimeter - in other words, shit is gettin' real!

Connecting the multimeter between the GND and 3V3 pin, there is indeed 3.3 V going across those pins. When we measure voltage between the GND and A0 pins, we see that it is 0.53 V even though serial output us showing 0.51 V - a discrepancy!

To investigate further, disconnect the LM60 and connect a 10 kΩ potentiometer as follows:

We use the same program as above and focus on the raw value and the voltage that is reported. Turning the potentiometer's knob all the way to the left, we get a raw value of 0 and a voltage of 0.00. Connecting the multimeter to A0 and GND gives us the same voltage. Turning the knob all the way to the right gives us a raw value between 998 and 1001 and voltages of 3.22 V and 3.23 V, respectively. The multimeter shows 3.29 V.

This means that the A0 pin doesn't work as expected - the conversion from the raw value to the voltage is incorrect! As it goes, this is a known bug in the NodeMCU Arduino software.

While the potentiometer is attached, we take additional readings to ensure that the relationship between actual voltage and raw value is linear. Plotting these using an Excel spreadsheet shows that the relationship is indeed linear:

To compensate for this bug, we need only change the value of ANALOG_TO_VOLTAGE_FACTOR from 3.3 / 1024 to 3.29 / 1001. After making this change, the reported temperature is now approximately 59.45°F. Closer to the expected value, but still low.

Finally, let us check for a calibration error in the LM60. An easy way to do this is to put some ice cubes in a plastic bag and hold them against the LM60. The reported temperature is 25.37°F or 24.32°F instead of the expected 32°F.

The results of these experiments are as follows:

Actual Voltage V Actual Temperature °F Actual Temperature °C
0.52 68 20
0.40 32 0

So, assume that the voltage returned by the LM60 is indeed in linear proportion to the temperature, those two data points can be used to calculate the equation of that line.

The slope, m, is (20 - 0)/(0.52 - 0.40) = 20/0.12 = 166.67 °C / volt

Using the point-slope form of the line:

y - y1 = m(x - x1)
where (x1, y1) is (0.52, 20) we get
y - 20 = 166.7*(x - 0.52)
y = 166.7*(x - 0.52) + 20
In terms of the variables in our program, that equation becomes:
tempC = 166.7*(voltage - 0.52) + 20;
The final version of our program is as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*
 * LM60TemperatureSensor
 * 
 * By: Mike Klepper
 * Date: 8 March 2017
 * 
 * This program measures ambient temperature using the LM60 analog sensor;
 * it displays the results in the Serial Monitor.
 * 
 * The program includes corrections for a problem with the NodeMCU's AO pin
 * as well as a calibration problem with the LM60 itself.
 */

const int ADC_PIN = A0;
const float ANALOG_TO_VOLTAGE_FACTOR = 3.29/1001.0; //0.00322265625

int rawValue;
float voltage;
float tempC;
float tempF;

void setup() 
{
  Serial.begin(115200);
}

void loop() 
{
  rawValue = analogRead(ADC_PIN);
  voltage = rawValue * ANALOG_TO_VOLTAGE_FACTOR;
  tempC = 166.7 * (voltage - 0.52) + 20;
  tempF = 9.0 * tempC/5.0  + 32.0;

  Serial.print("raw value: ");
  Serial.print(rawValue);
  Serial.print("  voltage: ");
  Serial.print(voltage);
  Serial.print("  deg C: ");
  Serial.print(tempC);
  Serial.print("  deg F: ");
  Serial.println(tempF);

  delay(2000);

  yield();
}

Here's the output shown in the serial monitor:

The reported temperature is now 67.79°F - not exactly 68.0°F like the thermometer reports, but certainly much closer!

What accounts for this final discrepancy?

The problem is that it is not possible for this program to return exactly 68.0°F! Why is this? Well, the A0 pin returns integer values. If it reports 158, our program calculates the temperature to be 67.79°F. If A0 reports 159, the program calculates the temperature as 68.78°F. The A0 pin is incapable of returning a value between 158 and 159!

This concludes our first foray into using analog sensors with the NodeMCU ESP8266. We encountered problems with the NodeMCU's analog input pin as well as with the sensor's calibration, and we were able to solve both problems using high school algebra.

No comments:

Post a Comment