In short: Wire an LM35 analog sensor to an Arduino analog pin (or a DHT22 to a digital pin), convert the reading with the right formula, apply a one- or two-point calibration, and stream degrees Celsius to the Serial Monitor in under 20 lines of code.
Measuring temperature is the "hello world" of embedded sensing, yet doing it correctly teaches almost every concept you will use in real instrumentation: analog-to-digital conversion, reference voltages, quantization error, calibration, and digital bus protocols. This tutorial walks through two of the most common sensors — the analog LM35 and the digital DHT22/DHT11 — with real wiring, honest accuracy numbers, and serial-output code you can paste directly into the Arduino IDE.
Choosing a sensor: analog vs. digital
The LM35 is a linear analog device: its output voltage rises by exactly 10 mV per °C, so 25 °C produces 250 mV. There is no library and no protocol — you simply read a voltage. The DHT22 (and cheaper DHT11) are digital sensors that return calibrated temperature and humidity over a single-wire proprietary bus, but at a slower sample rate (once every 2 s for the DHT22).
| Property | LM35 | DHT22 (AM2302) | DHT11 |
|---|---|---|---|
| Output | Analog 10 mV/°C | 1-wire digital | 1-wire digital |
| Range | −55 to 150 °C | −40 to 80 °C | 0 to 50 °C |
| Accuracy | ±0.5 °C | ±0.5 °C | ±2 °C |
| Humidity | No | Yes, ±2% RH | Yes, ±5% RH |
| Max sample rate | limited by ADC | 0.5 Hz | 1 Hz |
A standard Uno has a 10-bit ADC (0–1023 counts) across a 5 V reference. That is 4.89 mV per count, which equals roughly 0.49 °C of resolution for the LM35 — you can never resolve finer than that without a better reference.
Wiring the LM35
The LM35 in a TO-92 package has three pins, flat face toward you: left is +Vs (to 5 V), middle is Vout (to analog pin A0), right is GND. Keep the signal wire short, and if you see noise add a 100 nF capacitor between Vout and GND close to the sensor.
Reading the LM35 in code
The conversion chain is: ADC count → voltage → temperature. With the default 5 V reference, each count is 5000 mV / 1024, and dividing by the 10 mV/°C slope gives temperature directly.
const int PIN = A0;
void setup(){ Serial.begin(9600); }
void loop(){
int raw = analogRead(PIN); // 0..1023
float mV = raw * (5000.0 / 1024.0); // millivolts
float tempC = mV / 10.0; // 10 mV per degC
Serial.print("Temp: ");
Serial.print(tempC, 2);
Serial.println(" C");
delay(1000);
}
Switch to analogReference(INTERNAL) on an Uno to use the 1.1 V band-gap reference. Resolution improves to ~1.07 mV/count (about 0.1 °C), but the ceiling drops to ~110 °C. Change the 5000.0 to 1100.0 in the math.
Calibration: why your reading is off
Factory tolerance, a noisy 5 V rail, and self-heating all bias the result. Professional labs characterize sensors against a traceable reference — the same philosophy behind device modeling in tools like Synopsys HSPICE or Cadence Spectre, where a model is only as good as the measured data it is fit to. For a hobby build, two practical methods work:
- One-point offset: place the sensor in a known environment (ice-water bath at 0 °C, stirred). Record the error and subtract it:
tempC = tempC - offset;. - Two-point (gain + offset): measure at two known temperatures (0 °C and 100 °C boiling, altitude-corrected). Fit a straight line
true = m*measured + band apply it. This corrects both slope and offset error.
Always average many samples — summing 16 analogRead calls and dividing cuts random ADC noise by a factor of four, since noise falls as the square root of the sample count.
The animated ADC: watching a reading settle
Using the DHT22 instead
For humidity plus temperature with on-chip calibration, the DHT22 is simpler to trust. Install Adafruit's DHT sensor library, add a 10 kΩ pull-up resistor between the data pin and 5 V, and read every 2 seconds:
#include "DHT.h"
#define DHTPIN 2
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
void setup(){ Serial.begin(9600); dht.begin(); }
void loop(){
float t = dht.readTemperature(); // Celsius
float h = dht.readHumidity();
if (isnan(t) || isnan(h)) { Serial.println("read failed"); }
else { Serial.print(t); Serial.print(" C ");
Serial.print(h); Serial.println(" %RH"); }
delay(2000);
}
The isnan() guard matters: the single-wire protocol has no error correction beyond a checksum, so a failed read returns NaN rather than crashing. This same defensive pattern scales to any sensor network — a lesson our full embedded curriculum reinforces across every build. If you want to combine this reading with environmental logging, pair it with the soil-moisture sensor tutorial or step up to the ADC noise-reduction guide for lab-grade stability. Browse the full track on our courses page.
Teams building production sensor boards — not breadboards — take this further with proper signal conditioning, ESD protection, and PCB layout, the kind of work done by hardware design houses such as Avecas VLSI and board design services.
Ready to build real instruments? The CourseTron Embedded Systems with Arduino course takes you from first blink to calibrated, interrupt-driven data loggers. Enroll today.
Frequently Asked Questions
Why does my LM35 read a few degrees too high?
Self-heating and a noisy supply rail are the usual causes. The LM35 draws tiny current but poor layout and a floating ground add offset. Apply a one-point ice-bath calibration and average 16+ samples.
Can I use the LM35 with a 3.3 V board?
Yes, but change the conversion constant to 3300.0/1024.0 and remember the ADC on many 3.3 V boards is 12-bit (0–4095), so divide by 4096 instead.
Why is my DHT22 reading only every two seconds?
The sensor's internal sampling cycle is fixed at roughly 2 seconds; polling faster just returns the last cached value or a failed read.
How do I get better than 0.5 °C resolution?
Switch to the 1.1 V internal reference, use oversampling and averaging, or move to an I2C sensor like the TMP117 (±0.1 °C) with a 16-bit ADC.