Generated banner for Matlab Detect Capture Usrp
Generated logo for Matlab Detect Capture Usrp

Matlab Detect Capture Usrp

MathWorksOfficialCreated by MathWorks

  • Skill
  • Tool integration
  • Software development
  • BSD-2-Clause
  • No issues found

From the author

Smart triggered RF capture on NI USRP radios with Wireless Testbench — record only when a signal of interest appears, not continuously (triggered spectrum sensing). Use energy detection to capture when signal power rises above the noise floor, or preamble detection to cross-correlate against a known sequence (WLAN L-LTF, 5G NR PSS/SSS, LTE PSS, Zadoff-Chu, custom protocols) and capture only when that protocol's preamble is detected. Use when implementing triggered (wake-on-signal) capture or spectrum sensing, capturing protocol-based signals via cross-correlation, calibrating detection thresholds (plotDetectionSignals / plotThreshold), scanning frequency bands for activity, or building transmit-then-detect workflows.
— the description by MathWorks

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured workflow for implementing triggered RF capture on NI USRP radios using the MATLAB Wireless Testbench. It enables users to move beyond continuous streaming by configuring detectors that record data only when specific conditions are met, such as signal energy rising above a noise floor or the detection of a known protocol preamble. The skill includes support for adaptive and fixed thresholding, multi-capture sequences, and transmit-then-detect loopback testing, while providing a systematic approach to calibrating detection thresholds when performance is inconsistent.

What it does

  • Configures energy-based triggers for wake-on-signal recording.
  • Implements preamble-based triggers using cross-correlation for protocols like 5G NR, LTE, and WLAN.
  • Supports transmit-then-detect workflows for hardware loopback verification.
  • Provides diagnostic sub-workflows for calibrating detection thresholds.
  • Enables multi-capture sequences with sample-clock timestamps.
  • Facilitates frequency-scanning loops for spectrum activity monitoring.

When to use it

  • When you need to capture specific RF events rather than continuous IQ data.
  • When you have a known preamble sequence to trigger on.
  • When you need to verify signal detection thresholds using visual calibration tools.
  • When performing transmit-then-detect testing on a single radio.
  • When scanning multiple frequency bands for signal activity.

Advantages

  • Reduces data storage requirements by avoiding continuous recording.
  • Improves signal capture reliability through adaptive thresholding.
  • Simplifies hardware testing with integrated transmit-and-detect capabilities.
  • Provides clear diagnostic paths for tuning detection parameters.
  • Ensures consistent radio resource management through proper object lifecycle handling.

Usage examples

“I need to capture a signal on my USRP radio 'usrp1' when the energy rises above the noise floor. Use an adaptive threshold.”
The agent generates a MATLAB script using energyDetector with adaptive thresholding, configured for the specified radio, and includes a verification block to report the detection status.
“My preamble detection is failing to trigger on my 5G NR signal. How can I fix it?”
The agent guides you through the threshold calibration sub-workflow, using plotThreshold to visualize the correlation output and adjust the adaptive gain or offset.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/matlab/matlab-detect-capture-usrp/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip matlab-detect-capture-usrp.zip -d .agents/skills

Documentation by MathWorks

Detect and Capture RF Signals on NI USRP Radios

Reference Loading

  • Code patterns (required at Step 4): for the seven copy-ready capture patterns (A–G) and the loopback table, Read references/patterns.md before generating any capture code.

  • API reference (required at Step 3): Read references/api.md for signatures, property ranges, and threshold constraints. Every generation validates parameters against it before emitting code — see the Always rule below. This read is not optional.

  • On demand: references/overview.md — conceptual architecture and detector behavior.

  • On demand, after a failure only: references/threshold-calibration.md — the calibration sub-workflow (plotDetectionSignals / plotThreshold step-by-step). Do not load it during first-time code generation.

Sub-Workflows

This skill exposes one sub-workflow that is loaded on demand from references/:

Sub-workflowWhat it provides
Threshold Calibration (references/threshold-calibration.md)Step-by-step plot -> diagnose -> tune procedure for both energyDetector and preambleDetector, plus combined symptom -> cause -> fix table
Trigger: when to route into Threshold Calibration

Route into references/threshold-calibration.md whenever any of these conditions hold:

  1. No detection — generated Pattern returned status == 0 (capture timed out, no data captured) and the user expected a signal to fire it. This is the primary trigger.

  2. Wrong detection — Pattern returned status >= 1 but the captured data is wrong: noise instead of signal, baseline instead of preamble, saturated waveform (max(abs(data)) near 1.414), or sanity ratio fails (r < 50 for energyDetector, r < 100 for preambleDetector).

  3. Unstable detection — the user reports, or an earlier run in this session already showed, that the Pattern fires inconsistently (sometimes status=0, sometimes status=1); thresholds are sitting on a jittery boundary. Do not run extra captures hunting for this — the Always rule below caps a clean run at one.

  4. User explicitly asks to "calibrate", "tune thresholds", "tune detection", "fix detection", "why didn't it detect", "diagnose", or names plotDetectionSignals / plotThreshold.

Sanity ratio r — where it comes from. Every Pattern's %% Verify block computes and prints r, so triggers 2 and 3 are read straight off the pattern's own output. No extra run is needed.

DetectorFormula the pattern printsReal signal
energyDetector (A/B/E/F)r = max(abs(data).^2) / median(abs(data).^2)r >= 50
preambleDetector (C/D/G)same ratio on the matched-filter output, filter(conj(flipud(pd.Preamble)), 1, data)r > 100
Wideband chirp / OFDM (F)raw r under-reports — 15-20 is normal; the pattern also prints a matched-filter ratiomatched-filter ratio >> 100

Do not route into the sub-workflow for first-time code generation — generate the Pattern from this SKILL.md, run it, and only route into calibration on a failure outcome. The sub-workflow assumes a detector object is already configured and a TX or external signal is present.

If the failure mode is RF-path (no peaks at all on the figure, wrong frequency, broken cable, wrong antenna name), the sub-workflow exits early back to this skill — calibration cannot fix RF problems.

Generate working MATLAB code to detect and capture RF signals using energy detection or preamble detection triggers on NI USRP radios with Wireless Testbench.

Prerequisites

Before this skill applies, the user must have:

  • MATLAB R2022a+ (preambleDetector) or R2023b+ (energyDetector)

  • Wireless Testbench toolbox installed

  • Wireless Testbench Support Package for NI USRP Radios installed

  • Radio previously configured using the Radio Setup wizard

  • Physical NI USRP radio connected and validated

When to Use

Trigger this skill when the user wants smart/triggered capture — record only when something interesting appears on the air, not continuously:

  • Capture only when signal energy rises above the noise floor (energyDetector, Patterns A/B/E/F) — wake-on-signal recording, opportunistic capture, transmit-then-detect verification

  • Capture only when a known protocol preamble is cross-correlated and matched (preambleDetector, Patterns C/D/G) — WLAN L-LTF, 5G NR PSS/SSS, LTE PSS, Zadoff-Chu, custom sync sequences

  • Calibrate detection thresholds (plotDetectionSignals for energyDetector, plotThreshold for preambleDetector)

  • Scan multiple frequencies / channels for activity (e.g., WLAN channel scan across 2.4 GHz band)

  • Transmit a test waveform from the same radio and capture it back upon detection (loopback verification)

  • Capture multiple consecutive triggered signals with sample-clock timestamps

When NOT to Use

Route to another skill if the user's goal is:

  • Untriggered capture (immediate IQ capture without detection) -> matlab-transmit-capture-usrp

  • Continuous streaming (System objects, real-time processing loops) -> use the matlab-read-documentation skill for "Live Data I/O" (Wireless Testbench)

  • Radio setup or troubleshooting -> matlab-set-up-usrp-radio

  • Clock/time synchronization -> use the matlab-read-documentation skill for "Radio Management" (Wireless Testbench)

  • FPGA targeting -> use the matlab-read-documentation skill for "Target NI USRP Radios" (Wireless Testbench)

Decision Tree

User request
|
|-- Mentions "untriggered capture", "immediate capture", "basebandReceiver"
|     -> REDIRECT to matlab-transmit-capture-usrp
|
|-- Mentions "System object", "streaming loop", "real-time processing"
|     -> REDIRECT: use matlab-read-documentation for "Live Data I/O" (Wireless Testbench streaming)
|
|-- Wants triggered capture (detection-based)
|     |
|     |-- Knows signal structure (preamble sequence available)
|     |     |-- Adaptive threshold -> Pattern C
|     |     |-- Fixed threshold -> Pattern D
|     |     |-- Wants to calibrate threshold -> Pattern D + plotThreshold
|     |     |-- Frequency scanning loop -> Pattern G
|     |
|     |-- Explicit energy/power cue (energy rise, power increase), no preamble
|     |     |-- Adaptive threshold (energy delta + minimum) -> Pattern A
|     |     |-- Fixed threshold -> Pattern B
|     |     |-- Wants to calibrate threshold -> Pattern A/B + plotDetectionSignals
|     |     |-- Multiple captures with timestamps -> Pattern E
|     |     |-- Transmit-then-detect workflow -> Pattern F
|     |
|     |-- Ambiguous: no energy/power cue, no preamble, no parameters given
|           -> ASK FIRST (do not assume energy): "Do you have a known preamble
|              sequence to correlate against, or do you want to trigger on any
|              signal energy increase? Also, what threshold values or detection
|              parameters would you like to use?"

Code Generation Steps

Key Functions
FunctionPurposeToolboxAvailable From
energyDetectorArm radio; trigger capture when energy rises above the noise floorWireless TestbenchR2023b
preambleDetectorArm radio; trigger capture on correlation with a known preambleWireless TestbenchR2022a
captureBlocking triggered IQ capture (returns on detection or timeout)Wireless Testbenchwith detector object
capture(..., "NumCaptures", N)Capture N consecutive triggered signals (Pattern E)Wireless TestbenchR2024a
plotDetectionSignalsCalibrate energy threshold — energyDetector onlyWireless TestbenchR2023b
plotThresholdCalibrate preamble threshold — preambleDetector onlyWireless TestbenchR2022a
transmit / stopTransmissionSend / stop a test waveform on the detector object (Pattern F)Wireless Testbenchwith detector object
zadoffChuSeqGenerate a Zadoff-Chu preamble sequence (Patterns C/D)Communications ToolboxR2012b
wlanLLTF / wlanNonHTConfigBuild the WLAN L-LTF preamble (Pattern G)WLAN ToolboxR2015b
chirpGenerate a test chirp waveform (Pattern F)Signal Processing Toolboxbefore R2006a
Step 1: Determine Detector Type

Select on an explicit cue, not on the mere absence of a preamble.

SignalUser saysDetector
Energy-basedexplicit energy/power cue: "energy", "power increase/rise", "amplitude above the noise floor"energyDetector
Preamble-based"preamble", "Zadoff-Chu", "L-LTF", "PSS/SSS", "correlation", a known sequencepreambleDetector
Ambiguousonly "detect a signal" / "capture when it appears" — no energy/power cue, no preamble, no threshold parametersAsk first (Guardrails -> Ask First); do not default to energy
Step 2: Gather Required Inputs
ParameterRequiredAsk if missing
Radio nameYesAlways
Center frequencyYesYes
Sample rateYesYes
Threshold methodYesYes (adaptive/fixed)
Threshold valuesYesYes
Capture durationYesYes
TimeoutYesDefault seconds(1) if not specified
Preamble sequenceOnly for preambleDetectorYes
Step 3: Validate Constraints

Check parameter values against valid ranges before generating code. See references/api.md for complete constraint tables (WindowLength, FixedThreshold, Preamble length, TriggerOffset, AdaptiveThresholdGain, AdaptiveThresholdOffset).

If a value violates these constraints, inform the user and suggest the valid range. Do NOT generate code with invalid values.

Step 4: Generate Code

The seven copy-ready patterns (A–G) live in references/patterns.md. Read references/patterns.md now, then use the pattern the Decision Tree selected. Each pattern ships with concrete default values — replace the values marked % <- set with the user's radio and signal parameters, and validate them against the references/api.md ranges (Step 3) before running. For loopback bring-up (transmit-then-detect), follow the Local Hardware Testing table in that file.

PatternDetectorThresholdUse case
AenergyDetectoradaptiveWake-on-signal capture on an energy rise
BenergyDetectorfixedCapture above a fixed power level
CpreambleDetectoradaptiveCapture on correlation with a known preamble
DpreambleDetectorfixedPreamble capture / plotThreshold calibration
EenergyDetectoradaptiveMultiple consecutive captures with timestamps
FenergyDetectoradaptiveTransmit-then-detect (loopback)
GpreambleDetectoradaptiveFrequency-scanning loop (WLAN channel scan)

If a generated pattern fires unreliably (timeout despite a real signal, false positives, or captures of noise/baseline), route into the threshold calibration sub-workflow — see the per-pattern calibration notes in references/patterns.md and the procedure in references/threshold-calibration.md.

Guardrails

Organized by when they apply: Always rules fire on every generation, Ask First rules pause for the user, Never rules trigger a refusal and redirect.

Always
  • Use a radioName variable from user input — never hardcode radio names.

  • Begin every pattern with clear ed pd — releases any prior radio lease regardless of detector class. The same physical radio can be held by either an energyDetector or a preambleDetector object; clearing only one variable name does not release a lease held by the other, which causes validateLeaseOwner errors when patterns are pasted sequentially into the same MATLAB session.

  • Validate threshold ranges before generating code — see references/api.md for exact valid ranges per parameter (key: energyDetector FixedThreshold [0, 8191], preambleDetector FixedThreshold [0, 4095]). If a value is out of range, state the valid range and do not emit the code.

  • End every generated code block with a %% Verify section that reports the outcome and prints the sanity ratio r (formulas in the table above). Do NOT hard-assert on status: a timeout (status == 0) is an expected result to handle (warn + route to calibration), not a crash — a hard assert makes a no-signal bench run throw.

  • Run the generated script once — do not re-run it to "confirm." This caps whole-script runs, not the loops inside one run: let Pattern E complete all NumCaptures captures and let Pattern G finish every channel in its scan loop. Once %% Verify reports success (status == 1, or status > 0 for multi-capture) and the printed r clears its threshold — or, for a wideband chirp/OFDM signal, the matched-filter ratio confirms it — the task is done. Do NOT execute the full deliverable again end-to-end or re-open plotDetectionSignals / plotThreshold to double-check — each extra radio round-trip costs real seconds and risks a timeout. Only exception: the user reports earlier inconsistent firing (sub-workflow trigger 3), where a short repeat is allowed — see Exit Criteria in references/threshold-calibration.md.

  • Use transmit() / stopTransmission() on the detector object for transmit-then-detect — do NOT create a separate basebandTransmitter.

  • Set ThresholdMethod before calling transmit() — you cannot switch fixed↔adaptive during a continuous transmission (it errors: "stop the ongoing transmission first"). MinimumEnergy, EnergyDeltaThreshold, and FixedThreshold can be tuned live, so sweep those without stopping.

  • Match the calibration plot to the detector class — plotDetectionSignals is energyDetector-only; plotThreshold is preambleDetector-only. Never cross them.

  • Normalize any custom or random preamble into [-1, 1] before assigning pd.Preamble. Preamble elements must lie in [-1, 1] (references/api.md). Zadoff-Chu via seq / norm(seq, 2) already satisfies this; for a random or protocol-derived sequence, scale it with preamble = preamble / max(abs(preamble)) — raw randn/sample values overflow [-1, 1] and error at assignment.

Ask First
  • When the detection method is unstated, ask before generating — do not default to energy detection. The absence of a preamble in the request is not evidence for energy detection. If the request names no explicit energy/power cue, no preamble/correlation sequence, and no threshold parameters, ask one question covering both the method (energy vs. preamble) and its parameters, then proceed. If the method is clear and only parameters are missing, ask once for those.

Never
  • Never generate untriggered capture — if the user wants immediate capture without detection, redirect to matlab-transmit-capture-usrp.

  • Never generate streaming code — if the user wants continuous real-time processing, redirect to the streaming workflow (use the matlab-read-documentation skill for "Live Data I/O", Wireless Testbench).


Copyright 2026 The MathWorks, Inc.


Files

references/patterns.md

Code Patterns (A–G)

Copy-ready capture patterns for matlab-detect-capture-usrp. Pick the pattern the Decision Tree in SKILL.md selected, then:

  • Replace every value marked % <- set with the user's radio and signal parameters.

  • Validate those values against references/api.md ranges (SKILL.md Step 3) before running.

  • For loopback bring-up (SMA cable, transmit-then-detect), use the Local Hardware Testing table at the end of this file.

PatternDetectorThresholdUse case
AenergyDetectoradaptiveWake-on-signal capture on an energy rise
BenergyDetectorfixedCapture above a fixed power level
CpreambleDetectoradaptiveCapture on correlation with a known preamble
DpreambleDetectorfixedPreamble capture / plotThreshold calibration
EenergyDetectoradaptiveMultiple consecutive captures with timestamps
FenergyDetectoradaptiveTransmit-then-detect (loopback)
GpreambleDetectoradaptiveFrequency-scanning loop (WLAN channel scan)

Pattern A: Energy Detection with Adaptive Threshold

%% Energy Detection - Adaptive Threshold
% Detect and capture signal when energy increases above threshold.
clear ed pd
radioName = "MyN310";              % <- set: your radio configuration name
ed = energyDetector(radioName);

% Configure RF parameters
ed.CenterFrequency = 2.45e9;       % <- set: Hz
ed.SampleRate = 30.72e6;           % <- set: Hz
ed.RadioGain = 30;                 % <- set: dB (device-dependent range)
ed.CaptureDataType = "double";

% Configure adaptive threshold
ed.ThresholdMethod = "adaptive";
ed.MinimumEnergy = 0.1;            % <- set: energy floor [0, 8191]; use 0 over loopback (let the dB delta trigger)
ed.EnergyDeltaThreshold = 3;       % <- set: dB rise that triggers capture
ed.WindowLength = 300;             % <- set: integration window [0, 4095]

% Optional: visualize energy curves before capturing (TX must be running for transmit-then-detect)
% plotDetectionSignals(ed, milliseconds(2));

% Capture data upon detection
[data, timestamp, droppedSamples, status] = capture(ed, milliseconds(5), seconds(2));

%% Verify
if status == 1
    r = max(abs(data).^2) / median(abs(data).^2);   % sanity ratio: real signal r >= 50
    fprintf("Captured %d samples at %s (dropped: %d). Sanity ratio r = %.1f (>= 50 = real signal).\n", ...
        numel(data), string(timestamp), droppedSamples, r);
    if r < 50
        warning("Sanity ratio below 50 - this looks like noise, not signal; see references/threshold-calibration.md.");
    end
else
    warning("No detection before timeout (status 0) - expected with no active signal; to tune see references/threshold-calibration.md.");
end
Pattern A — Calibration

If Pattern A fires unreliably (timeouts despite a real signal, false positives on noise, or captures of pure baseline), route into the threshold calibration sub-workflow:

-> Load references/threshold-calibration.md (Sub-Workflow A: energyDetector Calibration)

The sub-workflow walks plotDetectionSignals -> diagnose curves -> tune MinimumEnergy / EnergyDeltaThreshold -> post-capture sanity ratio in four steps, with a combined symptom-cause-fix table at the end.

Pattern B: Energy Detection with Fixed Threshold

%% Energy Detection - Fixed Threshold
% Detect and capture signal when energy exceeds fixed level.
clear ed pd
radioName = "TestX410";            % <- set: your radio configuration name
ed = energyDetector(radioName);

% Configure RF parameters
ed.CenterFrequency = 3.5e9;        % <- set: Hz
ed.SampleRate = 122.88e6;          % <- set: Hz
ed.RadioGain = 30;                 % <- set: dB (device-dependent range)
ed.CaptureDataType = "double";

% Configure fixed threshold
ed.ThresholdMethod = "fixed";
ed.FixedThreshold = 500;           % <- set: fixed power threshold [0, 8191]; over loopback, measure it (see Loopback) — 500 is an OTA placeholder
ed.WindowLength = 300;             % <- set: integration window [0, 4095]

% Capture data upon detection
[data, timestamp, droppedSamples, status] = capture(ed, milliseconds(1), seconds(1));

%% Verify
if status == 1
    r = max(abs(data).^2) / median(abs(data).^2);   % sanity ratio: real signal r >= 50
    fprintf("Captured %d samples at %s (dropped: %d). Sanity ratio r = %.1f (>= 50 = real signal).\n", ...
        numel(data), string(timestamp), droppedSamples, r);
    if r < 50
        warning("Sanity ratio below 50 - this looks like noise, not signal; see references/threshold-calibration.md.");
    end
else
    warning("No detection before timeout (status 0) - signal did not exceed the fixed threshold; measure it (see references/threshold-calibration.md).");
end

Pattern C: Preamble Detection with Adaptive Threshold

For loopback bring-up, start with Pattern D (fixed threshold) and use plotThreshold to read the noise floor; move to Pattern C only after the path is calibrated.

%% Preamble Detection - Adaptive Threshold
% Detect and capture signal by correlating with known preamble.
clear ed pd
radioName = "SigRadio";            % <- set: your radio configuration name
pd = preambleDetector(radioName);

% Configure RF parameters
pd.CenterFrequency = 2.2e9;        % <- set: Hz
pd.SampleRate = 10.24e6;           % <- set: Hz
pd.RadioGain = 30;                 % <- set: dB (device-dependent range)
pd.CaptureDataType = "double";

% Define preamble sequence (e.g., normalized Zadoff-Chu)
seq = zadoffChuSeq(38, 137);       % <- set: root index, sequence length [4, 1024]
preamble = seq / norm(seq, 2);
pd.Preamble = preamble;

% Configure adaptive threshold
pd.ThresholdMethod = "adaptive";
pd.AdaptiveThresholdGain = 0.3;    % <- set: typical 4-16 OTA, 0.1-0.9 loopback (boundary at 1.0)
pd.AdaptiveThresholdOffset = 0.001; % <- set: start at 0; raise only if false triggers occur

% Optional: visualize threshold curve before capturing (TX must be running)
% plotThreshold(pd, milliseconds(1));

% Capture data upon preamble detection
[data, timestamp, droppedSamples, status] = capture(pd, milliseconds(10), seconds(1));

%% Verify
if status == 1
    matched = filter(conj(flipud(pd.Preamble)), 1, data);
    r = max(abs(matched).^2) / median(abs(matched).^2);   % real preamble fire: r > 100
    fprintf("Captured %d samples at %s (dropped: %d). Sanity ratio r = %.1f (> 100 = real preamble).\n", ...
        numel(data), string(timestamp), droppedSamples, r);
    if r < 100
        warning("Sanity ratio below 100 - marginal, or fired on noise instead of the preamble; see references/threshold-calibration.md.");
    end
else
    warning("No detection before timeout (status 0) - preamble not correlated; to tune see references/threshold-calibration.md.");
end
Pattern C — Calibration

If Pattern C fires unreliably (timeouts over loopback, false positives over OTA, or captures full of noise), route into the threshold calibration sub-workflow:

-> Load references/threshold-calibration.md (Sub-Workflow B: preambleDetector Calibration)

The sub-workflow walks plotThreshold -> diagnose curves -> tune AdaptiveThresholdGain / AdaptiveThresholdOffset -> post-capture peak/baseline sanity ratio in four steps, with a combined symptom-cause-fix table at the end.

Pattern D: Preamble Detection with Fixed Threshold

%% Preamble Detection - Fixed Threshold
% Detect preamble using fixed correlation power threshold.
clear ed pd
radioName = "MyX310";              % <- set: your radio configuration name
pd = preambleDetector(radioName);

% Configure RF parameters
pd.CenterFrequency = 2.4e9;        % <- set: Hz
pd.SampleRate = 30.72e6;           % <- set: Hz
pd.RadioGain = 30;                 % <- set: dB (device-dependent range)
pd.CaptureDataType = "double";

% Define preamble sequence
seq = zadoffChuSeq(25, 63);        % <- set: root index, sequence length [4, 1024]
preamble = seq / norm(seq, 2);
pd.Preamble = preamble;

% Configure fixed threshold
pd.ThresholdMethod = "fixed";
pd.FixedThreshold = 0.1;           % <- set: fixed correlation threshold [0, 4095]

% Plot threshold to calibrate (this pattern's purpose)
plotThreshold(pd, milliseconds(1));

% Capture data upon preamble detection
[data, timestamp, droppedSamples, status] = capture(pd, milliseconds(10), seconds(1));

%% Verify
if status == 1
    matched = filter(conj(flipud(pd.Preamble)), 1, data);
    r = max(abs(matched).^2) / median(abs(matched).^2);   % real preamble fire: r > 100
    fprintf("Captured %d samples at %s (dropped: %d). Sanity ratio r = %.1f (> 100 = real preamble).\n", ...
        numel(data), string(timestamp), droppedSamples, r);
    if r < 100
        warning("Sanity ratio below 100 - marginal, or fired on noise instead of the preamble; see references/threshold-calibration.md.");
    end
else
    warning("No detection before timeout (status 0) - preamble not above the fixed threshold; to tune see references/threshold-calibration.md.");
end

Pattern E: Multi-Capture Energy Detection with Timestamps

%% Multi-Capture Energy Detection
% Capture multiple consecutive signals with sample clock timestamps.
% The "NumCaptures" name-value requires R2023b + R2024a or later (single capture works on R2023b).
clear ed pd
radioName = "MultiCap";            % <- set: your radio configuration name
ed = energyDetector(radioName, ...
    CaptureDataType="double", ...
    TimestampUnit="sample-clock-cycle");

% Configure RF parameters
ed.CenterFrequency = 3.5e9;        % <- set: Hz
ed.SampleRate = 245.76e6;          % <- set: Hz
ed.RadioGain = 30;                 % <- set: dB (device-dependent range)

% Configure threshold
ed.ThresholdMethod = "adaptive";
ed.MinimumEnergy = 0.1;            % <- set: energy floor [0, 8191]; use 0 over loopback (let the dB delta trigger)
ed.EnergyDeltaThreshold = 3;       % <- set: dB rise that triggers capture

% Capture N consecutive signals
numCaptures = 5;                   % <- set: number of consecutive captures (R2024a+)
[data, timestamp, droppedSamples, status] = capture(ed, ...
    milliseconds(1), seconds(3), "NumCaptures", numCaptures);

%% Verify
if status > 0
    if iscell(data)                % NumCaptures > 1 returns a cell array, one cell per capture
        firstCapture = data{1};
    else
        firstCapture = data;
    end
    r = max(abs(firstCapture).^2) / median(abs(firstCapture).^2);   % real signal r >= 50
    fprintf("Successfully captured %d of %d requested signals (%d samples each).\n", ...
        status, numCaptures, numel(firstCapture));
    fprintf("Capture 1 sanity ratio r = %.1f (>= 50 = real signal).\n", r);
    fprintf("Timestamps (clock cycles): %s\n", mat2str(timestamp));
    if r < 50
        warning("Sanity ratio below 50 - this looks like noise, not signal; see references/threshold-calibration.md.");
    end
else
    warning("No signals captured before timeout - expected with no active signal; to tune see references/threshold-calibration.md.");
end

Pattern F: Transmit-Then-Detect Workflow

%% Transmit and Detect with Energy Detector
% Transmit a test waveform and capture upon energy detection.
clear ed pd
radioName = "TestN310";            % <- set: your radio configuration name
ed = energyDetector(radioName);

% Configure RF parameters
ed.CenterFrequency = 2.45e9;       % <- set: Hz
ed.SampleRate = 30.72e6;           % <- set: Hz
ed.Antennas = "RF0:RX2";  % loopback partner of RF0:TX/RX
ed.RadioGain = 30;                 % <- set: dB (typical 20-40 OTA, ~30 loopback)
ed.CaptureDataType = "double";

% Configure adaptive threshold
ed.ThresholdMethod = "adaptive";
ed.MinimumEnergy = 0;              % <- loopback: 0 lets the dB delta trigger (scale-invariant); see Loopback
ed.EnergyDeltaThreshold = 1.5;     % <- dB rise; loopback rise scales with TransmitGain: ~1.5 at TxGain 30, ~3 at 50 (see Loopback)

% Generate test waveform (chirp with zero padding) — keep it complex ('complex') and column-shaped for transmit
chirpSignal = single(chirp(0:1/1e3:2, 0, 0.5, 15, 'complex')).';
chirpSignal(end) = [];
padLen = 6500;
zeroPad = complex(zeros(padLen, 1), zeros(padLen, 1));
testWaveform = [zeroPad; chirpSignal * 0.999; zeroPad];

% Transmit test waveform continuously
transmit(ed, testWaveform, "continuous", ...
    TransmitAntennas="RF0:TX/RX", ...
    TransmitGain=50);  % <- set: typical 10-30 dB OTA, ~50 dB loopback

% Capture upon energy detection
[data, timestamp, droppedSamples, status] = capture(ed, milliseconds(3), seconds(2));

% Stop transmission
stopTransmission(ed);

%% Verify
if status == 1
    % A chirp is wideband, so the raw ratio under-reports: r ~15-20 is normal here.
    % The matched filter against the known TX waveform is the authoritative check.
    r = max(abs(data).^2) / median(abs(data).^2);
    template = double(chirpSignal) / norm(double(chirpSignal));
    mf = abs(filter(conj(flipud(template)), 1, data));
    mfRatio = max(mf).^2 / median(mf).^2;
    fprintf("Captured %d samples of test waveform (dropped: %d). Transmission stopped.\n", ...
        numel(data), droppedSamples);
    fprintf("Raw ratio r = %.1f (15-20 is normal for a chirp); matched-filter ratio = %.1f (>> 100 = real signal).\n", ...
        r, mfRatio);
    if mfRatio < 100
        warning("Matched-filter ratio below 100 - the capture may be noise; see references/threshold-calibration.md.");
    end
else
    warning("No detection before timeout (status 0) - raise TransmitGain/RadioGain or set MinimumEnergy=0; see references/threshold-calibration.md.");
end

Pattern G: Frequency Scanning Loop (WLAN Channel Scan)

%% WLAN Channel Scan with Preamble Detection
% Scan multiple WLAN channels for activity using preamble detection.
clear ed pd
radioName = "WiFiScanner";         % <- set: your radio configuration name
pd = preambleDetector(radioName);

% Configure preamble detector
pd.CaptureDataType = "double";
pd.ThresholdMethod = "adaptive";
pd.AdaptiveThresholdGain = 0.3;    % <- set: typical 4-16 OTA, 0.1-0.9 loopback
pd.AdaptiveThresholdOffset = 0.001; % <- set: start at 0; raise if false triggers occur
pd.RadioGain = 30;                 % <- set: dB (device-dependent range)

% Define WLAN L-LTF preamble (legacy long training field)
% wlanNonHTConfig defaults to CBW20, so wlanLLTF returns 160 samples at 20 MHz.
lltf = wlanLLTF(wlanNonHTConfig);
preamble = lltf / max(abs(lltf));  % raw max|L-LTF| is ~1.44 - must scale into [-1, 1]
pd.Preamble = preamble;

% Define channels to scan (e.g., 2.4 GHz band channels 1, 6, 11)
channelFreqs = [2.412e9, 2.437e9, 2.462e9];
channelNames = ["Ch1", "Ch6", "Ch11"];
% SampleRate MUST match the preamble's own rate, or the correlator never peaks.
% CBW20 L-LTF is sampled at 20 MHz. Change the bandwidth and change this together.
sampleRate = 20e6;                 % <- set: Hz (must equal the L-LTF bandwidth)
pd.SampleRate = sampleRate;

% Scan each channel
captures = struct([]);
for idx = 1:length(channelFreqs)
    pd.CenterFrequency = channelFreqs(idx);
    [data, timestamp, ~, detectionOk] = capture(pd, ...
        milliseconds(10), milliseconds(100));
    if detectionOk
        captures = [captures, struct( ...
            Channel=channelNames(idx), ...
            Frequency=channelFreqs(idx), ...
            Data=data, ...
            Timestamp=timestamp)]; %#ok<AGROW>
    end
end

%% Verify
fprintf("Scan complete. Detected activity on %d of %d channels.\n", ...
    numel(captures), numel(channelFreqs));
if isempty(captures)
    warning("No activity on any channel - first confirm SampleRate matches the preamble bandwidth (20 MHz for CBW20 L-LTF), then tune AdaptiveThresholdGain; see references/threshold-calibration.md.");
else
    for k = 1:numel(captures)
        matched = filter(conj(flipud(pd.Preamble)), 1, captures(k).Data);
        r = max(abs(matched).^2) / median(abs(matched).^2);   % real preamble fire: r > 100
        fprintf("  %s (%.3f GHz): %d samples, sanity ratio r = %.1f (> 100 = real preamble)\n", ...
            captures(k).Channel, captures(k).Frequency/1e9, numel(captures(k).Data), r);
        if r < 100
            warning("%s ratio below 100 - marginal, or fired on noise instead of the preamble; see references/threshold-calibration.md.", captures(k).Channel);
        end
    end
end

Local Hardware Testing (Loopback)

Loopback (SMA cable from RF0:TX/RX to RF0:RX2) is the standard development path for transmit-then-detect workflows because it gives a deterministic, repeatable signal without relying on an external transmitter.

Absolute energy thresholds are radio-, gain-, and signal-specific — measure them, never hardcode. Over loopback the integrated energy is tiny and scales with RadioGain (≈ 6e-5 at RadioGain 30, ≈ 0.1 at RadioGain 70 on an N310) — it never reaches the OTA-scale values in the pattern comments. Prefer adaptive detection: the EnergyDeltaThreshold dB-rise is scale-invariant. For a fixed threshold, measure first — see references/threshold-calibration.md (Sub-Workflow A, Step 0).

ItemValueWhy
RX antennaAntennas="RF0:RX2"Loopback partner of RF0:TX/RX
TX antennaTransmitAntennas="RF0:TX/RX"Pass via transmit() Name-Value
RadioGain30–60Lifts the capture off the numerical floor. Amplifies signal and noise together, so it does not raise SNR — use TransmitGain for that. N310 has headroom to ~70 before the rail
TransmitGain50Drives the signal above the RX noise floor (this sets SNR / the sanity ratio)
MinimumEnergy (Pattern A/E/F)0 over loopbackLet EnergyDeltaThreshold trigger on the dB rise — scale-invariant, fires whatever the absolute energy. A nonzero floor must sit below the measured signal energy or it blocks the trigger (the old "1" sat far above it and timed out)
FixedThreshold (Pattern B)measure (≈ ¼–½ of the measured peak)Absolute → grab one reference with FixedThreshold=1e-9, compute max(movsum(abs(x).^2,WindowLength)), then set ¼–½ of that. The OTA example (500) sits far above a loopback signal and never fires
EnergyDeltaThreshold (Pattern A/E/F)1.5 dB at modest TransmitGain (~30); ~3 dB at high TransmitGain (~50)The achievable dB rise scales with TransmitGain (SNR): at ~30 dB TX the rise is only ~2–3 dB, so a 3 dB delta never crosses → timeout. Start low (1.5 dB); raise to 6 dB only if false positives
AdaptiveThresholdGain (Patterns C/G)0.3–0.5Anywhere in the loopback window 0.1-0.9 works; the patterns ship 0.3. Default 8 in OTA examples is far too high for loopback's quiet baseline
max|data|keep < 0.95 (hard rail 1.414)√2 = int16 saturation; keep below the 0.95 guard band. At TxGain=50, N310 stays clean to ~RadioGain 70 (device-dependent: N310 0-75, N320/X410 0-60)
First testAdaptive with MinimumEnergy=0, or fixed with a measured thresholdBoth fire deterministically over loopback

Copyright 2026 The MathWorks, Inc.

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
MathWorks
Publisher
MathWorks (Official)
Source
matlab/matlab-agentic-toolkit · skills-catalog/wireless-communications/matlab-detect-capture-usrp · commit 2a7f215
Licence
BSD 2-Clause License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE.md)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by MathWorks or MathWorks.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download matlab-detect-capture-usrp.zip