When selecting an environmental sensors type for an ESP32 or Arduino project, the choice between analog, single-wire digital, and I2C protocols dictates your firmware architecture, power budget, and hardware reliability. The Bosch BME280 wins for high-precision multi-variable logging via I2C, the DHT22 remains the budget single-wire standard for basic HVAC monitoring, and the TI LM35 serves as a legacy analog baseline for pure temperature applications. This guide breaks down the exact wiring, raw-to-physical math, and bench-level interference mitigation required to interface these three distinct sensor architectures with 3.3V microcontrollers.
Core Sensing Principles and Output Signals
The LM35 relies on a solid-state bandgap circuit where the output voltage scales linearly at exactly 10mV/°C. The DHT22 (AM2302) pairs a polymer humidity capacitor with an NTC thermistor, converting physical changes into a 40-bit digital stream via a proprietary single-bus timing protocol. The BME280 integrates a piezoresistive pressure membrane, a capacitive humidity element, and a film thermistor, exposing compensated data through standard I2C or SPI hardware registers.
The fundamental difference in these sensors type architectures is what the output actually is and how the microcontroller consumes it. The LM35 outputs a continuous analog voltage (0V to 1.5V for typical ranges) requiring an Analog-to-Digital Converter (ADC) pin. The DHT22 outputs a 5V-tolerant digital square wave where data is encoded in the pulse widths, demanding microsecond-accurate blocking delays from the CPU. The BME280 outputs structured digital bytes over an I2C bus, offloading all timing and bit-banging to the microcontroller's dedicated hardware peripheral.
Wiring and Pinout Matrix
Before writing any code, you must match the sensor's voltage domain to your microcontroller. A common beginner mistake is wiring a 5V analog sensor directly to a 3.3V ESP32 ADC pin without checking the maximum output swing, or relying on the ESP32's internal I2C pull-ups, which are far too weak for reliable communication.
| Sensor Module | Protocol | VCC Supply Range | ESP32 Data Pin | External Pull-up Needed? | Output Signal Type |
|---|---|---|---|---|---|
| TI LM35 | Analog | 4.0V – 30V | GPIO 34 (ADC1_CH6) | No | Linear Voltage (10mV/°C) |
| Aosong DHT22 | Single-Wire | 3.3V – 5.5V | GPIO 4 (Digital) | Yes (4.7kΩ to VCC) | 40-bit Timed Bitstream |
| Bosch BME280 | I2C / SPI | 1.71V – 3.6V | SDA (21) / SCL (22) | Yes (4.7kΩ to 3.3V) | Compensated I2C Registers |
| Bosch BME688 | I2C / SPI | 1.71V – 3.6V | SDA (21) / SCL (22) | Yes (4.7kΩ to 3.3V) | I2C + Gas Resistance |
VIN or 5V pin. However, at 150°C, its output is 1.5V, which is safely within the ESP32's 3.3V ADC limit. Do not use it for measurements exceeding 330°C on a 3.3V logic board without a voltage divider.
Raw-to-Unit Math and Calibration Scaling
Reading the raw data is only the first step; converting it to a physical unit requires understanding the sensor's internal scaling and your microcontroller's ADC characteristics. Here is the exact math for each sensors type.
LM35: Analog ADC Scaling on ESP32
The LM35 requires no factory calibration, but the ESP32's 12-bit SAR ADC requires software scaling. The ESP32 ADC is notoriously non-linear at the extreme bottom (0–100mV) and top of its range. Assuming an 11dB attenuation setting (which maps ~0–3.1V to the 0–4095 raw range):
// ESP32 Arduino Core ADC Math
int raw_adc = analogRead(34);
float voltage = (raw_adc * 3.1) / 4095.0; // 3.1V is the practical max at 11dB attenuation
float temp_c = voltage * 100.0; // 10mV per degree Celsius
Calibration needed: For bench-grade accuracy, you must perform a two-point calibration using an ice bath (0°C) and boiling water (100°C adjusted for altitude) to map the ESP32's specific ADC non-linearity, or use the esp_adc_cal library to apply the chip's factory-stored eFuse calibration values.
DHT22: Single-Wire Bit Decoding
The DHT22 outputs a 40-bit stream: 16 bits for relative humidity, 16 bits for temperature, and an 8-bit checksum. The raw temperature value is in tenths of a degree, and the most significant bit (MSB) of the temperature word indicates a negative value.
// Conceptual Raw Decoding (Usually handled by Adafruit DHT library)
uint16_t raw_temp = (data[2] << 8) | data[3];
bool is_negative = (raw_temp & 0x8000) != 0;
if (is_negative) raw_temp &= 0x7FFF; // Clear sign bit
float temp_c = raw_temp / 10.0;
if (is_negative) temp_c = -temp_c;
Calibration needed: None in firmware. The sensor's internal MCU handles the thermistor lookup table. However, the DHT22 suffers from a known ±0.5°C offset drift over time in high-condensation environments.
BME280: I2C Register Compensation
The BME280 does not output raw temperature directly. It outputs a 20-bit raw ADC value that must be compensated using factory-programmed trimming parameters stored in the sensor's non-volatile memory. The Bosch BME280 datasheet details a complex floating-point or integer algorithm involving a t_fine variable that cascades into the pressure and humidity calculations.
// Using Adafruit_BME280 library (Abstracts the Bosch compensation math)
#include
Adafruit_BME280 bme;
void setup() {
bme.begin(0x76); // Default I2C address
// Set to 'Forced' mode to prevent self-heating from continuous measurement
bme.setSampling(Adafruit_BME280::MODE_FORCED,
Adafruit_BME280::SAMPLING_X1, // Temp
Adafruit_BME280::SAMPLING_X1, // Pressure
Adafruit_BME280::SAMPLING_X1, // Humidity
Adafruit_BME280::FILTER_OFF);
}
void loop() {
bme.takeForcedMeasurement();
float temp_c = bme.readTemperature(); // Returns fully compensated float
}
Interference Sources and Hardware Mitigation
Every sensors type has specific electromagnetic and architectural vulnerabilities. Ignoring these on the bench leads to phantom readings, watchdog resets, and I2C bus lockups in the field.
LM35: 60Hz Mains Hum and Ground Loops
The Problem: Because the LM35 outputs a low-impedance analog voltage, running unshielded jumper wires longer than 6 inches near AC mains wiring will induce 50/60Hz hum. On an oscilloscope, you will see a 20mV peak-to-peak sine wave riding on the DC signal, causing the ESP32 ADC reading to flutter by ±2°C.
The Fix: Use a twisted-pair cable for the signal and ground. Solder a 100nF ceramic decoupling capacitor directly across the VCC and GND pins at the sensor base. For runs over 1 meter, abandon the LM35 and switch to a digital sensor, or buffer the analog signal with an LM358 op-amp configured as a low-pass filter.
DHT22: RTOS Timing Jitter and Checksum Failures
The Problem: The DHT22 protocol requires the microcontroller to pull the data line low for exactly 1ms, then listen for 20-40µs pulses. On an ESP32 running FreeRTOS with WiFi enabled, background tasks (like the TCP/IP stack or ADC calibration tasks) can cause context switches that stretch these microsecond delays, resulting in "Checksum Failed" or "Timeout" errors.
The Fix: Never read a DHT22 inside a standard loop() without disabling interrupts. Wrap the read function in portMUX_TYPE spinlocks or use noInterrupts() / interrupts() (though the latter is deprecated in ESP-IDF). Better yet, assign the DHT22 read to a dedicated hardware timer interrupt or use an ESP32-S2/S3 with Programmable IO (PIO) to handle the bit-banging in hardware.
BME280: I2C Bus Capacitance and Self-Heating
The Problem: I2C relies on open-drain lines pulled high by resistors. Long wires add parasitic capacitance, slowing the voltage rise time and causing the ESP32 to misread logic HIGHs. Additionally, running the BME280 in continuous mode at 1Hz causes the internal IC to self-heat, artificially raising the temperature reading by 1.5°C to 2.0°C above ambient.
The Fix: Keep I2C traces under 30cm. If you must use longer cables, drop the I2C clock speed from 400kHz to 100kHz and use 2.2kΩ pull-up resistors instead of 4.7kΩ to charge the line capacitance faster. To eliminate self-heating, configure the BME280 in Forced Mode (as shown in the code block above), waking the sensor only when a reading is requested, allowing the silicon to cool between samples.






