Skill v1.0.1
currentAutomated scan100/100+11 new
version: "1.0.1" name: matlab-generate-ble-waveform description: > 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+. license: https://www.mathworks.com/content/dam/mathworks/license/pmrl/license.md compatibility: ">=R2022a" metadata: author: MathWorks version: "1.0"
BLE Waveform Generation
Routing
| Intent | Cues | Pattern | |
|---|---|---|---|
| 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
bluetoothLENodefor link-layer/network modeling - Path loss modeling → use
bluetoothPathLossfor propagation/channel modeling
PHY Modes
| Mode | symbolRate | Data Rate | Coding | Duration (37 B) | |
|---|---|---|---|---|---|
"LE1M" | 1e6 | 1 Mbps | None | ~336 us | |
"LE2M" | 2e6 | 2 Mbps | None | ~172 us | |
"LE500K" | 1e6 | 500 kbps | FEC S=2 | ~974 us | |
"LE125K" | 1e6 | 125 kbps | FEC S=8 | ~2768 us |
References
For detailed guidance beyond what's in this file, see:
- functionalAPI.md — Full API reference for bleWaveformGenerator Name-Value pairs
- phy-modes-and-timing.md — Detailed timing calculations per PHY mode
- cte-direction-finding.md — CTE pipeline: generation, IQ sampling, constraints
- direction-finding.md — AoA/AoD angle estimation with bleAngleEstimate
- rfphy-compliance-testing.md — RF-PHY test waveform patterns and config
- transmitter-measurements.md — OBW, PAPR, power measurement patterns
- practical-receiver-simulation.md — BER simulation and impaired receiver
- wlan-coexistence.md — WLAN interference and coexistence scenarios
- rssi-localization.md — RSSI beacon and distance estimation patterns
- data-whitening.md — Whitening on/off comparison and internals
Parameters (bleWaveformGenerator)
| Param | Default | Range | |
|---|---|---|---|
Mode | "LE1M" | LE1M, LE2M, LE500K, LE125K | |
ChannelIndex | 37 | 0-39 | |
SamplesPerSymbol | 8 | >=1 (use >=4 for plots) | |
WhitenStatus | "On" | On, Off | |
DFPacketType | "Disabled" | Disabled, ConnectionCTE, ConnectionlessCTE | |
AccessAddress | adv default | 32-bit binary col vector | |
ModulationIndex | 0.5 | [0.45, 0.55] | |
PulseLength | 1 | [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:4wf = 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:3wf = 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;endsemilogy(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 payloadpayload = 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
| Mode | ConnectionCTE | ConnectionlessCTE | |
|---|---|---|---|
| LE1M | Yes | Yes | |
| LE2M | Yes | No | |
| LE500K | No | No | |
| LE125K | No | No |
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)
| Request | Why Invalid | |
|---|---|---|
| LE2M + FEC coding | FEC only exists in coded PHY (LE500K/LE125K) | |
| LE2M on ch 37/38/39 | Primary advertising = LE1M or Coded only | |
| CTE + LE500K/LE125K | CTE restricted to LE1M/LE2M per BLE 5.1 spec | |
| ConnectionlessCTE + LE2M | ConnectionlessCTE = LE1M only | |
| "3-slot" or "5-slot" BLE | Slot-based = BR/EDR; BLE uses single packets | |
| CTE on ch 37/38/39 | CTE = data channels (0-36) only | |
| ISO params where BN×IRC×Sub_Int > ISO_Interval | Sub-events exceed interval = impossible scheduling |
Conventions
fs = (1e6 + 1e6*(mode=="LE2M")) * sps— never hardcode- Axes: us (time), MHz (freq), dB (power)
tiledlayout/nexttile(notsubplot)- Spectrum: always `20*log10(abs(fftshift(fft(wf)))/N + eps)` — do NOT use
periodogram,pspectrum, orpwelchfor 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>=4for 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 modepayloadLength = 37; % Payload length in bytessps = 8; % Samples per symbolchannelIndex = 37; % BLE channel index (0-39)% Derived parameterssymbolRate = 1e6 + 1e6*(phyMode=="LE2M"); % 2 Msym/s for LE2M, 1 Msym/s otherwisefs = sps * symbolRate; % Sample rate in HznumBits = 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
endstatements
Naming
- camelCase:
messageBits,txWaveform,symbolRate,channelIndex - Descriptive loop counters:
countMode,pktIdx(noti,j) - Named constants:
bitsPerByte = 8,tIFS = 150e-6 - Multi-init:
[numErrors, perCount] = deal(0, 0)
Gotchas
| Mistake | Fix | |
|---|---|---|
bleWaveformConfig(...) | Does not exist. Use bleWaveformGenerator(bits, NV...) directly | |
bluetoothWaveformGenerator for BLE | BR/EDR only. Use bleWaveformGenerator | |
comm.BLEReceiver | Does not exist. Use bleIdealReceiver | |
symbolRate=1e6 for LE2M | LE2M = 2e6 | |
'PHYMode' as param name | Use 'Mode' | |
'LECODED' as mode | Use "LE500K" or "LE125K" | |
| Config object as 1st arg | Must be binary col vector randi([0 1],N,1) | |
Empty [] bits | Minimum 1 byte | |
Real zeros() for IFS | Use complex(zeros(N,1)) | |
| CTE + coded modes | CTE only on LE1M/LE2M | |
ConnectionlessCTE + LE2M | LE1M only | |
| LE2M on ch 37/38/39 | Spec violation. Use LE1M or Coded | |
CTELength/CTEType as NV args | Not valid. Use DFPacketType | |
bleAngleEstimate(wf, NV...) | Use bleAngleEstimate(iqSamples, bleAngleEstimateConfig) | |
bleWaveformGenerator for RF-PHY | Use bluetoothTestWaveformConfig + bluetoothTestWaveform | |
| AWGN without oversampling correction | Use convertSNR(EbNo,"ebno","snr",SamplesPerSymbol=sps); pass explicit power to awgn (never use "measured") | |
bleIdealReceiver with impairments | Use helperBLEPracticalReceiver | |
| Eb/No for coded modes without code rate | SNR = EbNo + 10*log10(codeRate) - 10*log10(sps) | |
| SPS=1 without warning | SPS=1 is at Nyquist limit — warn user: no spectral analysis, marginal decode performance | |
Timing ppm offset without resample | Apply clock drift via resample(wf, 1e6+ppm, 1e6) or fractional delay filter | |
| Conformance Df1/Df2 without PRBS9 | Use 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 spectrum | Use manual fftshift(fft(wf)) — skill convention | |
SN as property name | Use 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 AdvertisingData | Use hex string: "0201060709..." (length-type-value) |
Copyright 2026 The MathWorks, Inc.