Generated banner for Matlab Generate Ble Waveform
Generated logo for Matlab Generate Ble Waveform

Matlab Generate Ble Waveform

MathWorksOfficialCreated by MathWorks

  • Skill
  • Tool integration
  • Science and research
  • BSD-2-Clause
  • No issues found

From the author

Generate Bluetooth Low Energy (BLE) PHY waveforms. Read BEFORE writing any BLE waveform code to avoid hallucinating API patterns. Covers bleWaveformGenerator, bleIdealReceiver, bleCTEIQSample, bleAngleEstimate, bluetoothTestWaveform for LE1M/LE2M/LE500K/LE125K. Bluetooth Toolbox R2022a+.
— the description by MathWorks

AutoGineer summary

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

This skill provides a structured approach to generating, simulating, and analyzing Bluetooth Low Energy (BLE) physical layer waveforms. It covers the use of core MATLAB functions such as bleWaveformGenerator, bleIdealReceiver, and bleAngleEstimate to support tasks ranging from basic waveform creation to complex direction-finding (CTE) pipelines, RF-PHY compliance testing, and BER simulations. The skill emphasizes correct configuration for LE1M, LE2M, and Coded PHY modes while adhering to Bluetooth 5.1+ specifications.

What it does

  • Generates BLE PHY waveforms for LE1M, LE2M, LE500K, and LE125K modes.
  • Simulates direction-finding pipelines using Constant Tone Extension (CTE) for AoA/AoD.
  • Performs RF-PHY compliance testing and transmitter measurements like PAPR and OBW.
  • Models receiver performance through BER simulations and WLAN coexistence scenarios.
  • Configures advertising and data channel PDUs with custom access addresses and whitening.
  • Provides visualization patterns for IQ samples and spectral analysis.

When to use it

  • When generating BLE PHY waveforms for simulation or testing.
  • When implementing direction-finding (AoA/AoD) using CTE.
  • When performing RF-PHY compliance or transmitter performance measurements.
  • When simulating receiver sensitivity and BER under noise or interference.
  • When comparing different BLE PHY modes or analyzing spectral characteristics.

Advantages

  • Ensures adherence to BLE specification constraints regarding PHY modes and channels.
  • Provides standardized patterns for complex tasks like CTE pipelines and multi-packet bursts.
  • Prevents common API hallucinations by enforcing correct parameter usage.
  • Includes robust conventions for spectral analysis and visualization.
  • Facilitates accurate simulation of practical receiver impairments.

Usage examples

“Generate an LE2M BLE waveform for channel 15 with a random payload.”
The agent provides a script using bleWaveformGenerator with Mode set to 'LE2M' and ChannelIndex set to 15, ensuring the symbol rate is correctly calculated as 2 Msym/s.
“How do I simulate a CTE connection-oriented packet for direction finding?”
The agent provides a code snippet using bleWaveformGenerator with DFPacketType set to 'ConnectionCTE', followed by the appropriate IQ sampling and angle estimation workflow.
“Show me how to calculate the Bit Error Rate for an LE1M signal.”
The agent generates a loop-based script that iterates through SNR values, applies AWGN to the generated waveform, and uses bleIdealReceiver to calculate the error rate.
“Can I use LE2M for advertising on channel 37?”
The agent correctly identifies this as a specification violation and refuses the request, explaining that advertising channels (37-39) are restricted to LE1M or Coded PHYs.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/matlab/matlab-generate-ble-waveform/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip matlab-generate-ble-waveform.zip -d .agents/skills

Documentation by MathWorks

BLE Waveform Generation

Routing

IntentCuesPattern
Basic generation"generate", "waveform"Single-Mode
Compare modes"compare", "all modes"All-Mode
Direction finding"CTE", "AoA", "AoD"CTE
RF-PHY compliance"compliance", "RF-PHY"RF-PHY Test
Coexistence"WLAN", "interference"WLAN Coexistence
TX measurements"PAPR", "OBW", "power"TX Measurements
Localization"RSSI", "beacon", "distance"RSSI Localization
BER/receiver"BER", "noise", "sensitivity"BER Simulation
Multi-packet"burst", "IFS"Multi-Packet
Spectrum"spectrum", "spectral mask"Spectral Analysis
Whitening"whitening", "scrambling"Whitening Comparison
Impairments"CFO", "practical", "realistic"End-to-End

Default (ambiguous request): LE1M, 37-byte payload, channel 37.

When To Use

Use this skill when the user asks to generate, simulate, or analyze BLE PHY-layer waveforms — including mode comparisons, CTE/direction finding, RF-PHY compliance testing, BER simulations, TX measurements, WLAN coexistence, or RSSI localization. Applies to any task involving bleWaveformGenerator, bleIdealReceiver, bleCTEIQSample, bleAngleEstimate, or bluetoothTestWaveform.

When Not To Use

  • BR/EDR (Classic Bluetooth) → use bluetoothWaveformGenerator (covered by a separate BR/EDR skill)

  • Channel Sounding (CS) → use bleCSWaveform(bleCSConfig) (R2024b+); CS has its own ranging/distance-measurement pipeline and is not part of PHY waveform generation

  • Network-level simulation → use bluetoothLENode for link-layer/network modeling

  • Path loss modeling → use bluetoothPathLoss for propagation/channel modeling

PHY Modes

ModesymbolRateData RateCodingDuration (37 B)
"LE1M"1e61 MbpsNone~336 us
"LE2M"2e62 MbpsNone~172 us
"LE500K"1e6500 kbpsFEC S=2~974 us
"LE125K"1e6125 kbpsFEC S=8~2768 us

References

For detailed guidance beyond what's in this file, see:

Parameters (bleWaveformGenerator)

ParamDefaultRange
Mode"LE1M"LE1M, LE2M, LE500K, LE125K
ChannelIndex370-39
SamplesPerSymbol8>=1 (use >=4 for plots)
WhitenStatus"On"On, Off
DFPacketType"Disabled"Disabled, ConnectionCTE, ConnectionlessCTE
AccessAddressadv default32-bit binary col vector
ModulationIndex0.5[0.45, 0.55]
PulseLength1[1, 4]

Core Recipe

messageBits = randi([0 1], payloadLenBytes*8, 1);
waveform = bleWaveformGenerator(messageBits, ...
    Mode=phyMode, SamplesPerSymbol=sps, ...
    ChannelIndex=ch, WhitenStatus="On");
symbolRate = 1e6 + 1e6*(phyMode=="LE2M");
fs = symbolRate * sps;

Patterns

All-Mode Comparison
phyModes = ["LE1M","LE2M","LE500K","LE125K"];
sps = 8;
messageBits = randi([0 1], 37*8, 1);
figure; tl = tiledlayout(4,2,TileSpacing="compact",Padding="compact");
title(tl,"BLE - All PHY Modes")
for idx = 1:4
    wf = bleWaveformGenerator(messageBits, ...
        Mode=phyModes(idx), SamplesPerSymbol=sps, ChannelIndex=37, WhitenStatus="On");
    sr = 1e6 + 1e6*(phyModes(idx)=="LE2M");
    fs = sr*sps; t = (0:length(wf)-1)/fs*1e6;
    nexttile; plot(t,real(wf),t,imag(wf))
    xlabel("Time (\mus)"); ylabel("Amplitude"); title(phyModes(idx)+" - IQ")
    legend("I","Q"); grid on; xlim([0 min(80,t(end))])
    nexttile; N=length(wf); f=(-N/2:N/2-1)*(fs/N)/1e6;
    plot(f, 20*log10(abs(fftshift(fft(wf)))/N+eps))
    xlabel("Frequency (MHz)"); ylabel("dB"); title(phyModes(idx)+" - Spectrum")
    grid on; xlim([-3 3]); ylim([-80 0])
end
Multi-Packet Burst (T_IFS = 150 us)
sps = 8; fs = 1e6*sps;
messageBits = randi([0 1], 37*8, 1);
ifsGap = complex(zeros(round(150e-6*fs), 1));
burst = complex(zeros(0,1));
for pkt = 1:3
    wf = bleWaveformGenerator(messageBits, Mode="LE1M", SamplesPerSymbol=sps, ChannelIndex=37);
    burst = [burst; wf; ifsGap]; %#ok<AGROW>
end
Round-Trip Decode
messageBits = randi([0 1], 37*8, 1); sps = 8;
wf = bleWaveformGenerator(messageBits, Mode="LE1M", SamplesPerSymbol=sps, ChannelIndex=37, WhitenStatus="On");
rxBits = bleIdealReceiver(wf, Mode="LE1M", SamplesPerSymbol=sps, ChannelIndex=37, WhitenStatus="On");
assert(isequal(rxBits, messageBits))
Data Channel + Custom Access Address
accessAddr = randi([0 1], 32, 1);
wf = bleWaveformGenerator(randi([0 1],27*8,1), ...
    Mode="LE2M", SamplesPerSymbol=8, ChannelIndex=9, ...
    AccessAddress=accessAddr, WhitenStatus="On");
fs = 2e6 * 8;
CTE Generation
messageBits = randi([0 1], 20*8, 1); sps = 8; fs = 1e6*sps;
wf_noCTE = bleWaveformGenerator(messageBits, Mode="LE1M", ...
    SamplesPerSymbol=sps, ChannelIndex=10, WhitenStatus="On");
wf_conn = bleWaveformGenerator(messageBits, Mode="LE1M", ...
    SamplesPerSymbol=sps, ChannelIndex=10, WhitenStatus="On", DFPacketType="ConnectionCTE");
wf_cless = bleWaveformGenerator(messageBits, Mode="LE1M", ...
    SamplesPerSymbol=sps, ChannelIndex=10, WhitenStatus="On", DFPacketType="ConnectionlessCTE");

% CTE appends unmodulated constant tone for antenna switching (typ. 72-216 us)
cteDelta_conn_us = (length(wf_conn) - length(wf_noCTE)) / fs * 1e6;
cteDelta_cless_us = (length(wf_cless) - length(wf_noCTE)) / fs * 1e6;
CTE Full Pipeline (Tx → Rx → Angle)
pduHex = '02049B03270102030405';  % 10-byte PDU (>=6 bytes needed for >=11 IQ samples)
pdu = int2bit(hex2dec(reshape(pduHex, 2, [])'), 8, false);
pdu = pdu(:);
cfgCRC = crcConfig(Polynomial="z^24+z^10+z^9+z^6+z^4+z^3+z+1", ...
    InitialConditions=int2bit(hex2dec('555551'),24), DirectMethod=true);
pduCRC = crcGenerate(pdu, cfgCRC);
txWf = bleWaveformGenerator(pduCRC, ChannelIndex=36, DFPacketType="ConnectionlessCTE");
[~, ~, iqSamples] = bleIdealReceiver(txWf, ChannelIndex=36, ...
    DFPacketType="ConnectionlessCTE", SlotDuration=2);
cfgAngle = bleAngleEstimateConfig;
cfgAngle.ArraySize = 4; cfgAngle.SlotDuration = 2; cfgAngle.SwitchingPattern = [1 2 3 4];
angle = bleAngleEstimate(iqSamples, cfgAngle);

Alternative (R2022a+): iqSamples = bleCTEIQSample(cteSamples, Mode="LE1M", SlotDuration=2);

BER Simulation
messageBits = randi([0 1], 100*8, 1); sps = 8; phyMode = "LE1M";
snrValues = 0:4:20; berResults = zeros(size(snrValues));
for idx = 1:length(snrValues)
    wf = bleWaveformGenerator(messageBits, Mode=phyMode, SamplesPerSymbol=sps, ChannelIndex=5, WhitenStatus="On");
    sigPower = mean(abs(wf).^2);
    rxWf = awgn(wf, snrValues(idx), 10*log10(sigPower));
    rxBits = bleIdealReceiver(rxWf, Mode=phyMode, SamplesPerSymbol=sps, ChannelIndex=5, WhitenStatus="On");
    n = min(length(messageBits), length(rxBits));
    berResults(idx) = sum(messageBits(1:n) ~= double(rxBits(1:n))) / n;
end
semilogy(snrValues, berResults, "-o"); xlabel("SNR (dB)"); ylabel("BER"); grid on

bleIdealReceiver returns int8 — cast with double(). For Eb/No conversion: snr = convertSNR(EbNo,"ebno","snr",SamplesPerSymbol=sps).

RF-PHY Test Waveform
cfg = bluetoothTestWaveformConfig;
cfg.Mode = "LE1M"; cfg.PayloadLength = 37;
cfg.PacketType = "Disabled"; cfg.ModulationIndex = 0.5;
testWf = bluetoothTestWaveform(cfg);

rfCfg = bluetoothRFPHYTestConfig;
rfCfg.Test = "Output power"; rfCfg.Mode = "LE1M";
rfCfg.PayloadLength = 37; rfCfg.OutputPower = 0; rfCfg.CenterFrequency = "Mid";
WLAN Coexistence
sps = 8; fs = 1e6*sps;
bleWf = bleWaveformGenerator(randi([0 1],37*8,1), Mode="LE1M", SamplesPerSymbol=sps, ChannelIndex=37, WhitenStatus="On");
t = (0:length(bleWf)-1)'/fs;
wlanInterferer = 0.1*complex(randn(length(bleWf),1),randn(length(bleWf),1)) .* exp(1j*2*pi*3e6*t);
combined = bleWf + wlanInterferer;
CIR_dB = 10*log10(mean(abs(bleWf).^2) / mean(abs(wlanInterferer).^2));
TX Measurements (Power, OBW, PAPR)
sps = 8; wf = bleWaveformGenerator(randi([0 1],255*8,1), Mode="LE1M", ...
    SamplesPerSymbol=sps, ChannelIndex=37, WhitenStatus="On", ModulationIndex=0.5);
fs = 1e6*sps; N = length(wf);
avgPower_dBm = 10*log10(mean(abs(wf).^2)) + 30;
spec = abs(fftshift(fft(wf))).^2; cumP = cumsum(spec)/sum(spec);
f = (-N/2:N/2-1)*(fs/N);
occBW_MHz = (f(find(cumP>=0.995,1)) - f(find(cumP>=0.005,1))) / 1e6;
papr_dB = 10*log10(max(abs(wf).^2) / mean(abs(wf).^2));
Whitening Comparison
bits = randi([0 1], 37*8, 1); sps = 8;
wfOn  = bleWaveformGenerator(bits, Mode="LE1M", SamplesPerSymbol=sps, ChannelIndex=37, WhitenStatus="On");
wfOff = bleWaveformGenerator(bits, Mode="LE1M", SamplesPerSymbol=sps, ChannelIndex=37, WhitenStatus="Off");
Advertising PDU (ADV_IND)
cfgAdv = bleLLAdvertisingChannelPDUConfig;
cfgAdv.PDUType = "Advertising indication";
cfgAdv.AdvertiserAddress = "A1B2C3D4E5F6";
cfgAdv.AdvertiserAddressType = "Random";
cfgAdv.AdvertisingData = "0201060709546573744245020A00";
pduBits = bleLLAdvertisingChannelPDU(cfgAdv);
wf = bleWaveformGenerator(pduBits, Mode="LE1M", SamplesPerSymbol=8, ChannelIndex=37);

PDUType values: "Advertising indication", "Scan request", "Scan response", "Connection indication", "Advertising direct indication", "Advertising non connectable indication". Addresses: 12-char hex string (no colons). AdvertisingData: hex string (length-type-value AD structs).

Data Channel PDU
cfgData = bleLLDataChannelPDUConfig;
cfgData.LLID = "Data (start fragment/complete)";
cfgData.SequenceNumber = 1;
cfgData.NESN = 0;
cfgData.MoreData = false;
payload = dec2hex(randi([0 255], 50, 1))';  % 50-byte hex payload
payload = reshape(payload', 1, []);
pduBits = bleLLDataChannelPDU(cfgData, payload);
wf = bleWaveformGenerator(pduBits, Mode="LE2M", SamplesPerSymbol=8, ChannelIndex=15);

LLID values: "Data (continuation fragment/empty)", "Data (start fragment/complete)", "Control". SequenceNumber (not SN). Payload: hex string, numeric vector [0,255], or n×2 char array.

End-to-End (Practical Receiver)

Uses helperBLEPracticalReceiver (AGC + CFO + timing recovery). Requires: openExample('bluetooth/BLEPracticalReceiverExample'). SNR conversion for coded modes: SNR = EbNo + 10*log10(codeRate) - 10*log10(sps).

CTE Compatibility

ModeConnectionCTEConnectionlessCTE
LE1MYesYes
LE2MYesNo
LE500KNoNo
LE125KNoNo

CTE requires data channels (0-36). Never use ChannelIndex 37/38/39 with DFPacketType≠"Disabled". CTE is a data-channel-only feature.

Spec Constraints

  • Ch 37/38/39 (advertising): LE1M or LE Coded only. NOT LE2M.

  • CTE: LE1M/LE2M on data channels (0-36) only. Never coded PHY.

  • Access address: Advertising = '8E89BED6' (fixed); data = random.

  • Whitening init: Auto from ChannelIndex. Not settable.

  • T_IFS: 150 us. TX power: max +20 dBm. Mod index: 0.45-0.55.

  • bleIdealReceiver: Hard-decision only, returns int8.

  • "Slots" (1-slot, 3-slot, 5-slot): BR/EDR concept only. BLE has no slot-based packets.

  • ISO streams: Verify BN × IRC × Sub_Interval <= ISO_Interval. If math exceeds, REFUSE.

Invalid Combos (REFUSE these requests)
RequestWhy Invalid
LE2M + FEC codingFEC only exists in coded PHY (LE500K/LE125K)
LE2M on ch 37/38/39Primary advertising = LE1M or Coded only
CTE + LE500K/LE125KCTE restricted to LE1M/LE2M per BLE 5.1 spec
ConnectionlessCTE + LE2MConnectionlessCTE = LE1M only
"3-slot" or "5-slot" BLESlot-based = BR/EDR; BLE uses single packets
CTE on ch 37/38/39CTE = data channels (0-36) only
ISO params where BN×IRC×Sub_Int > ISO_IntervalSub-events exceed interval = impossible scheduling

Conventions

  • fs = (1e6 + 1e6*(mode=="LE2M")) * sps — never hardcode

  • Axes: us (time), MHz (freq), dB (power)

  • tiledlayout/nexttile (not subplot)

  • Spectrum: always 20*log10(abs(fftshift(fft(wf)))/N + eps) — do NOT use periodogram, pspectrum, or pwelch for BLE spectral plots

  • xlim([0 min(80,t(end))]) for coded modes

  • Plot both I and Q: plot(t, real(wf), t, imag(wf))

  • IFS gap: complex(zeros(N,1)) (output is complex)

  • SamplesPerSymbol>=4 for spectrum plots; SPS=1 is valid for generation but insufficient for visualization or accurate spectral analysis (at Nyquist limit)

Code Style

Generated scripts must follow MathWorks example conventions. Use %% section headers, softcode all parameters, and comment non-obvious logic.

Structure & Softcoding

Divide scripts with %% headers: Configuration → Waveform Generation → Processing → Analysis → Visualization. All user-configurable values as named variables at the top; no magic numbers in processing logic.

%% Configuration
% Specify BLE waveform generation parameters.

phyMode = "LE1M";                               % PHY transmission mode
payloadLength = 37;                             % Payload length in bytes
sps = 8;                                        % Samples per symbol
channelIndex = 37;                              % BLE channel index (0-39)

% Derived parameters
symbolRate = 1e6 + 1e6*(phyMode=="LE2M");       % 2 Msym/s for LE2M, 1 Msym/s otherwise
fs = sps * symbolRate;                          % Sample rate in Hz
numBits = payloadLength * 8;                    % Payload length in bits

%% Waveform Generation
% Generate BLE waveform from random payload bits.

messageBits = randi([0 1], numBits, 1);
waveform = bleWaveformGenerator(messageBits, ...
    Mode=phyMode, SamplesPerSymbol=sps, ChannelIndex=channelIndex);
Comments
  • Inline (right-aligned): units, range, or brief purpose — % Eb/No in dB

  • Block (above 2-5 lines): intent or why — % IFS gap must be complex (bleWaveformGenerator output is complex)

  • Constraints: spec reference — % LE2M uses 2 Msym/s (only mode with 2x symbol rate)

  • Do NOT comment self-evident lines, every line mechanically, or closing end statements

Naming
  • camelCase: messageBits, txWaveform, symbolRate, channelIndex

  • Descriptive loop counters: countMode, pktIdx (not i, j)

  • Named constants: bitsPerByte = 8, tIFS = 150e-6

  • Multi-init: [numErrors, perCount] = deal(0, 0)

Gotchas

MistakeFix
bleWaveformConfig(...)Does not exist. Use bleWaveformGenerator(bits, NV...) directly
bluetoothWaveformGenerator for BLEBR/EDR only. Use bleWaveformGenerator
comm.BLEReceiverDoes not exist. Use bleIdealReceiver
symbolRate=1e6 for LE2MLE2M = 2e6
'PHYMode' as param nameUse 'Mode'
'LECODED' as modeUse "LE500K" or "LE125K"
Config object as 1st argMust be binary col vector randi([0 1],N,1)
Empty [] bitsMinimum 1 byte
Real zeros() for IFSUse complex(zeros(N,1))
CTE + coded modesCTE only on LE1M/LE2M
ConnectionlessCTE + LE2MLE1M only
LE2M on ch 37/38/39Spec violation. Use LE1M or Coded
CTELength/CTEType as NV argsNot valid. Use DFPacketType
bleAngleEstimate(wf, NV...)Use bleAngleEstimate(iqSamples, bleAngleEstimateConfig)
bleWaveformGenerator for RF-PHYUse bluetoothTestWaveformConfig + bluetoothTestWaveform
AWGN without oversampling correctionUse convertSNR(EbNo,"ebno","snr",SamplesPerSymbol=sps); pass explicit power to awgn (never use "measured")
bleIdealReceiver with impairmentsUse helperBLEPracticalReceiver
Eb/No for coded modes without code rateSNR = EbNo + 10*log10(codeRate) - 10*log10(sps)
SPS=1 without warningSPS=1 is at Nyquist limit — warn user: no spectral analysis, marginal decode performance
Timing ppm offset without resampleApply clock drift via resample(wf, 1e6+ppm, 1e6) or fractional delay filter
Conformance Df1/Df2 without PRBS9Use comm.PNSequence (z^9+z^5+1) for TP/TRM/CA/BV-05-C; separate stable-bit (Df1) from alternating-bit (Df2) patterns
periodogram/pwelch for BLE spectrumUse manual fftshift(fft(wf)) — skill convention
SN as property nameUse SequenceNumber on bleLLDataChannelPDUConfig
Address with colons "A1:B2:..."Use 12-char hex string without colons: "A1B2C3D4E5F6"
PDUType="ADV_IND"Use full string: "Advertising indication"
Binary vector as AdvertisingDataUse hex string: "0201060709..." (length-type-value)

Copyright 2026 The MathWorks, Inc.

Files

references/practical-receiver-simulation.md

BLE End-to-End PHY Simulation with RF Impairments and Practical Receiver

Verified against MathWorks example (2026-05-22): bluetooth/ug/end-to-end-bluetooth-low-energy-phy-simulation-with-rf-impairments-and-corrections


Overview

This reference covers the full link-level simulation pipeline: TX waveform generation → RF impairments → AWGN channel → practical receiver with corrections → BER/PER measurement. Covers all 4 PHY modes simultaneously.

Unlike the ideal receiver pattern in SKILL.md (which uses bleIdealReceiver), this pipeline uses helperBLEPracticalReceiver with real-world correction algorithms (AGC, CFO compensation, timing recovery, packet detection).


Pipeline Architecture

TX: bits → bleWaveformGenerator → helperBLEImpairmentsAddition → awgn → channel output
RX: channel output → helperBLEPracticalReceiver → rxBits
                     (AGC → DC removal → CFO correction → matched filter →
                      preamble detection → timing correction → demod → de-whiten)

Complete Simulation Code

Configuration
EbNo = 2:4:10;                                  % Eb/No in dB
sps = 4;                                        % Samples per symbol, must be > 1
dataLength = 42;                                % Header (2) + Payload (37) + CRC (3) bytes
simMode = ["LE1M", "LE2M", "LE500K", "LE125K"];
maxNumErrors = 100;                             % Stopping criterion
maxNumPackets = 10;                             % Stopping criterion (use 10000 for publication)
bitsPerByte = 8;
numMode = numel(simMode);
snrLength = length(EbNo);
[ber, per] = deal(zeros(numMode, snrLength));
Eb/No to SNR Conversion (Critical)
% Uncoded modes (LE1M, LE2M): simple oversampling correction
snrVec = EbNo - 10*log10(sps);

% Coded modes: add code rate gain
% LE500K (rate 1/2):
snrVec = EbNo + 10*log10(1/2) - 10*log10(sps);

% LE125K (rate 1/8):
snrVec = EbNo + 10*log10(1/8) - 10*log10(sps);

General formula:

if any(phyMode == ["LE1M", "LE2M"])
    snrVec = EbNo - 10*log10(sps);
else
    if phyMode == "LE500K"
        codeRate = 1/2;
    else
        codeRate = 1/8;
    end
    snrVec = EbNo + 10*log10(codeRate) - 10*log10(sps);
end
Sample Rate Calculation (Compact Form)
sampleRate = sps * (1 + (phyMode == "LE2M")) * 1e6;

This evaluates to:

  • LE1M/LE500K/LE125K: sps * 1 * 1e6

  • LE2M: sps * 2 * 1e6

Receiver Configuration
rxCfg = struct(Mode=phyMode, SamplesPerSymbol=sps, DFPacketType="Disabled");

rxCfg.CoarseFreqCompensator = comm.CoarseFrequencyCompensator( ...
    Modulation="OQPSK", ...
    SampleRate=sampleRate, ...
    SamplesPerSymbol=2*sps, ...
    FrequencyResolution=30);

rxCfg.PreambleDetector = comm.PreambleDetector(Detections="First");
Main Simulation Loop
for countMode = 1:numMode
    phyMode = simMode(countMode);
    sampleRate = sps * (1 + (phyMode == "LE2M")) * 1e6;

    % Compute SNR vector for this mode
    if any(phyMode == ["LE1M", "LE2M"])
        snrVec = EbNo - 10*log10(sps);
    else
        if phyMode == "LE500K", codeRate = 1/2; else, codeRate = 1/8; end
        snrVec = EbNo + 10*log10(codeRate) - 10*log10(sps);
    end

    for countSnr = 1:snrLength
        % Reproducible random stream
        stream = RandStream("combRecursive", Seed=0);
        stream.Substream = countSnr;
        RandStream.setGlobalStream(stream);

        errorRate = comm.ErrorRate(Samples="Custom", ...
            CustomSamples=1:(dataLength*bitsPerByte-1));

        % Initialize impairments struct
        initImp = helperBLEImpairmentsInit(phyMode, sps);

        % Receiver config
        rxCfg = struct(Mode=phyMode, SamplesPerSymbol=sps, DFPacketType="Disabled");
        rxCfg.CoarseFreqCompensator = comm.CoarseFrequencyCompensator( ...
            Modulation="OQPSK", SampleRate=sampleRate, ...
            SamplesPerSymbol=2*sps, FrequencyResolution=30);
        rxCfg.PreambleDetector = comm.PreambleDetector(Detections="First");

        [numErrors, perCount, numPacket] = deal(0, 0, 1);

        while numErrors <= maxNumErrors && numPacket <= maxNumPackets
            % --- TX ---
            txBits = randi([0 1], dataLength*bitsPerByte, 1, "int8");
            channelIndex = randi([0 39], 1, 1);

            if channelIndex <= 36
                accessAddress = [1 0 0 0 1 1 1 0 1 1 0 0 1 0 0 1 ...
                    1 0 1 1 1 1 1 0 1 1 0 1 0 1 1 0]';
            else
                accessAddress = [0 1 1 0 1 0 1 1 0 1 1 1 1 1 0 1 ...
                    1 0 0 1 0 0 0 1 0 1 1 1 0 0 0 1]';
            end

            txWaveform = bleWaveformGenerator(txBits, ...
                Mode=phyMode, SamplesPerSymbol=sps, ...
                ChannelIndex=channelIndex, AccessAddress=accessAddress);

            % --- Impairments ---
            initImp.pfo.FrequencyOffset = randsrc(1, 1, -50e3:10:50e3);
            initImp.pfo.PhaseOffset = randsrc(1, 1, -10:5:10);
            initoff = 0.15*sps;
            stepsize = 20*1e-6;
            initImp.vdelay = (initoff:stepsize:initoff+stepsize*(length(txWaveform)-1))';
            initImp.dc = 20;

            txImpairedWfm = helperBLEImpairmentsAddition(txWaveform, initImp);

            % --- Channel ---
            rxWaveform = awgn(txImpairedWfm, snrVec(countSnr));

            % --- RX ---
            rxCfg.ChannelIndex = channelIndex;
            rxCfg.AccessAddress = accessAddress;
            [rxBits, recAccessAddress] = helperBLEPracticalReceiver(rxWaveform, rxCfg);

            % --- BER/PER ---
            if length(txBits) == length(rxBits)
                errors = errorRate(txBits, rxBits);
                ber(countMode, countSnr) = errors(1);
                currentErrors = errors(2) - numErrors;
                if currentErrors
                    perCount = perCount + 1;
                end
                numErrors = errors(2);
            else
                perCount = perCount + 1;
            end
            numPacket = numPacket + 1;
        end
        per(countMode, countSnr) = perCount / (numPacket - 1);
    end
end
Plotting
marker = "ox*s";
color = "bmgr";
figure;
tl = tiledlayout(2, 1);
for countMode = 1:numMode
    nexttile(1)
    semilogy(EbNo, ber(countMode,:).', "-"+marker(countMode)+color(countMode))
    hold on

    nexttile(2)
    semilogy(EbNo, per(countMode,:).', "-"+marker(countMode)+color(countMode))
    hold on
end
nexttile(1); grid on; xlabel("Eb/No (dB)"); ylabel("BER")
legend(simMode); title("BER of BLE under RF Impairments")
nexttile(2); grid on; xlabel("Eb/No (dB)"); ylabel("PER")
legend(simMode); title("PER of BLE under RF Impairments")

RF Impairments Reference

ImpairmentParameterTypical RangeBLE Spec Limit
Frequency offsetinitImp.pfo.FrequencyOffset-50 to +50 kHz+/-150 kHz
Phase offsetinitImp.pfo.PhaseOffset-10 to +10 degrees—
Timing driftstepsize20 ppm+/-50 ppm
Static timing offsetinitoff0.15*sps samples—
DC offsetinitImp.dc20 (% of max amp)—
Impairment Application Pipeline
txWaveform → [Frequency/Phase Offset] → [Variable Delay (timing drift)] → [DC Offset] → txImpairedWfm

Practical Receiver Architecture

helperBLEPracticalReceiver implements:

StageAlgorithmSystem Object / Technique
1AGCAutomatic gain normalization
2DC removalMean subtraction
3Coarse CFO correctioncomm.CoarseFrequencyCompensator (OQPSK modulation, FreqRes=30 Hz)
4Matched filteringGaussian pulse-shaped filter
5Preamble detectioncomm.PreambleDetector (first detection)
6Timing correctionSymbol timing recovery
7DemodulationGFSK demodulation
8De-whiteningLFSR-based (from ChannelIndex)

Key difference from bleIdealReceiver:

  • bleIdealReceiver: no impairments modeled, ideal demodulation, returns int8 bits

  • helperBLEPracticalReceiver: handles real-world impairments, includes correction algorithms, returns decoded bits


Helper Functions (from MathWorks Example)

These helpers ship with the Bluetooth Toolbox example. To use them, open the example in MATLAB first:

openExample('bluetooth/BLEPHYWithRFImpairmentsAndCorrectionsExample')
HelperPurposeInputsOutputs
helperBLEImpairmentsInitCreate impairment struct(phyMode, sps)Struct with pfo, vdelay, dc fields
helperBLEImpairmentsAdditionApply impairments to waveform(txWaveform, initImp)Impaired waveform
helperBLEPracticalReceiverFull RX chain with corrections(rxWaveform, rxCfg)[rxBits, accessAddress]
helperBLEReferenceResultsSpec-defined reference BER/PER(phyMode, payloadLen)[BER, PER, EbNo]

Eb/No to SNR Conversion — Complete Reference

The relationship between Eb/No and SNR depends on the mode:

SNR = Eb/No + 10*log10(codeRate) - 10*log10(sps)
ModeCode RateFormula
LE1M1 (uncoded)SNR = EbNo - 10*log10(sps)
LE2M1 (uncoded)SNR = EbNo - 10*log10(sps)
LE500K1/2SNR = EbNo + 10*log10(0.5) - 10*log10(sps)
LE125K1/8SNR = EbNo + 10*log10(0.125) - 10*log10(sps)

Why this matters: The awgn function takes SNR (not Eb/No). If you pass Eb/No directly to awgn, the noise level will be wrong by 10*log10(sps) dB + code rate gain.

Applying noise: Compute explicit signal power and pass it to awgn: sigPower = mean(abs(waveform).^2); rxWf = awgn(waveform, snr, 10*log10(sigPower));. Never use the "measured" flag — it obscures power assumptions and can produce incorrect noise levels after channel effects.


Access Address Details

Advertising Channels (37-39)

Fixed access address: 0x8E89BED6 (LSB-first binary):

accessAddress = [0 1 1 0 1 0 1 1 0 1 1 1 1 1 0 1 1 0 0 1 0 0 0 1 0 1 1 1 0 0 0 1]';
Data Channels (0-36)

Random per-connection, must meet BLE spec Section 2.1.2 Vol-6 Part-B requirements:

accessAddress = [1 0 0 0 1 1 1 0 1 1 0 0 1 0 0 1 1 0 1 1 1 1 1 0 1 1 0 1 0 1 1 0]';

Requirements for a valid data channel access address:

  • Not equal to advertising access address

  • No more than 6 consecutive 0s or 1s

  • Not all 4 bytes equal

  • At least 2 transitions in MSB 6 bits

  • At least 24 bit transitions total


BER/PER Measurement Approach

Using comm.ErrorRate
errorRate = comm.ErrorRate(Samples="Custom", ...
    CustomSamples=1:(dataLength*bitsPerByte-1));
  • CustomSamples excludes the last bit (to avoid edge effects)

  • Returns 3-element vector: [BER, numErrors, numBitsCompared]

  • Accumulates across multiple calls (stateful System Object)

PER Logic
if length(txBits) == length(rxBits)
    % Packet decoded successfully (may still have bit errors)
    errors = errorRate(txBits, rxBits);
    if errors(2) - previousErrors > 0
        perCount = perCount + 1;  % Packet has errors
    end
else
    % Packet decode failed entirely (length mismatch = packet error)
    perCount = perCount + 1;
end
per = perCount / totalPackets;
Stopping Criteria

Standard Monte Carlo approach:

  • maxNumErrors = 100 (minimum for quick estimate)

  • maxNumPackets = 10 (minimum for quick estimate)

  • For publication: maxNumErrors = 1000, maxNumPackets = 10000


Reference Results from Bluetooth Spec

headerLen = 2; crcLen = 3;
payloadLen = dataLength - headerLen - crcLen;  % 37 bytes
[refBER, refPER, refEbNo] = helperBLEReferenceResults(phyMode, payloadLen);
ModeRef Eb/No (dB)BERPERNotes
LE1M34.90.0010.308Includes margin for real-world impairments
LE2M34.90.0010.308Same target as LE1M
LE500K31.90.0010.308FEC gain reduces required Eb/No
LE125K25.90.0010.308Strongest coding → lowest Eb/No requirement

Simulated results will outperform these reference values because the reference includes margin for impairments beyond what's modeled in the simulation (multipath, interference, etc.).


Key Differences: Ideal vs Practical Receiver

AspectIdeal (bleIdealReceiver)Practical (helperBLEPracticalReceiver)
Input requirementsClean or AWGN-only waveformWaveform with RF impairments
Impairment correctionNoneAGC, DC removal, CFO, timing
Packet detectionImplicit (no sync needed)Preamble-based detection
Output typeint8 column vectorint8 column vector
Use caseQuick BER, CTE IQ extractionRealistic link-level simulation
SpeedFaster (less processing)Slower (correction algorithms)
ToolboxBuilt-in functionExample helper (must open example first)

When to Use Which

ScenarioReceiverReason
Quick BER curve (AWGN only)bleIdealReceiverSimple, fast, built-in
RF impairment robustness testinghelperBLEPracticalReceiverHandles CFO, timing drift, DC
CTE/direction findingbleIdealReceiver (3rd output)Only way to get IQ samples
Receiver algorithm developmenthelperBLEPracticalReceiver (modified)Customizable correction chain
Link budget analysisEitherDepends on whether impairments matter

Copyright 2026 The MathWorks, Inc. Last verified: 2026-05-22

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.

The author says: >=R2022a

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-generate-ble-waveform · 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.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download matlab-generate-ble-waveform.zip