If you need to read a low-voltage, high-impedance sensor (like a thermocouple, shunt resistor, or piezo element) with a microcontroller, you cannot wire it directly to an ADC. The ESP32’s internal ADC expects a 0–3.3V signal and has a relatively low input impedance that will load down delicate sensors. The solution is a non-inverting op-amp circuit configured for precise DC gain.

This guide walks through the exact component selection, topology math, and breadboard verification for scaling a 0–100mV sensor output to a safe 0–3.0V range for the original ESP32 ADC, terminating in a concrete bill of materials.

The Core Decision: Inverting vs. Non-Inverting Topology

When designing an op-amp circuit for sensor conditioning, you must choose between the inverting and non-inverting configurations. For high-impedance sensors, the non-inverting topology is the mandatory default.

Criteria Non-Inverting Topology Inverting Topology
Input Impedance Extremely high (op-amp input bias impedance, often >100 MΩ) Low (equal to the input resistor, e.g., 10 kΩ)
Sensor Loading Negligible; does not draw current from the sensor Significant; loads down high-impedance sources, causing voltage droop
Gain Equation $A_v = 1 + (R_f / R_g)$ (Always ≥ 1) $A_v = - (R_f / R_{in})$ (Can be < 1, but inverts phase)
Best Application Thermocouples, piezo, biopotential, shunt monitors Current-to-voltage conversion, summing mixers, low-impedance DAC buffering

Verdict: Use the non-inverting configuration. The inverting topology’s low input impedance would form a voltage divider with your sensor’s internal resistance, destroying your measurement accuracy before the signal even reaches the op-amp.

Topology Nodes and Element Behavior Matrix

The non-inverting amplifier relies on negative feedback to force the voltage at the inverting input to match the non-inverting input. Here are the critical nodes:

  • Node + ($V_{in}$): Non-inverting input. Connects directly to the sensor signal.
  • Node - ($V_{fb}$): Inverting input. Connects to the feedback voltage divider.
  • $V_{out}$: Output node. Connects to the ESP32 ADC pin and the top of the feedback resistor ($R_f$).
  • $R_f$ (Feedback Resistor): Connects between $V_{out}$ and Node -.
  • $R_g$ (Ground Resistor): Connects between Node - and Circuit Ground.
Bench Tip: The op-amp’s internal high-gain differential stage continuously adjusts $V_{out}$ to keep the voltage difference between Node + and Node - at virtually zero. This is the "virtual short" principle. If Node + is at 50mV, the op-amp will drive $V_{out}$ just high enough so that the $R_f$/$R_g$ divider drops exactly 50mV across $R_g$.

Behavior Table: What Changes When Elements Shift

Element Changed Direction of Change Effect on Circuit Behavior
$R_f$ (Feedback) Increases Gain increases. Output voltage rises for the same input. Noise gain also increases.
$R_g$ (Ground) Increases Gain decreases. Circuit approaches a unity-gain buffer (Gain = 1) if $R_g$ approaches infinity.
$V_{in}$ (Sensor) Exceeds Common-Mode Range Op-amp input stage saturates. Output locks to the positive or negative rail regardless of feedback.
Load on $V_{out}$ Decreases (Heavy current draw) If load draws >20mA (typical limit for small op-amps), $V_{out}$ droops and gain equation fails.

Design Walkthrough: 0–100mV to 0–3.0V Sensor Amplifier

Let’s design a circuit to amplify a 0–100mV shunt voltage to a 0–3.0V range.

ESP32 ADC Non-Linearity Warning: The original ESP32 (WROOM-32) ADC is notoriously non-linear above 3.1V and saturates near 3.2V. While the ESP32-S3 and external ADCs (like the ADS1115) can read the full 3.3V rail, we will design for a 3.0V maximum output to guarantee accuracy across all ESP32 silicon revisions.

1. Calculate Required Gain:
Target $V_{out}$ = 3.0V. Max $V_{in}$ = 0.1V.
Gain ($A_v$) = 3.0V / 0.1V = 30.

2. Select Resistor Values:
Using the formula $A_v = 1 + (R_f / R_g)$, we need $R_f / R_g = 29$.
To minimize thermal noise and avoid excessive loading on the op-amp output, we want resistors in the 1kΩ to 100kΩ range. Let’s set $R_g = 1.00 \text{ k}\Omega$.
Therefore, $R_f = 29.0 \text{ k}\Omega$.

3. Pick Standard 1% Tolerance (E96) Components:
29.0 kΩ is not a standard 1% value. The closest E96 values are 28.7 kΩ and 29.4 kΩ.
Let’s select $R_f = 29.4 \text{ k}\Omega$ and $R_g = 1.00 \text{ k}\Omega$.
Actual Gain = $1 + (29.4 / 1.0) = 30.4$.
Max Output = $100\text{mV} \times 30.4 = 3.04\text{V}$. This is perfectly safe for the ESP32 ADC.

4. Power Decoupling:
Place a 100nF (0.1µF) X7R MLCC capacitor as physically close to the op-amp’s VCC and GND pins as possible. Without this, the breadboard parasitics will cause high-frequency oscillation, reading as random noise on your ADC.

Component Selection Decision Tree

Not all op-amps can operate on a single 3.3V supply while reading signals near ground. Older parts like the LM741 or TL072 require dual supplies (±15V) and cannot swing their outputs to the rails. Use this decision matrix to pick your exact part number.

If your application requires... Then choose this Op-Amp Why this part wins
General 3.3V sensor amplification, low cost, hobbyist prototyping Microchip MCP6002 (Dual, DIP-8) Rail-to-rail input/output (RRIO), operates down to 1.8V, costs ~$0.50. Input common-mode range includes ground.
Microvolt precision, thermocouples, eliminating 1/f noise and DC drift TI OPA333 (Single, SOT-23-5 or SOIC) Chopper-stabilized zero-drift architecture. Max offset voltage is 10µV. Costs ~$3.50 but eliminates software calibration.
High-speed signals (>100kHz), audio, or fast transient settling TI OPA2134 (Dual, DIP-8) FET inputs, 8MHz bandwidth, low distortion. Note: Requires at least a 5V single supply (or ±2.5V), so not ideal for native 3.3V ESP32 rails.

Final Pick for this Build: The Microchip MCP6002. It is cheap, available in through-hole DIP-8 for breadboarding, and its rail-to-rail architecture guarantees the output can swing from 0V to 3.3V on a single supply.

Breadboard Testing and Verification Protocol

Do not connect your sensor or your ESP32 until the op-amp stage is verified on the bench. Follow these exact steps:

  1. Power Off & Wire the Rails: Connect the 3.3V output from your breadboard power supply (or ESP32 3V3 pin) to the MCP6002 Pin 8 (VDD). Connect Ground to Pin 4 (VSS).
  2. Install Decoupling: Insert the 100nF capacitor across the VDD and VSS pins of the op-amp.
  3. Wire Feedback Network: Connect the 29.4kΩ $R_f$ between Pin 1 (Output A) and Pin 2 (Inverting Input A). Connect the 1.00kΩ $R_g$ between Pin 2 and Ground.
  4. Ground the Input: Temporarily jumper Pin 3 (Non-inverting Input A) directly to Ground.
  5. Power On & Measure Quiescent State: Turn on the 3.3V supply. Use your multimeter to measure DC voltage between Pin 1 (Output) and Ground. It should read < 5mV (the MCP6002 max input offset voltage is ±4.5mV). If it reads 3.3V or oscillates, check your decoupling cap and wiring.
  6. Inject Test Signal: Remove the ground jumper from Pin 3. Use a precision voltage source (or a potentiometer wired as a voltage divider across 3.3V) to inject exactly 50.0mV into Pin 3.
  7. Verify Gain: Measure Pin 1. You should read $50\text{mV} \times 30.4 = \mathbf{1.52\text{V}}$ (±20mV tolerance for meter and resistor drift).
  8. Connect to ESP32: Once verified, wire Pin 1 to your designated ESP32 ADC pin (e.g., GPIO 34). Add a 100nF capacitor from the ADC pin to ground in software/firmware to form a low-pass RC filter with the op-amp's output impedance.

Failure Modes: What Breaks at the Extremes?

Understanding how the circuit fails when a component breaks is critical for designing fault-tolerant firmware. Here is the failure-mode contrast for the feedback network:

  • $R_f$ Opens (Broken feedback path): The negative feedback loop is destroyed. The op-amp enters open-loop mode. Because of the massive internal open-loop gain (>100dB), even microvolts of input offset will drive the output hard against the positive rail. Result: $V_{out}$ pegs at ~3.28V. Firmware should flag an ADC reading >3.2V as a sensor fault.
  • $R_f$ Shorts: The output is directly tied to the inverting input. The circuit becomes a unity-gain voltage follower. Result: Gain drops to exactly 1.0. A 100mV input yields a 100mV output. Your ESP32 will read extremely low values.
  • $R_g$ Opens: The inverting input is disconnected from ground. The feedback network cannot pull the Node - voltage down to match Node +. Result: Similar to an open $R_f$, the op-amp saturates and rails high.
  • $R_g$ Shorts: The inverting input is tied directly to ground. The op-amp must drive the output to 0V to satisfy the virtual short (since Node + is near ground). Result: Gain becomes 1, but the output is clamped near 0V regardless of small input signals.

By designing your ESP32 firmware to expect values between 0.1V and 3.1V, you can easily trap the open-circuit failure modes (which rail at ~3.28V) and trigger a safe shutdown or alert. For deeper stability analysis and phase margin considerations when adding capacitive loads to the output, refer to Analog Devices' guide on avoiding op-amp instability.

With the MCP6002, 1% tolerance resistors, and proper decoupling, this non-inverting op-amp circuit will provide a clean, linear, and robust analog front-end for your microcontroller projects.