A cable can make one voltage step look like two. Send the same low-voltage square wave down two lengths of 50 ฮฉ coax. Observe the launch and its echo at one oscilloscope input. On each cable, replace the open end with a 50 ฮฉ terminator and watch the echo shrink. Use the change in round-trip time to estimate how fast a voltage disturbance travels along the cable.
Why the second step returns
Coaxial cable has an inner conductor and a surrounding shield. Together they carry the signal and its return. Its characteristic impedance, Zโ, is the ratio of voltage to current in a single travelling wave. A multimeter reading between the center contact and shield of an open cable does not give Zโ. Just after a step is launched, the source sees roughly the cable’s characteristic impedance until information about the far end has had time to return.
Time-domain reflectometry (TDR) applies a fast change and watches how the cable sends part of it back. At a resistive far end the ideal voltage reflection coefficient is ฮ = (Rโ โ Zโ) / (Rโ + Zโ), where Rโ is the load resistance. An open end has ฮ = +1 in the ideal limit. A 50 ฮฉ load on a 50 ฮฉ line has ฮ = 0. The load matters only after the incident wave reaches it and the returning wave reaches the scope. The short source feed and the tee are part of the launch fixture. Real connectors, scope capacitance, source mismatch and frequency-dependent cable loss can all add or reshape small steps.
The Belden 8259 table gives a nominal velocity factor of 0.66. That number is speed along the cable divided by the speed of light in vacuum, c = 299,792,458 m/s. A round trip on length L gives t = 2L/(0.66c), about 50.54 ns at 5 m and 151.62 ns at 15 m. The test compares the time between steps on the same scope channel, so the trigger need not start at precisely the same position in every capture.
Connect one launch node

tricky-circuit-campaigns133-138/batch-014/packages/coax-echo-round-trip/functional.svg)Use a SIGLENT SDG1032X generator, a SIGLENT SDS1104X-E oscilloscope, a 50 ฮฉ BNC (Bayonet NeillโConcelman) plug-to-plug feed of at most 30 cm, and an Amphenol RF 031-208-RFX tee. Use 5 m and 15 m nominal lengths of Belden 8259 cable from the same stock, with 50 ฮฉ BNC plugs. For bare cable the manufacturer of the Amphenol RF 031-351 straight crimp plug lists Belden 8259 as compatible. Order or use correctly assembled leads. Check continuity of each end-to-end conductor and no center-to-shield short with both ends disconnected. A 202120 50 ฮฉ BNC jack terminator mates to the far plug.
| Port | Connection | Setting or role |
|---|---|---|
| Generator CH1 | Short feed into one tee jack | 100 kHz square, 1.00 V peak-to-peak and +0.50 V offset with the load display set to 50 ฮฉ |
| Scope CH1 | Tee plug directly into CH1 | 1 Mฮฉ input, direct-current (DC) coupling, channel attenuation 1ร |
| Cable near end | Other tee jack | Install one length at a time. Keep both shield and center continuous. |
| Cable far end | A: open plug. B: 202120 on the same plug. | Use one 50 ฮฉ load at the far end only. |
The generator has a physical 50 ฮฉ source. Its 50 ฮฉ display setting reports the voltage expected into a matching load. An ideal matching line initially sees the displayed 1 V step. An ideal open return adds another 1 V at the tee after the round trip. In the matched condition, a 1 V high plateau across 50 ฮฉ dissipates 20 mW during the high interval and about 10 mW averaged at 50% duty. Use the specified 1 W terminator and keep this a 0โ2 V nominal bench experiment with no external circuit connected. The scope’s 1 Mฮฉ input includes a nominal 17 pF input capacitance in this four-channel model. It loads fast edges even though its DC resistance is high. Avoid a second termination at the tee or a long branch to the scope.
Capture matched and open ends
- With output disabled, connect the fixture and the short cable with the far-end terminator. Use both instruments with their normal protective-earth connections. The shells share the instrument return. Use only the loose bench cable and disconnect any external circuit, antenna, power injector or source. Do not float either instrument. Measure each length between near and far plug reference planes and record how well you know it.
- Enable generator CH1 and confirm the approximate 0-to-1 V matched step at the tee. Use the generator’s Square function at 50% duty. Its table lists a 4.2 ns maximum edge at 1 V peak-to-peak into 50 ฮฉ for Square under the stated condition. Pulse mode has different edge limits. Set the scope’s 20 MHz limit off, run only CH1, and use normal sample acquisition. Set a rising CH1 trigger near 0.5 V. Use about 50 ns per division and adjust the horizontal position so the exported trace spans at least โ35 to +300 ns relative to the trigger. Verify the acquired sample interval is at most 2 ns. Use a range that shows at least โ0.25 to +2.25 V without clipping.
- Save the matched trace as a comma-separated values (CSV) file. Disable the output before removing the terminator. Enable output again, keep the same instrument settings and save the open trace. The initial step should still be near 1 V. A later rise is expected for the open cable. If there is clipping, ringing past the intended low-voltage range, a missing step or a large matched echo, stop and check termination, tee placement, and scope settings before using the file.
- Repeat both configurations on the long cable. Refit the same terminator and use the same feed and tee. Record at least three fresh four-capture sets, with the same settings and reconnections where practical. Keep originals and the blank observations sheet as separate files. A changed time base, end connection or lead changes the comparison.
Use the scope’s Save/Recall CSV function and retain the instrument settings with the raw acquisition. Copy the actual CH1 samples into two columns headed time_ns,voltage_v. Keep real time relative to the recorded trigger. Multiply seconds by 109 if the export uses seconds. Use volts, decimal points and comma delimiters. Retain every sample in the chosen window and use a contiguous export at the required spacing. If the display CSV is downsampled, save more acquisition points and keep the contiguous subset around the trigger. Keep at most 100,000 rows. Do not interpolate or fill gaps before analysis.
Read the two delays

tricky-circuit-campaigns133-138/batch-014/packages/coax-echo-round-trip/echo-example.svg)For each trace the program takes median voltages on baseline and incident plateaus at โ30 to โ10 ns and +15 to +30 ns. It interpolates the launch midpoint between adjacent real samples. The expected time T comes from measured length and the 0.66 nominal factor. Relative to the launch midpoint, the program looks for the positive open-echo midpoint in 0.70โ1.30 ร T and takes its late plateau from 1.38โ1.55 ร T. These windows bracket this fixture’s first return. A long settling time or strong connector ringing can make the assumptions fail. The script rejects a noisy plateau, a missing or multiply qualified crossing, inconsistent launch steps, a large matched residual and a transient in the expected matched echo window if its excursion exceeds 15% of the matched incident step. Its voltage and residual limits are practical checks for this experiment, not universal cable tolerances.
The open-echo fraction divides the measured change by the incident step. Its value can fall below the ideal +1 because the signal travels the cable twice and the scope and launch circuit have finite bandwidth. The signed matched residual is useful as a control. Use delay, rather than echo height, for this propagation estimate. Even if both midpoints are repeatable, the two cables can have different edge shapes and different connector effects.
Use v/c = 2(Lโ โ Lโ) / [c(tโ โ tโ)], converting ns to seconds. The constructed traces give 50.5 and 151.5 ns, so ฮt = 101.0 ns and v/c โ 0.661. A fixed additive delay bias cancels in the difference only if shared between the two measurements. An illustrative length difference of 10.00 ยฑ 0.10 m and time difference of 101 ยฑ 3 ns would give a corner interval of about 0.635โ0.688. Those example bounds are inputs to arithmetic, not instrument uncertainty or a calibration claim. Derive bounds from your own length readings, acquisition and repeatability. Use the blank observations sheet (ZIP; file: tricky-circuit-campaigns133-138/batch-014/packages/coax-echo-round-trip/observations-blank.csv) for actual results. This article contains no bench readings.
Run the complete Python analyzer
Use Python 3.10 or later with its standard library. The four input files are ordered short-open, short-matched, long-open, long-matched. The short measured length must be 4.8โ6.0 m and the long 13.0โ17.0 m for these time windows. The provenance field is your choice, and the software cannot verify how a file was acquired. First run the constructed example using the command in the run notes (ZIP; file: tricky-circuit-campaigns133-138/batch-014/packages/coax-echo-round-trip/README-experiment.txt), then replace all four files and set --kind bench for your own captures.
"""Estimate coax echo delay from a four-capture, open/matched bench comparison."""
import argparse
import csv
import json
import math
from pathlib import Path
import statistics
LIGHT_M_S = 299_792_458
NOMINAL_FACTOR = 0.66
MAX_GAP_NS = 2.0
MAX_ROWS = 100_000
ROUNDING_EPS = 1e-9 # Numerical allowance at inclusive voltage and ratio limits.
def read_trace(path):
points = []
with path.open(newline="", encoding="utf-8-sig") as source:
rows = csv.reader(source, strict=True)
if next(rows, None) != ["time_ns", "voltage_v"]:
raise ValueError(f"{path}: expected time_ns,voltage_v header")
for line, row in enumerate(rows, start=2):
if len(row) != 2 or len(points) >= MAX_ROWS:
raise ValueError(f"{path}:{line}: expected two values and at most {MAX_ROWS} rows")
try:
time, voltage = map(float, row)
except ValueError as error:
raise ValueError(f"{path}:{line}: expected numeric samples") from error
if not (math.isfinite(time) and math.isfinite(voltage)):
raise ValueError(f"{path}:{line}: samples must be finite")
if points:
gap = time - points[-1][0]
if gap <= 0:
raise ValueError(f"{path}:{line}: time must be strictly increasing")
if gap > MAX_GAP_NS + 1e-6:
raise ValueError(f"{path}:{line}: sample gap exceeds 2 ns")
if abs(voltage) > 3.0:
raise ValueError(f"{path}:{line}: outside the low-voltage fixture range")
points.append((time, voltage))
if len(points) < 40:
raise ValueError(f"{path}: too few samples")
return points
def plateau(points, low, high, label):
if points[0][0] > low or points[-1][0] < high:
raise ValueError(f"trace does not cover {label} window")
values = [v for t, v in points if low <= t <= high]
if len(values) < 4 or max(values) - min(values) > 0.08 + ROUNDING_EPS:
raise ValueError(f"{label} window needs four samples on a stable plateau")
return statistics.median(values)
def rising_midpoint(points, low, high, threshold, label):
crossings = []
for i in range(1, len(points) - 2):
t0, v0 = points[i - 1]
t1, v1 = points[i]
if v0 < threshold <= v1 and all(v >= threshold for _, v in points[i:i + 3]):
crossing = t0 + (t1 - t0) * (threshold - v0) / (v1 - v0)
if low <= crossing <= high:
crossings.append(crossing)
if len(crossings) != 1:
raise ValueError(f"expected one stable {label} midpoint in its search window")
return crossings[0]
def inspect_trace(path, expected_ns):
points = read_trace(path)
baseline = plateau(points, -30, -10, "baseline")
incident = plateau(points, 15, 30, "incident")
step = incident - baseline
if not 0.65 - ROUNDING_EPS <= step <= 1.35 + ROUNDING_EPS:
raise ValueError(f"{path}: incident step should be 0.65 to 1.35 V")
launch = rising_midpoint(points, -10, 15, baseline + step / 2, "launch")
late = plateau(points, launch + 1.38 * expected_ns, launch + 1.55 * expected_ns, "post-return")
return points, launch, incident, step, late
def compare(open_path, matched_path, length_m):
expected = 2e9 * length_m / (LIGHT_M_S * NOMINAL_FACTOR)
points, launch, incident, open_step, late = inspect_trace(open_path, expected)
matched, matched_launch, matched_incident, matched_step, matched_late = inspect_trace(
matched_path, expected,
)
if abs(open_step - matched_step) > 0.07 + ROUNDING_EPS:
raise ValueError("incident steps differ by more than 0.07 V")
delta = late - incident
open_fraction = delta / open_step
matched_fraction = (matched_late - matched_incident) / matched_step
if not 0.35 - ROUNDING_EPS <= open_fraction <= 1.20 + ROUNDING_EPS:
raise ValueError("open echo must be positive and between 0.35 and 1.20 of the incident step")
if abs(matched_fraction) > 0.15 + ROUNDING_EPS:
raise ValueError("matched residual exceeds 0.15 of the incident step")
matched_window = [
abs(v - matched_incident) / matched_step for t, v in matched
if matched_launch + 0.70 * expected <= t <= matched_launch + 1.30 * expected
]
peak_matched_excursion = max(matched_window)
if peak_matched_excursion > 0.15 + ROUNDING_EPS:
raise ValueError("matched window excursion exceeds 0.15 of the incident step")
echo = rising_midpoint(
points, launch + 0.70 * expected, launch + 1.30 * expected,
incident + delta / 2, "echo",
)
return {
"length_m": length_m,
"nominal_delay_ns": expected,
"echo_delay_ns": echo - launch,
"incident_step_v": open_step,
"observed_open_echo_fraction": open_fraction,
"observed_matched_residual_fraction": matched_fraction,
"matched_window_peak_fraction": peak_matched_excursion,
}
def main():
parser = argparse.ArgumentParser(description=__doc__)
for label in ("short_open", "short_match", "long_open", "long_match"):
parser.add_argument(label, type=Path)
parser.add_argument("--short-m", type=float, required=True)
parser.add_argument("--long-m", type=float, required=True)
parser.add_argument("--kind", choices=("synthetic", "bench"), required=True,
help="Provenance supplied by the operator")
args = parser.parse_args()
try:
if not 4.8 <= args.short_m <= 6.0:
raise ValueError("short length must be 4.8 to 6.0 m")
if not 13.0 <= args.long_m <= 17.0:
raise ValueError("long length must be 13.0 to 17.0 m")
short = compare(args.short_open, args.short_match, args.short_m)
long = compare(args.long_open, args.long_match, args.long_m)
separation = long["echo_delay_ns"] - short["echo_delay_ns"]
if separation <= 0:
raise ValueError("long cable return must arrive later")
factor = 2e9 * (args.long_m - args.short_m) / (LIGHT_M_S * separation)
if not 0 < factor < 1:
raise ValueError("inferred speed outside the physical range")
except (OSError, UnicodeError, csv.Error, ValueError) as error:
parser.error(str(error))
print(json.dumps({
"input_kind": args.kind, "short": short, "long": long,
"delay_difference_ns": separation,
"two_length_velocity_factor": factor,
}, indent=2, allow_nan=False))
if __name__ == "__main__":
main()
A valid run returns a JSON record including both delays, both open fractions, both signed matched residuals, the largest matched excursions in the return windows and the two-length estimate. A failed check exits with an explanation and no result. Inspect the traces and record what happened. If your time axis or capture differs from this fixture, change the acquisition setup before interpreting the result.
Challenge
Both open traces rise near 50 ns, even with the 15 m cable installed. Can you use 50 ns to estimate propagation for the long cable? What would the matched control and the short feed tell you?
Show the reasoning
The expected far-end return is near 152 ns on the 15 m cable. A step that appears at the same delay on two different lengths may come from a common connection or an acquisition artifact. If it survives the matched control, it is poor evidence for an open-end return. Verify the cable under test, shorten the fixed feed, and look for a return that moves with length and shrinks with far-end termination.
For another experiment that qualifies a fast edge before using it, see Pico PIO capture and its input waveform checks.
Downloads and primary sources
- Analyzer (ZIP; file:
tricky-circuit-campaigns133-138/batch-014/packages/coax-echo-round-trip/coax_echo.py) ยท Reader archive with both editable figures, all traces and tests (ZIP; file:tricky-circuit-campaigns133-138/batch-014/packages/coax-echo-round-trip/experiment-tests-v1.zip). - Tektronix TDR primer. Reflection coefficient, propagation and test fixture effects.
- Belden 8259 datasheet. Nominal impedance, propagation factor and frequency-dependent loss.
- SIGLENT SDG1000X datasheet and load setting note. Square output timing and amplitude convention.
- SIGLENT SDS1000X-E datasheet and user manual. CH1 input, sample rate and CSV storage.
- Amphenol RF 031-208-RFX tee, 031-351 cable compatibility listing and 202120 far-end load.
Download the complete reader files (ZIP): full programs, blank observation worksheets, original editable SVG/PNG diagrams and selected support files for coax-echo-round-trip. Inline main programs remain complete; this download is supplementary.



