A diode and a capacitor can hold most of a sine wave’s positive peak. Between peaks, the load draws charge from the capacitor and the held voltage falls. Change one resistor while watching both ends of a Vishay BAT85S Schottky diode. Then use a small part of the trace to test the discharge equation without guessing a constant diode drop.
A peak detector charges a capacitor when the input rises high enough to make the diode conduct toward the capacitor. The diode then blocks most reverse current when the input drops below the held voltage. The repeated variation between the held peak and valley is ripple. The two loads in this lab deliberately trade ripple against how long the circuit remembers a previous peak. All numerical examples are calculated; the worksheet has no bench readings.
| Node | Connections | What to read |
|---|---|---|
| GEN | Generator center contact, one end of R1 | Check source setting here before wiring. |
| A | Other end of R1, diode anode, scope CH1 tip | Actual voltage at the diode input. |
| H | Diode banded cathode, C1, one end of the chosen RL, scope CH2 tip | Held voltage and its fall between peaks. |
| 0 V | Generator shield, opposite ends of C1 and RL, both scope ground clips | Common reference; do not put a ground clip on A or H. |
Keep the protective earth connections intact. This fixture uses only the generator output and one shared ground. Do not connect it to mains, another powered circuit, an amplifier’s floating output, or a charged storage capacitor. Turn the output off before moving the load or diode. Leave a load fitted and verify that H returns near zero before moving leads.
At the proposed ±2.0 V open-circuit signal, R1 and the source’s nominal 50 Ω series impedance limit the positive startup current to about 1.90 mA in the limiting model of a zero-volt output and zero diode drop. Even with 2.2 V positive and R1 at 990 Ω, the conservative ceiling ignoring source resistance is 2.23 mA. The negative half-cycle can reverse-bias D1: if H remains at +2.2 V and A reaches −2.2 V, the difference is 4.4 V. The BAT85S data sheet’s 30 V reverse rating is a limit, not a target for this experiment. Record actual amplitudes and stop if the setup exceeds the proposed low-voltage range.
If the hold trace goes negative, switch the output off and check diode direction, scope ground clips and channel coupling. If the hold trace never rises, check the band, the actual input sine and continuity through R1. If the valley is nearly the peak, zoom CH2 vertically with DC coupling still active. If it remains unresolved, report the scope limit instead of entering an invented droop. Do not move the cursors into recharge merely to get a larger drop.
When A remains below H, D1 is reverse-biased, so the intended discharge current flows through RL and the finite CH2 probe input. The probe input resistance and RL are in parallel, meaning they draw separate currents from the same node to 0 V. Add their conductances, the reciprocals of resistance. At this frequency, the probe’s nominal 10 pF is about 0.01% of the 100 nF capacitor, so the simple model leaves it out. Diode leakage, stray coupling and capacitor tolerance still matter.
Reff = 1 ÷ (1/RL + 1/10 MΩ), τ = Reff × C1
The time constant τ is the time for an ideal discharge to reach about 37% of its starting voltage. With a 100 nF capacitor, the nominal time constants for 22 kΩ and 100 kΩ are about 2.195 ms and 9.901 ms. Starting from a constructed H reading of 1.700 V, the model predicts approximately 1.354 V and 1.616 V respectively 0.500 ms later. These numbers predict a chosen reverse interval; neither is a prediction of the whole-cycle valley or a measured result.
VB = VAhold × exp(−Δt/τ), τwindow = −Δt ÷ ln(VB/VAhold)
VAhold means H at cursor A, not node A. The voltage ratio estimates an apparent time constant only when the hold actually falls by a resolved amount. Whole-cycle ripple is peak minus valley. It includes recharge timing and must be kept separate from the selected window’s drop. The smaller load resistance should make discharge faster under the model. An observed change can also reflect different recharge intervals, source variation, leakage or measurement limits.
Vishay gives forward-voltage limits for specific currents, such as 240 mV maximum at 0.1 mA and 320 mV maximum at 1 mA under the stated test conditions. During recharge, current changes as the sine and capacitor voltage move. Subtracting one assumed forward voltage from the input crest cannot reproduce that waveform. The code deliberately starts from the measured hold value at cursor A.
Copy the blank comma-separated values (CSV) worksheet. Give each load one row, in ohms, nanofarads, milliseconds and volts. In anode_a_v and hold_a_v, A means the first cursor position; B in the other two columns means the second cursor position on the same trace. Set reverse_segment_confirmed to yes only after inspecting both traces between the cursors. The program checks the endpoints, but two points cannot establish the full interval.
Save peak_hold.py and run python3 peak_hold.py observations.csv. The result reports model τ, observed ripple, observed window drop and the difference from the ideal prediction. An unresolved apparent time constant appears as null if the interval falls by less than 5 mV. That is an analyzer convention; use the actual scale and repeatability of your scope to decide whether a larger reading is resolved. The host tests use constructed examples.
"""Analyze two manually checked diode-off windows of a peak hold circuit."""
import argparse
import csv
import json
import math
import sys
FIELDS = (
"load", "resistor_ohm", "cap_nf", "period_ms", "dt_ms", "peak_v",
"valley_v", "anode_a_v", "hold_a_v", "anode_b_v", "hold_b_v",
"reverse_segment_confirmed",
)
NOMINAL = {"22k": 22_000, "100k": 100_000}
# Well below the fixture's readable precision; only cancels float rounding.
BOUNDARY_EPS = 1e-12
def finite(row, key):
try:
value = float(row[key])
except (ValueError, TypeError) as exc:
raise ValueError(f"{key}: enter a number") from exc
if not math.isfinite(value):
raise ValueError(f"{key}: use a finite reading")
return value
def analyze_row(row):
label = row["load"]
if label not in NOMINAL:
raise ValueError("load must be 22k or 100k")
r, c, period, dt, peak, valley, aa, va, ab, vb = (
finite(row, k) for k in FIELDS[1:-1]
)
if not 0.95 * NOMINAL[label] <= r <= 1.05 * NOMINAL[label]:
raise ValueError("resistor value or label does not match the fixture")
if not 90 <= c <= 110:
raise ValueError("enter the 100 nF capacitor value in nanofarads")
if not 0.95 <= period <= 1.05 or not 0.20 <= dt <= 0.75 * period + BOUNDARY_EPS:
raise ValueError("check the 1 kHz period and the cursor interval in milliseconds")
if row["reverse_segment_confirmed"] != "yes":
raise ValueError("verify the entire interval on both traces before entering yes")
if not 0 < valley < peak <= 2.3:
raise ValueError("check for a resolved positive hold peak and valley")
if not valley - 0.02 <= vb <= va <= peak + 0.02 or vb <= 0:
raise ValueError("hold cursors must follow a non-rising part of the waveform")
if not all(-2.5 <= v <= 2.5 for v in (aa, ab)):
raise ValueError("anode cursor outside the fixture voltage range")
if min(va - aa, vb - ab) < 0.050 - BOUNDARY_EPS:
raise ValueError("anode must be at least 50 mV below hold at both cursors")
effective_r = 1.0 / (1.0 / r + 1.0 / 10_000_000)
tau_ms = effective_r * c * 1e-6
predicted_b = va * math.exp(-dt / tau_ms)
tau_observed = None
drop = va - vb
if drop >= 0.005 - BOUNDARY_EPS:
tau_observed = dt / -math.log1p((vb - va) / va)
return {
"load": label,
"effective_resistance_ohm": effective_r,
"nominal_model_tau_ms": tau_ms,
"observed_window_tau_ms": tau_observed,
"observed_cycle_ripple_v": peak - valley,
"observed_window_drop_v": drop,
"predicted_hold_b_v": predicted_b,
"hold_b_minus_prediction_v": vb - predicted_b,
}
def analyze(stream):
reader = csv.DictReader(stream, strict=True)
if reader.fieldnames != list(FIELDS):
raise ValueError("use the header from the blank worksheet")
seen = {}
for row in reader:
try:
if None in row:
raise ValueError("too many columns")
result = analyze_row(row)
if result["load"] in seen:
raise ValueError("duplicate load row")
seen[result["load"]] = result
except ValueError as exc:
raise ValueError(f"line {reader.line_num}: {exc}") from exc
if set(seen) != set(NOMINAL):
raise ValueError("enter one complete row for each load")
return {
"assumptions": "Measured reverse interval, resistor and 10 MΩ probe only; no diode model or bench assertion.",
"rows": [seen[key] for key in NOMINAL],
}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("worksheet")
args = parser.parse_args()
try:
with open(args.worksheet, encoding="utf-8-sig", newline="") as stream:
result = analyze(stream)
except (OSError, UnicodeError, csv.Error, ValueError) as exc:
parser.exit(2, f"{exc}\n")
print(json.dumps(result, indent=2, allow_nan=False))
if __name__ == "__main__":
main()
Why might the 100 kΩ load make the circuit worse at following a falling input amplitude, even when it gives less ripple on a steady sine?
The larger resistance removes charge more slowly. The capacitor keeps a larger fraction of the previous high peak until it can discharge. An input with falling peaks may then stay below the held voltage for more than one cycle, so the output responds late. That lag is the same storage effect that reduced ripple with a steady input.
Download the experiment files: full program, host tests, blank worksheet and original circuit drawing.
See the voltmeter loading experiment for a circuit where the instrument’s input is a much bigger part of the discharge path. That setup begins with one stored charge; this detector periodically recharges through a diode.