When makers ask 'what does sensors mean' in embedded systems, the practical answer is that a sensor is a transducer paired with signal conditioning. It maps a physical phenomenon—like thermal energy or magnetic flux—into an electrical parameter (voltage, current, or digital bus data) that a microcontroller's ADC or communication peripheral can parse. Unlike a raw transducer that might just change resistance, a complete sensor module outputs a standardized signal. This guide breaks down the exact output types, wiring requirements, and raw-to-unit math needed to interface common sensors with an ESP32 or Arduino.

The Transduction Principle: Physical to Electrical

At the silicon level, a sensor relies on transduction: the conversion of one form of energy into another. A thermocouple generates a microvolt-level potential difference via the Seebeck effect when subjected to a thermal gradient. A piezoresistive pressure sensor changes its electrical resistance when mechanical strain deforms its silicon lattice. However, a microcontroller cannot read resistance or raw electron flow directly.

This is where signal conditioning bridges the gap. The sensor's internal circuitry (or an external op-amp network) excites the transducer, amplifies the microvolt signal, filters high-frequency noise, and linearizes the output. The result is a clean, proportional analog voltage, a 4-20mA current loop, or a serialized digital packet. Understanding what the output actually is—and whether it is ratiometric to the supply voltage or an absolute reference—is the difference between a stable reading and a noisy, drifting mess.

Sensor Output Types and Interfacing Specifications

Before writing a single line of code, you must identify the sensor's output domain. Conflating an absolute analog voltage with a ratiometric output, or assuming all I2C devices share the same logic levels, will fry your microcontroller or yield garbage data. Below is a data-dense specification table for four ubiquitous sensor classes.

Sensor Model Physical Target Output Type Supply Range (VCC) Interface / Wiring Raw Output Characteristic
LM35 Temperature Analog Voltage (Absolute) 4.0V – 30.0V VOUT to ADC, GND 10 mV / °C (Linear, 0V at 0°C)
ACS712-20A AC/DC Current Analog Voltage (Ratiometric) 4.5V – 5.5V VOUT to ADC, GND 100 mV / A (Offset at VCC/2)
BME280 Temp / Hum / Press Digital (I2C / SPI) 1.71V – 3.6V SDA, SCL, VCC, GND 20-bit RAW ADC (Requires compensation)
DS18B20 Temperature Digital (1-Wire) 3.0V – 5.5V DATA (4.7k pull-up), VCC, GND 12-bit signed integer (0.0625°C/bit)
Callout: Ratiometric vs. Absolute Outputs
The ACS712 is ratiometric. If your 5V Arduino supply sags to 4.8V, the sensor's zero-current offset drops from 2.50V to 2.40V. If your ADC reference remains fixed at 5.0V, your current readings will drift. The LM35 is absolute; its 10mV/°C output is referenced to an internal bandgap, meaning supply sag does not affect the temperature reading, provided VCC stays above 4.0V.

Raw Reading to Physical Unit: The Conversion Math

A microcontroller's ADC does not measure temperature or current; it measures voltage and returns a dimensionless integer. To get physical units, you must apply scaling math based on the ADC's resolution and the sensor's transfer function.

Analog Voltage Scaling (LM35 on a 10-bit ADC)

Assume an Arduino Uno with a default 5.0V reference and a 10-bit ADC (1024 steps). The voltage resolution per step is:

V_resolution = 5.0V / 1024 = 0.00488V (4.88 mV)

Since the LM35 outputs exactly 10 mV (0.010V) per degree Celsius, the raw-to-unit math is:

Temperature (°C) = (ADC_Raw * 0.00488) / 0.010

Temperature (°C) = ADC_Raw * 0.488

If your ADC reads 153, the temperature is 153 * 0.488 = 74.6°C.

Ratiometric Current Math (ACS712-20A)

The ACS712-20A outputs 100 mV per Ampere, but it is biased at VCC/2 to allow bidirectional (AC/DC) measurement. At zero current, the output is 2.5V (ADC raw ~512). To calculate current, you must subtract the zero-offset before scaling:

Current (A) = (ADC_Raw - 512) * (5.0 / 1024) / 0.100

Current (A) = (ADC_Raw - 512) * 0.0488

If the ADC reads 612, the current is (612 - 512) * 0.0488 = 4.88A.

Digital Bus Compensation (BME280 I2C)

Digital sensors bypass the microcontroller's ADC entirely, but they introduce a different math hurdle: compensation. The BME280 does not output a simple linear integer. It outputs raw 20-bit ADC values for temperature, pressure, and humidity, which must be corrected using factory-programmed calibration coefficients stored in the sensor's NVM (Non-Volatile Memory).

While you can write the 32-bit integer compensation algorithm manually using the formulas in the Bosch datasheet, the industry standard is to use the Adafruit BME280 library. The library handles the I2C register fetching and the complex floating-point math, returning a calibrated float:

#include <Adafruit_BME280.h>
Adafruit_BME280 bme;

void setup() {
  Serial.begin(115200);
  if (!bme.begin(0x76)) { // 0x76 or 0x77 depending on SDO pin
    Serial.println("BME280 not found, check wiring!");
    while (1);
  }
}

void loop() {
  // Library handles raw-to-unit math internally
  float temp = bme.readTemperature(); // Returns °C
  float pres = bme.readPressure() / 100.0F; // Returns hPa
  Serial.printf("Temp: %.2f C, Press: %.2f hPa\n", temp, pres);
  delay(2000);
}
ESP32 ADC Warning: If you port the LM35 or ACS712 analog math to an ESP32, the default analogRead() will fail. The ESP32 ADC is 12-bit (0-4095), highly non-linear, and caps at ~3.1V. For precision analog sensors on an ESP32, always use an external I2C ADC like the ADS1115, or rely entirely on digital sensors like the BME280.

Signal Integrity: Interference Sources and Calibration

Even with perfect math, real-world physics will corrupt your data if you ignore signal integrity. Sensors operate in noisy environments, and the wiring between the transducer and the microcontroller acts as an antenna.

Common Interference Sources

  • Electromagnetic Interference (EMI) on Analog Lines: Running a 10mV/°C LM35 signal parallel to a 120V AC mains cable or a PWM-driven motor wire will induce 50/60Hz hum and high-frequency switching noise. Fix: Use shielded twisted-pair cable for analog runs, and place a 100nF ceramic decoupling capacitor directly across the sensor's VCC and GND pins.
  • I2C Bus Capacitance and Float: The BME280 uses open-drain I2C lines. If you omit pull-up resistors, the SDA/SCL lines will float, causing random I2C lockups or corrupted compensation registers. Fix: Use 4.7kΩ pull-ups to 3.3V for standard 100kHz bus speeds. If your trace length exceeds 30cm or you are running at 400kHz, drop to 2.2kΩ pull-ups to overcome parasitic capacitance.
  • Ground Loops in Current Sensing: When measuring high-side current with an ACS712, the load's return path can create a ground potential difference between the sensor and the microcontroller. Fix: Ensure a single-point star ground topology on your PCB or breadboard, tying the sensor GND and MCU GND together as close to the power supply as possible.

Calibration and Scaling Adjustments

Factory calibration is rarely perfect for precision work. A BME280 might read 1.5°C higher than ambient due to self-heating from its internal I2C interface and voltage regulator. To fix this, implement a software offset in your code:

float calibrated_temp = bme.readTemperature() - 1.5; // Self-heating offset

For analog sensors, always measure your actual VCC rail with a multimeter. If your Arduino's 5V pin is actually outputting 4.82V via USB, your ADC resolution changes from 4.88mV to 4.70mV. Hardcoding 5.0 in your math will introduce a permanent 3.6% scaling error. Read the internal bandgap reference or use a precision 3.3V LDO to guarantee your math constants match reality.