Tutorials / Measuring Temperature Without an ADC: The RC Charge-Time Thermistor Trick

Measuring Temperature Without an ADC: The RC Charge-Time Thermistor Trick

August 29, 2026

ThermistorRC CircuitMeasurementMicrocontrollerNonlinear CalibrationBlock Diagram

Measuring Temperature Without an ADC: The RC Charge-Time Thermistor Trick

The Problem

In the early days of microcontrollers, ADCs were expensive or simply not available on the part you could afford. You still needed to measure an analog quantity like temperature. The trick: use the timing of an RC charge instead of a voltage conversion.

You have exactly two digital outputs and one digital input:

  • OUT1 — switches the supply into the charging resistor.
  • OUT2 — discharges the capacitor.
  • IN — a single digital input that reads one voltage threshold (its “logic 1” level).

No analog divider, no successive-approximation register, no ADC.

The Circuit

+24 V OUT1 charge switch R1 = 10 kΩ Thermistor (NTC) R_T(T) V_C → IN C = 100 µF OUT2 discharge GND

The measurement loop:

  1. Discharge — close OUT2 (OUT2 ON). The capacitor dumps to ~0 V. This guarantees every measurement starts from the same known state.
  2. Charge — open OUT2 (OUT2 OFF), close OUT1 (OUT1 ON), and start the timer. Current flows from +24 V through R1 and the thermistor into the capacitor.
  3. Time it — watch IN. When V_C crosses the logic-1 threshold V_L1, stop the timer. The elapsed time is your measurement.
  4. Discharge again — open OUT1 (OUT1 OFF), close OUT2 (OUT2 ON). The capacitor discharges back to ~0 V, readying the next measurement cycle.

The two outputs alternate: OUT1 and OUT2 are never on at the same time. OUT1 charges, OUT2 discharges. The IN pin is the observer.

The Physics

Charge phase

This is a first-order RC circuit. The capacitor charges toward the supply V_S through the total series resistance R_total = R1 + R_T(T):

τ(T)=(R1+RT(T))C\tau(T) = \big(R_1 + R_T(T)\big) \cdot C VC(t)=VS(1et/τ)V_C(t) = V_S \left( 1 - e^{-t / \tau} \right)

Time to reach the logic-1 threshold

The IN pin trips when V_C reaches the logic-1 level V_L1. Solve for the time t_L:

VL1=VS(1etL/τ)V_{L1} = V_S \left( 1 - e^{-t_L / \tau} \right) tL=τln(VSVSVL1)t_L = \tau \cdot \ln\left(\frac{V_S}{V_S - V_{L1}}\right)

Combine with τ:

tL=(R1+RT(T))Cln(VSVSVL1)\boxed{t_L = \big(R_1 + R_T(T)\big) \cdot C \cdot \ln\left(\frac{V_S}{V_S - V_{L1}}\right)}

Discharge phase

When V_C reaches V_L1, OUT1 opens and OUT2 closes. The capacitor now discharges through OUT2 to ground. With the supply disconnected, the circuit is a simple RC decay:

VC(t)=VC0et/τV_C(t) = V_{C0} \cdot e^{-t / \tau}

where VC0V_{C0} is the voltage at the start of discharge (approximately VL1V_{L1}). The time to discharge from VL1V_{L1} to a near-zero reset level VresetV_{reset} is:

tdischarge=τln ⁣(VL1Vreset)t_{discharge} = \tau \cdot \ln\!\left(\frac{V_{L1}}{V_{reset}}\right)

With the component values at 25 °C and a reset threshold of 1 V: tdischarge=2ln(12/1)4.97t_{discharge} = 2 \cdot \ln(12/1) \approx 4.97 s. The discharge is slower than the charge because the cap starts at 12 V and must decay almost to zero, whereas the charge only needs to climb from 0 to 12 V.

The same ODE τV˙C+VC=u\tau \dot{V}_C + V_C = u governs both phases — transfer functions assume zero initial conditions, so the discharge (which is a zero-input response driven by the initial voltage VC0V_{C0}) is solved directly from the ODE with u=0u = 0 and VC(0)=VC0V_C(0) = V_{C0}, giving V˙C=VC/τ\dot{V}_C = -V_C/\tau and the exponential decay above. One transfer function, two modes — only the input changes.

The thermistor

An NTC thermistor’s resistance falls as temperature rises. Using the B-equation (a simplified Steinhart–Hart with one beta constant), with T in kelvin, reference temperature T₀ = 298.15 K, and resistance R₀ at T₀:

RT(T)=R0exp ⁣(B(1T1T0))R_T(T) = R_0 \cdot \exp\!\left( B \left( \frac{1}{T} - \frac{1}{T_0} \right) \right)

The combined measurement equation

Put the thermistor into t_L:

tL(T)=Cln ⁣(VSVSVL1)[R1+R0exp ⁣(B(1T1T0))]t_L(T) = C \cdot \ln\!\left(\frac{V_S}{V_S - V_{L1}}\right) \cdot \left[ R_1 + R_0 \cdot \exp\!\left( B \left( \frac{1}{T} - \frac{1}{T_0} \right) \right) \right]

The Circuit as a Transfer Function

The math above is easy to lose track of. The clean way an engineer reads this circuit: the charge phase is just a first-order RC lag, and the capacitor is literally the integrator.

The capacitor is 1/s

The capacitor’s defining relation is iC=CdVCdti_C = C\,\frac{dV_C}{dt}. Solve for the voltage:

VC=1CiCdtVC(s)=1CsiC(s)V_C = \frac{1}{C}\int i_C\,dt \quad\Longleftrightarrow\quad V_C(s) = \frac{1}{Cs}\, i_C(s)

The capacitor voltage is the integral of its charging current — an integrator, exactly 1/(Cs)1/(Cs) scaled by 1/C1/C.

Close the loop through the resistor

The charging current comes through the total series resistance: iC=VSVCR1+RTi_C = \frac{V_S - V_C}{R_1 + R_T}. As a voltage divider in the s-domain (with Rtotal=R1+RTR_{total} = R_1 + R_T):

  G(s)=VC(s)VS(s)=1/CsRtotal+1/Cs=1RtotalCs+1=1τs+1  \boxed{\;G(s) = \frac{V_C(s)}{V_S(s)} = \frac{1/Cs}{R_{total} + 1/Cs} = \frac{1}{R_{total}\,C\,s + 1} = \frac{1}{\tau s + 1}\;}

That is the whole charge phase: a unity-gain first-order lag. Its step response is VC(t)=VS(1et/τ)V_C(t) = V_S\left(1 - e^{-t/\tau}\right), and setting VC=VL1V_C = V_{L1} recovers tL=τln ⁣(VSVSVL1)t_L = \tau \ln\!\left(\frac{V_S}{V_S - V_{L1}}\right) — the same result as the physics derivation, now in one line.

The state-space form

One state x=VCx = V_C is enough:

x˙=1τx+1τVS,y=x\dot{x} = -\frac{1}{\tau}\,x + \frac{1}{\tau}\,V_S, \qquad y = x A=[1τ],B=[1τ],C=[1],D=0A = \left[-\tfrac{1}{\tau}\right], \quad B = \left[\tfrac{1}{\tau}\right], \quad C = [1], \quad D = 0

With the component values at 25 °C (τ=2\tau = 2 s): A=[0.5]A = [-0.5], B=[0.5]B = [0.5]. OpenBlockSim’s StateSpace block takes exactly these four arrays.

The catch: the plant is temperature-dependent

τ=(R1+RT(T))C\tau = (R_1 + R_T(T))\,C changes with temperature, so the plant is linear time-varying (LTV): the dynamics are linear, but the parameter is not. That’s why the block model doesn’t just drop a single TransferFunction block with fixed coefficients and call it done — it computes τ(T)\tau(T) through the thermistor chain and feeds it into the ODE live. Freeze the temperature (a fixed tct_c constant) and the LTV plant collapses to the LTI lag 1/(τs+1)1/(\tau s + 1) above.

Why You Need a Lookup Table

Here is the key insight that drives the whole design.

  • t_L is linear in R_T. Charging to a fixed fraction of V_S always gives t_L ∝ τ ∝ R_total. Doubling the resistance doubles the time. That part is clean.
  • R_T is exponential in 1/T. The NTC thermistor resists exponentially, not linearly, with inverse temperature.

So even though time is a linear function of resistance, resistance is a highly nonlinear function of temperature. There is no closed-form inverse T=f(tL)T = f(t_L) you can solve analytically — the exponential sits inside a linear map and cannot be algebraically unwrapped.

The standard solution: calibrate empirically. During commissioning, record t_L at several known temperatures, build a t_L → T table, store it in memory, and interpolate between entries at runtime. The table is the inverse you can’t solve for.

Component values used in this model

SymbolValueMeaning
V_S24 Vsupply
V_L112 Vlogic-1 threshold
R110 kΩcurrent-limiting resistor
C100 µFcapacitor
R₀10 kΩthermistor resistance at 25 °C
B3435 Kthermistor beta constant
T₀298.15 K(25 °C) reference temperature

With these, at 25 °C: τ = 2.0 s, and t_L = τ · ln(2) = 1.39 s.

The calibration curve

Because R_T is exponential, the t_L(T) curve is strongly curved. A small change in hot temperatures (where R_T is small) yields a small change in time, while a large change in cold temperatures yields a big change in time. The table must be denser where the curve is steepest — usually spanning the full expected operating range with more points at the cold/cold-resistance end.

Temperature (°C)   R_T (Ω)    t_L (s)
       0           28,704     2.683
      10           18,410     1.969
      25           10,000     1.386
      40            5,759     1.092
      60            2,981     0.900

(Values from the model: hotter → lower R_T → shorter t_L, monotonically decreasing.)

Block Simulation Model

I built this as a runnable block diagram in OpenBlockSim, loaded from the Examples gallery under “Thermistor RC Charge/Discharge (2-OUT/1-IN)”. Comment annotations sit next to each block so you can read the diagram like the circuit.

The model uses the transfer function G(s)=1/(τs+1)G(s) = 1/(\tau s + 1) with τ=2\tau = 2 s (frozen at 25 °C) as the RC plant, and a feedback loop that automatically switches between charge and discharge phases:

Signal flow

  1. Switch block — routes either +24 V (OUT1, charge) or 0 V (OUT2, discharge) into the TF plant input, based on the relay output. When the relay is off (V_C below threshold), the switch passes +24 V. When the relay fires (V_C above threshold), the switch passes 0 V.
  2. TransferFunction blockG(s)=1/(2s+1)G(s) = 1/(2s+1), the RC plant. Its output is V_C, the capacitor voltage. With +24 V input it charges; with 0 V input it discharges.
  3. Relay block — the IN pin. Trips at VL1=12V_{L1} = 12 V (switchOn) and resets at 1 V (switchOff). The hysteresis band prevents chatter at the threshold. Output: 1 = charged, 0 = discharged.
  4. Feedback edge — relay output feeds back to the switch control input, closing the loop: V_C rises → relay fires → switch flips to 0 V → V_C falls → relay resets → switch flips back to 24 V → repeat.

Two Scope blocks display V_C (the charge/discharge waveform) and the relay output (the logic-1 pulse train).

What you see when you run it

The V_C scope shows a sawtooth-like waveform: exponential rise from 0 to 12 V (charge, tL1.39t_L \approx 1.39 s), then exponential decay from 12 V back to 1 V (discharge, 4.97\approx 4.97 s), then repeat. The relay scope shows a square wave that goes high when V_C crosses 12 V and low when V_C drops below 1 V.

One transfer function for both charge and discharge

The RC plant is the same physical circuit in both phases — the same resistor, the same capacitor. The only thing that changes is what drives the input. During charge, the input is VS=24V_S = 24 V. During discharge, the input is 0 V (OUT2 shorts the cap to ground). The transfer function G(s)=1/(τs+1)G(s) = 1/(\tau s + 1) describes the capacitor’s response to whatever input you give it:

  • Charge: input = VSV_S, output rises as VC(t)=VS(1et/τ)V_C(t) = V_S(1 - e^{-t/\tau})
  • Discharge: input = 0, output decays as VC(t)=VC0et/τV_C(t) = V_{C0} \cdot e^{-t/\tau}

Both are the same first-order ODE τV˙C+VC=u\tau \dot{V}_C + V_C = u, just with different input uu. One transfer function, two operating modes — the Switch block handles the mode selection.

How the Switch block represents the digital outputs

The Switch block has three inputs and one output. It works like a digital selector:

Switch portConnected toCircuit role
input[0]gnd (0 V)OUT2 ON — discharge path
input[1]relay outputIN pin (digital input) — the control signal
input[2]vs (24 V)OUT1 ON — charge supply

The relay (IN pin) is the control input. When the relay output is 0 (V_C below threshold), the switch passes input[2] = 24 V — this is OUT1 ON, OUT2 OFF (charge). When the relay output is 1 (V_C reached V_L1), the switch passes input[0] = 0 V — this is OUT1 OFF, OUT2 ON (discharge).

In the real microcontroller, the firmware would do this with two separate digital output pins. In the block diagram, the Switch block combines both outputs into a single signal-routing element: it is the OUT1/OUT2 selector, driven by the IN pin’s threshold detection. The Relay block acts as the IN pin — a digital input that reads V_C and outputs a 1 or 0 depending on whether V_C has crossed the logic-1 threshold.

Block(s)Circuit element / role
vs (Constant = 24)OUT1: +24 V supply (charge)
gnd (Constant = 0)OUT2: 0 V discharge path
sw (Switch)OUT1/OUT2 routing — relay state selects which source feeds the plant
tf (TransferFunction 1/(2s+1))The RC plant: G(s)=1/(τs+1)G(s) = 1/(\tau s + 1), τ=2\tau = 2 s at 25 °C
relay (Relay @ 12 V)IN pin — logic-1 threshold detector with hysteresis (switchOff = 1 V)
scope_v, scope_rOscilloscope views: V_C charge/discharge waveform, relay output

Verifying the model against the math

  1. Charge time — the first rising edge crosses 12 V at tL1.39t_L \approx 1.39 s, matching the analytic tL=τln(VS/(VSVL1))=2ln(2)1.386t_L = \tau \ln(V_S/(V_S - V_{L1})) = 2\ln(2) \approx 1.386 s to within 1%.
  2. Discharge time — the first falling edge drops from 12 V to 1 V in 4.97\approx 4.97 s, matching τln(VL1/Vreset)=2ln(12)4.97\tau \ln(V_{L1}/V_{reset}) = 2\ln(12) \approx 4.97 s.
  3. Periodicity — the cycle repeats with a period of 6.36\approx 6.36 s (tL+tdischarget_L + t_{discharge}), confirming the feedback loop is stable.

This is a useful pattern generally: after building any simulation, verify it against a closed-form or hand calculation before trusting it. A simulation that runs but disagrees with the equations is just a confident wrong answer.

Practical Notes

  • Discharge phase. The model includes the discharge explicitly: when V_C reaches V_L1, the relay fires and the switch routes 0 V into the TF plant, causing V_C to decay exponentially. The relay’s switchOff hysteresis (1 V) ensures the cap discharges well below the charge threshold before the next cycle begins. In the real circuit, the OUT2 discharge path has its own resistance (the ON-resistance of the output driver), which sets the discharge time constant. If that resistance is significant compared to R1 + R_T, the discharge won’t follow the same τ as the charge — model it separately if precision matters.
  • Logic-1 threshold. The real IN pin’s threshold V_L1 is the device’s actual switching level, not necessarily a round number. Measure it or specify it precisely — it directly multiplies into t_L. A slight error here shifts every calibration entry.
  • R1 is a safety resistor. It limits charging current and sets a floor on the minimum time constant so the microcontroller can measure it, even when the thermistor’s resistance collapses at high temperature.
  • Noise and jitter. Because you’re timing an analog threshold crossing with a digital counter, timer resolution and input hysteresis add small measurement uncertainty. Calibration absorbs the fixed part; the random part bounds your achievable precision.

Why This Still Matters

Even now, this two-output-one-input technique shows up in cost-critical projects and in any place where you’d rather not spend an ADC channel. It also teaches the general lesson that measurement can be done in the time domain: instead of resolving a voltage to N bits, you count the time it takes to cross a fixed threshold, and let the nonlinearity of your transducer become a calibration table rather than a math problem.

Comments