The Sensing Principle: How Resistive Position Tracking Works

A resistive position sensor—whether it is a rigid slide potentiometer like the Bourns PTA series or a flexible membrane softpot like the Spectra Symbol SSP-100—relies on a physical wiper traveling across a resistive element, typically a carbon composition or cermet track. By applying a stable reference voltage across the two fixed end terminals, the moving wiper acts as the tap point of a variable voltage divider. The resistance between the wiper and the ground terminal changes linearly with physical displacement, translating mechanical travel into an electrical gradient.

Let's be absolutely clear about the output: a standard 3-terminal resistive position sensor outputs an analog voltage strictly bounded between 0V and your supply voltage (VCC). It does not output a 4-20mA current loop, nor does it generate a digital pulse train like a quadrature encoder. Because the output is a continuous analog voltage, you must route the wiper pin directly to a microcontroller's Analog-to-Digital Converter (ADC) input to digitize the physical position.

Wiring and Pinout: ESP32 and Arduino Integration

Resistive sensors are passive and ratiometric, meaning their output voltage scales proportionally with the supply voltage. While they can technically operate anywhere from 2.7V to 5.5V, your wiring strategy must be dictated by your microcontroller's logic level and ADC reference voltage.

Sensor Pin Function Arduino Uno (5V) ESP32 DevKit (3.3V)
Pin 1 VCC (Supply) 5V 3.3V
Pin 2 Wiper (Signal) A0 - A5 ADC1 (GPIO 32-39)
Pin 3 GND (Reference) GND GND
Callout Tip: ESP32 ADC Constraints
Never connect the wiper to an ESP32 ADC2 pin (GPIO 0, 2, 4, 12-15, 25-27) if you plan to use WiFi or Bluetooth. ADC2 is shared with the radio subsystem and will return garbage data during transmission. Always use ADC1 pins (GPIO 32 through 39) for position sensing. For a deep dive on ESP32 ADC mapping, refer to this ESP32 ADC pinout guide.

Raw ADC to Physical Units: The Conversion Math

Because the sensor is ratiometric, the physical position is derived from the ratio of the raw ADC reading to the maximum possible ADC value, multiplied by the sensor's total mechanical travel length. If you are using a 100mm slide pot with an ESP32 (12-bit ADC, max value 4095), the base formula is:

Position_mm = (Raw_ADC / 4095.0) * 100.0

However, raw carbon-track sensors suffer from end-point dead zones and wiper contact bounce. You must apply software scaling (end-point trimming) and a low-pass filter to get usable data. Below is a complete, copy-pasteable Arduino/ESP32 C++ implementation that handles the math and applies an Exponential Moving Average (EMA) filter to smooth out wiper jitter.

// Resistive Position Sensor - Calibrated EMA Filter
// Target: ESP32 (12-bit ADC) or Arduino (10-bit ADC)

const int wiperPin = 34;       // ESP32 ADC1 pin
const float travelLength = 100.0; // Total physical travel in mm
const float deadZoneLow = 2.0;    // Mechanical deadzone at 0mm (in mm)
const float deadZoneHigh = 98.0;  // Mechanical deadzone at max (in mm)
const float alpha = 0.15;         // EMA filter weight (lower = smoother)

float filteredPosition = 0.0;

void setup() {
  Serial.begin(115200);
  analogReadResolution(12); // Set ESP32 to 12-bit (0-4095)
  analogSetAttenuation(ADC_11db); // Full 0-3.3V range for ESP32
}

void loop() {
  int rawADC = analogRead(wiperPin);
  
  // 1. Convert raw ADC to raw millimeters
  float rawMM = (rawADC / 4095.0) * travelLength;
  
  // 2. Apply end-point calibration (map deadzones to true 0-100mm)
  float calibratedMM = mapFloat(rawMM, deadZoneLow, deadZoneHigh, 0.0, travelLength);
  
  // 3. Constrain to physical limits
  calibratedMM = constrain(calibratedMM, 0.0, travelLength);
  
  // 4. Apply Exponential Moving Average (EMA) filter
  filteredPosition = (alpha * calibratedMM) + ((1.0 - alpha) * filteredPosition);
  
  Serial.print("Raw ADC: ");
  Serial.print(rawADC);
  Serial.print(" | Smoothed Position: ");
  Serial.print(filteredPosition, 2);
  Serial.println(" mm");
  
  delay(20); // 50Hz sampling rate
}

// Helper function for floating-point mapping
float mapFloat(float x, float in_min, float in_max, float out_min, float out_max) {
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

Common Interference Sources and Signal Conditioning

Resistive position sensors are notoriously susceptible to noise. The wiper forms a high-impedance node that acts as an antenna, and the physical contact introduces mechanical noise. Here is how to mitigate the three most common interference sources:

  • Wiper Contact Bounce: As the wiper moves across the carbon track, microscopic variations in contact resistance cause voltage spikes. Hardware fix: Solder a 100nF X7R ceramic capacitor directly between the wiper pin and GND. Place it physically at the sensor terminals, not at the microcontroller, to filter high-frequency noise before it travels down the wire.
  • Electromagnetic Interference (EMI): If your sensor cables run parallel to stepper motor wiring or switching power supplies, the unshielded analog signal will pick up induced voltages. Hardware fix: Use shielded twisted-pair cable for the wiper signal, and tie the shield to GND at the microcontroller end only to prevent ground loops.
  • ADC Quantization and Non-Linearity: The ESP32's internal ADC is notoriously non-linear at the extreme top and bottom of its voltage range (below 0.1V and above 3.1V). Software fix: This is exactly why the deadZoneLow and deadZoneHigh variables exist in the code above. Never rely on the raw 0-4095 extremes; trim the usable range by 2-5% on both ends.

Frequently Asked Questions

Why is my resistive position sensor output jittering even when stationary?

Stationary jitter is almost always caused by either a missing bypass capacitor or power supply ripple. Because the sensor is ratiometric, any noise on your 3.3V or 5V VCC rail will directly modulate the wiper's output voltage. First, ensure you have a 100nF ceramic capacitor soldered across the sensor's VCC and GND pins, and another 100nF cap between the wiper and GND. If the jitter persists, measure your MCU's VCC rail with an oscilloscope; if you see switching noise from an onboard buck converter, you may need to power the sensor from a dedicated low-dropout (LDO) linear regulator rather than the MCU's shared 3.3V pin.

Can I use a 5V resistive position sensor directly with a 3.3V ESP32 GPIO?

Yes, but you must change how you power it. Do not power the sensor with 5V and use a resistor voltage divider to drop the wiper signal down to 3.3V; this ruins the ratiometric nature of the sensor and introduces thermal drift from the divider resistors. Instead, power the sensor's VCC pin directly from the ESP32's 3.3V output. A 10kΩ carbon track sensor will only draw 0.33mA at 3.3V, which is well within the ESP32's 3.3V regulator limits. This ensures that as the ESP32's 3.3V rail fluctuates slightly, the ADC reference and the sensor supply fluctuate together, canceling out the error.

What is the difference between a resistive position sensor and a rotary encoder?

A resistive sensor provides absolute position immediately upon power-up, whereas a standard incremental rotary encoder only provides relative movement (pulses) and requires a homing routine to find its zero point. However, resistive sensors suffer from mechanical wear—the carbon track degrades over thousands of cycles, leading to dead spots. Encoders use optical or magnetic sensing with no physical contact, offering virtually infinite lifespans. Choose a resistive sensor for low-cost, low-cycle applications like DIY audio faders or throttle levers; choose an encoder for high-cycle industrial machinery or precision CNC dials.

How do I calibrate a softpot resistive sensor that has dead zones at the ends?

Membrane softpots (like the Spectra Symbol SSP series) often have 5mm to 10mm of inactive 'dead zone' at both physical ends where the wiper cannot make proper contact with the resistive ink. To calibrate this, physically measure the exact millimeter mark where the output voltage begins to rise linearly (the true zero) and where it maxes out before the end of the stroke. Enter these two physical measurements into the deadZoneLow and deadZoneHigh variables in the mapping function provided above. The software will then stretch the active electrical range to map perfectly to your desired 0-100% physical output.