Turn a $3 LoRa chip into a coherent linear-FM chirp generator, discipline it with a GPSDO, and dechirp the echoes in an SDR. Meteor radar, HF backscatter, transverter IF, ranging — all from one little square.
⚠️ Heads up: this is an experimenter's notebook, not a product manual. Before you transmit anything, check the rules in your country / ITU region. When in doubt, use a dummy load, pick a quiet band, coordinate with the locals, and be ready to ask forgiveness over permission. Radar-like waveforms on shared spectrum are powerful — don't be a jerk with them.
The Semtech SX1262 is a sub-GHz LoRa transceiver that natively generates linear-FM ("chirp") waveforms as part of its modulation. Two of its registers — one opcode, really — let you emit unmodulated upchirps forever, at chirp rates from 15 kHz/s to ~4 GHz/s, with phase determined entirely by the 32 MHz reference clock. Feed that reference from a GPSDO (for example the Leo Bodnar 32 MHz GPSDO) and every chirp is frequency-coherent with UTC to ppb — the slope, center, and PRF never drift. (Start time is a separate problem; UTC doesn't answer its SPI bus. See Timing & triggering.) Mix the result with its own conjugate in an SDR and you get pulse-compression gain of 3·SF dB (up to ~36 dB for SF12) with the range resolution of the chosen bandwidth.
That is a radar. A very small radar. On ~$20 of parts.
Range-Doppler capture — an example of a chirp travelling over 20 000 km on HF. With coherent gain like this, you start to see signals that have travelled all the way around the world.
- A cool idea
- Anatomy of a LoRa chirp
- The 65k-chirp ceiling — and how to go forever
- Register-level frequency control (skipping Radiolib's caps)
- Locking to a GPS reference
- Timing & triggering — what a GPSDO does and doesn't buy you
- The Big Deal — clean IQ sample rates make the math evaporate
- Chirp parameter tables
- How to pick SF and BW for your radar
- SDR dechirp pipeline
- Cool plots — seeing the physics
- Applications
- Interactive calculator
- Legal & safety
- Further reading
LoRa's "chirp spread spectrum" modulation is, at its core, linear FM. Each symbol is a
frequency ramp that sweeps the modulation bandwidth BW in a symbol time Tsym.
Data is encoded in the starting frequency of each ramp, but the LoRa preamble is a run
of plain, unmodulated upchirps — and the SX1262 has a one-byte command
(SetTxContinuousPreamble) that makes it emit those preamble upchirps indefinitely.
That is exactly the waveform we want for coherent radar:
- Linear FM → pulse compression via stretch / dechirp processing.
- Identical chirps back-to-back → coherent integration across pulses.
- Synthesizer locked to the 32 MHz reference → swap the xtal for a GPSDO and every chirp is frequency-coherent with UTC. Two boxes across town, sharing a reference, generate chirps that are bit-for-bit identical in shape — the only remaining question for bistatic operation is when each one starts pressing "play," which is a separate PPS/trigger problem covered below.
The SX1262 can transmit anywhere from roughly 50 MHz to 1.3 GHz — well beyond the 150–960 MHz the datasheet nominally promises, and well beyond what most host libraries let you pass in. The PLL will lock there; you just have to write the registers directly.
Two knobs set everything:
-
Spreading Factor SF ∈ {5, 6, 7, 8, 9, 10, 11, 12} — symbol length in chips.
-
Bandwidth BW — chosen from a fixed list derived from FXTAL/N with N ∈ {64, 128, 256, 512, 768, 1024, 1536, 2048, 3072, 4096}:
BW (kHz) Divisor N BW (Hz, exact) 7.8125 4096 32 000 000 ÷ 4096 10.4167 3072 32 000 000 ÷ 3072 15.6250 2048 32 000 000 ÷ 2048 20.8333 1536 32 000 000 ÷ 1536 31.2500 1024 32 000 000 ÷ 1024 41.6667 768 32 000 000 ÷ 768 62.5000 512 32 000 000 ÷ 512 125.0000 256 32 000 000 ÷ 256 250.0000 128 32 000 000 ÷ 128 500.0000 64 32 000 000 ÷ 64
From those two numbers, everything else falls out:
T_sym = 2^SF / BW (symbol length, seconds)
μ = BW / T_sym = BW² / 2^SF (chirp slope, Hz/s)
PRF = 1 / T_sym = BW / 2^SF (pulse repetition frequency)
BT = BW · T_sym = 2^SF (time-bandwidth product)
G_pc = 10·log10(2^SF) ≈ 3.01·SF (pulse-compression gain, dB)
Δr = c / (2·BW) (range resolution, monostatic)
R_ua = c · T_sym / 2 (unambiguous range, monostatic)
f_d,ua = PRF / 2 (unambiguous Doppler, ± one-sided)
v_ua = c · PRF / (4 · f_0) (unambiguous radial velocity)
- Tsym — the pulse length. 128 µs at the fastest, half a second at the slowest.
- μ — the chirp slope, in Hz/s. From ~15 kHz/s (SF12, BW 7.8125 kHz) to ~3.9 GHz/s (SF6, BW 500 kHz). Sets the beat-frequency-to-range scaling.
- PRF — pulse repetition frequency if you run chirps back-to-back.
- BT — the time-bandwidth product. Equal to 2SF because of how LoRa's symbol length is defined.
- Gpc — dechirp / pulse-compression gain. Each step up in SF buys ~3 dB of processing gain and doubles the pulse length. SF12 gives ~36 dB over a bare CW tone of the same peak power. That is why this works at all.
At BW = 500 kHz the range resolution is 299.792 m. At BW = 7.8125 kHz it is 19.186 km. The tradeoff: narrower BW means longer pulses and finer Doppler — great for meteor trails, long-range HF backscatter, and anything where you care about velocity more than range.
One relation worth keeping in your pocket: multiplying unambiguous range by unambiguous radial velocity gives a quantity that depends only on the carrier:
R_ua · v_ua = c² / (8 · f_0)
At f0 = 50 MHz that product is 2.248·108 m²/s. Pick any two numbers whose product is that value and you can find an (SF, BW) pair that delivers them.
LoRa packets start with a preamble of N unmodulated upchirps. N is a 16-bit register
inside SetPacketParams, so the maximum packet-mode preamble is 65 535 symbols —
hence the "65k chirp" number you'll see people quote. At SF12 / BW = 7.8125 kHz that is
already 5 hr 43 min of chirping, so for most purposes it is effectively unlimited, but
there is a cleaner answer.
Opcode SetTxContinuousPreamble (0xD2, no parameter bytes) makes the chip emit
preamble upchirps indefinitely, until you issue SetStandby (0x80). No packet
structure, no sync word, no payload — just a perfect chain of identical upchirps.
The related opcode SetTxContinuousWave (0xD1) emits pure CW at the PLL frequency —
useful for calibration, for transverter IF tones, and for measuring the crystal offset.
Most LoRa host libraries clamp you to one of the ISM bands (433 / 868 / 915 MHz). That's software politeness, not silicon limit. Under the hood the SX1262 has one 32-bit frequency register and a PLL that will lock well outside the "supported" range.
SetRfFrequency (0x86) takes a 32-bit integer RF_Freq:
RF_Freq = floor( f_RF · 2^25 / F_XTAL )
With FXTAL = 32 MHz this gives a tuning step of 32·106 / 225 ≈ 0.9537 Hz. The PLL is happy from somewhere around 50 MHz up through 1300 MHz in practice. Above ~960 MHz the internal matching starts to roll off but it still radiates; below ~150 MHz the on-chip PA is out of spec but still produces usable output for receive-only / low-level work, and for an IF source into a mixer it doesn't matter at all.
Suggested init order (register-level, no library required):
| Step | Opcode | Notes |
|---|---|---|
1. SetStandby(STDBY_RC) |
0x80 0x00 | always start here |
2. SetPacketType(LORA) |
0x8A 0x01 | picks LoRa modulation engine |
3. SetRfFrequency(f) |
0x86 + 4 bytes | the 32-bit word above |
4. CalibrateImage(f1,f2) |
0x98 + 2 bytes | band-edge bytes per datasheet §13.1.4 |
5. SetModulationParams(SF,BW,CR=1,LDRO=auto) |
0x8B + 4 bytes | only SF and BW matter for radar |
6. SetPacketParams(preamble=0xFFFF,…) |
0x8C + 9 bytes | preamble length only matters in finite mode |
7. SetPaConfig / SetTxParams |
0x95 / 0x8E | set power (ramp is moot for continuous) |
8. SetTxContinuousPreamble |
0xD2 | fire the chirp train |
9. SetStandby(STDBY_RC) |
0x80 0x00 | to stop |
The calibration step (0x98) is the only one that actually cares about the "allowed" bands — and even there, picking the nearest entry from the datasheet table works fine for out-of-band frequencies; you just accept a small image-rejection penalty.
The PA matching on an off-the-shelf E22 module is narrow-band — if you jump from 915 MHz down to 50 MHz you will be lossy at the antenna port. For a radar source that's often fine: you filter / amplify externally anyway. Use a directional coupler + 20 dB pad as a built-in reference echo for delay calibration.
The SX1262 output is not a clean sine wave — it's a square-ish PA switching waveform rich in odd harmonics (2nd, 3rd, 5th, 7th, …) plus some LO leakage. At high carrier frequencies the harmonics are spaced far apart in absolute terms (915 MHz fundamental → 2f = 1.83 GHz → 3f = 2.745 GHz), so a one-pole LPF kills them. At low frequencies the absolute spacing collapses: a 50 MHz fundamental puts harmonics at 100, 150, 200, 250 MHz — crowding into the 2 m band, the aviation band, FM broadcast, the whole VHF mess. Worse, the module's PA output network is matched for ~900 MHz, so at 50 MHz the odd harmonics are only 10–15 dB down from the fundamental instead of 40+ dB.
Always run the output through a bandpass filter matched to your actual operating frequency. A 5-element Chebyshev bandpass is cheap and easy at HF/VHF; for UHF, a simple LC tank + LPF combo will do. Don't trust the module. Don't skip the filter because the harmonics look "small" on a scope — they are plenty big enough to make enemies in the ham community and the FCC alike.
The SX1262 synthesizer is directly referenced to FXTAL. Every frequency it generates — center frequency, chirp slope, symbol rate, everything — scales linearly with the xtal. Drift a crystal 10 ppm and your chirps drift 10 ppm. Lock it to GPS and they don't drift at all.
Physically, the SX1262 accepts either a crystal (XTA ↔ XTB) or a clipped-sine / square TCXO drive on XTA with XTB left open. To feed an external reference:
- Disable the onboard TCXO control path — don't call
SetDIO3AsTcxoCtrlat init, or the DIO3 pin will try to power a TCXO that isn't there and the chip will wait forREADYforever. - Remove the module's xtal / TCXO and DC-block the GPSDO output into XTA through a small cap (10–100 nF). Leave XTB floating (datasheet §4.2).
- Target level ~0.6–1.2 Vpp sine or CMOS-level square. The Leo Bodnar 32 MHz GPSDO outputs clean CMOS that works directly; attenuate if the level is too high.
- Isolation matters. Put the buffer/amp close to the module, keep the reference trace short, and ground everything.
Once locked, the SX1262 becomes a frequency-disciplined chirp source — every slope, every center, every PRF, every tone derived from FXTAL, all pinned to UTC frequency to ppb. If you also reference your SDR to the same GPSDO (10 MHz input on most SDRs), your transmitter and receiver share a clock and the whole chain is coherent in frequency. This is the setup you want for any real radar work. What it does not give you for free is when each chirp starts — for that, keep reading.
Worth stating precisely, because the difference matters in practice:
What the GPSDO buys you (free):
- Chirp slope μ, center f0, PRF, and every other rate derived from FXTAL are locked to UTC frequency at the ppb level.
- Two boxes sharing the reference produce chirps whose waveforms are identical — slope, shape, phase noise, all of it. They're copies.
- Coherent integration over any practical dwell (minutes to hours, limited by GPSDO holdover) is essentially free.
What it doesn't buy you:
- A chirp starts when your MCU finishes clocking the
SetTxContinuousPreambleopcode out over SPI. That moment has tens of microseconds of jitter from SPI timing, IRQ latency,millis()quantization, OS scheduling, etc. The chirps themselves are metronomic, but chirp #1 is not pinned to any particular UTC tick. - Two boxes GPSDO'd to the same reference but separately started will produce identical chirp streams that are offset by an unknown (but stable) amount. The frequencies line up; the timelines don't.
How to get UTC-aligned starts if you actually need them
| Method | Jitter | Notes |
|---|---|---|
PPS-driven firmware — MCU waits for rising PPS edge, then issues SetTxContinuousPreamble |
~5–50 µs | trivial to implement, limited by SPI transaction + firmware overhead |
Hardware gate — CPLD/FPGA watches PPS, drives SX1262 NRESET or gates DIO on PPS edge |
sub-µs | build-your-own but clean |
| Self-synchronization — skip the trigger, send a known preamble pattern, let the RX solve for the time offset via cross-correlation of the first few chirps | none (offset recovered in software) | this is what most amateur bistatic setups actually do |
| Direct-path calibration — monostatic only, the TX-to-RX leakage is your t = 0 marker, no PPS needed | none | the cheat code |
For a one-box monostatic radar, you don't need a trigger at all — the direct-path spike anchors your time axis. For a two-box bistatic, the self-sync cross-correlation approach is usually fine for HF and VHF work where path-length uncertainty is already ≫ µs. Reach for the FPGA only if you're chasing sub-meter bistatic ranging.
Nomenclature note. Throughout this section Fs means the IQ sample rate — i.e. the rate at which your SDR delivers complex (I+jQ) samples. If your SDR advertises "2 MS/s" on a single I or Q stream it's already IQ at 2 MS/s in the complex-sample sense; if it advertises "2 MHz of real-IF baseband," that's 1 MS/s of IQ. When in doubt, take your chirp for a test drive and count samples per chirp — you will know.
Most dechirp algorithms run a per-symbol FFT. They work best when exactly an integer number of IQ samples fits in one chirp — better still, a power of two, so the FFT is free and zero-padding is optional rather than compensatory. Otherwise you either window and lose SNR, or fractional-resample and spend CPU, or just live with a few dB of scalloping in your processing gain and slow-time phase drift across chirps.
Because LoRa's bandwidths are defined as FXTAL/N and the symbol length is 2SF/BW, the symbol duration is always a rational fraction of FXTAL:
T_sym = (2^SF · N) / F_XTAL
samples_per_chirp = F_s · T_sym = (2^SF · N · F_s) / F_XTAL
Set the IQ sample rate (Fs) to a power-of-two divisor of 32 MHz. Then samples-per-chirp reduces to 2SF·N / 2k, which is always integer and almost always a pure power of two (the three LoRa BWs that use N = 768, 1536, 3072 pick up a factor of 3, so samples-per-chirp = 2n·3 — still integer, still a single FFT-friendly multiply away from a power of two).
Nice IQ sample rates with FXTAL = 32 MHz:
| IQ sample rate Fs | Divisor k (2k) | Comment |
|---|---|---|
| 1 MS/s | 32 (25) | easy on every SDR |
| 500 kS/s | 64 (26) | |
| 250 kS/s | 128 (27) | |
| 125 kS/s | 256 (28) | |
| 62.5 kS/s | 512 (29) | the sweet spot — narrow enough to fit in a Raspberry Pi's USB, wide enough to cover any LoRa BW |
| 31.25 kS/s | 1024 (210) | audio-card territory |
The key identity:
FFT_size = samples_per_chirp = 2^SF · N · F_s / F_XTAL
= 2^SF · N / 512 (at F_s = 62.5 kS/s, F_XTAL = 32 MHz)
Every one of the 70 LoRa SF×BW combinations gives an integer samples-per-chirp, and that number is literally the FFT length you feed your dechirp routine. No zero-padding, no rounding.
LoRa's BWs split into two families by divisor:
Tier 1 — "binary" BWs (divisor N = 2m) → FFT size is a pure 2n. These are the seven BWs you actually want for radar work.
| BW | Divisor N | SF6 | SF7 | SF8 | SF9 | SF10 | SF11 | SF12 |
|---|---|---|---|---|---|---|---|---|
| 500 kHz | 64 | 8 | 16 | 32 | 64 | 128 | 256 | 512 |
| 250 kHz | 128 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 |
| 125 kHz | 256 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 |
| 62.5 kHz | 512 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096 |
| 31.25 kHz | 1024 | 128 | 256 | 512 | 1024 | 2048 | 4096 | 8192 |
| 15.625 kHz | 2048 | 256 | 512 | 1024 | 2048 | 4096 | 8192 | 16384 |
| 7.8125 kHz | 4096 | 512 | 1024 | 2048 | 4096 | 8192 | 16384 | 32768 |
Tier 2 — "×3" BWs (divisor N = 2m·3) → FFT size is 2n·3. These three BWs exist for AU/JP LoRa channel spacing and carry an inherent factor of 3. The FFT is not pure 2n, but it is integer and mixed-radix FFT libraries (FFTW, KissFFT, numpy, scipy) handle it at full speed.
| BW | Divisor N | SF6 | SF7 | SF8 | SF9 | SF10 | SF11 | SF12 |
|---|---|---|---|---|---|---|---|---|
| 41.6667 kHz | 768 | 96 | 192 | 384 | 768 | 1536 | 3072 | 6144 |
| 20.8333 kHz | 1536 | 192 | 384 | 768 | 1536 | 3072 | 6144 | 12288 |
| 10.4167 kHz | 3072 | 384 | 768 | 1536 | 3072 | 6144 | 12288 | 24576 |
If your FFT code is 2n-only, pad to the next power of two and wear ~0.6 dB of scalloping on adjacent bins. Better: use an FFT library that does mixed radix.
FFT size scales linearly with the IQ sample rate. Double Fs → double every cell above. At Fs = 125 kS/s (= FXTAL/256) every Tier 1 cell is still a pure 2n, just one power larger. At 1 MS/s IQ, multiply by 16. The property is preserved for any Fs = FXTAL/2k.
Bottom line: pick any IQ sample rate from the clean list, pick SF and BW from Tier 1, and your FFT length is the number of samples you just captured per chirp. Zero padding is optional cosmetics.
If your SDR can't produce 62.5 kS/s natively, capture at a higher FXTAL/2k rate (say 1 MS/s or 2 MS/s) and decimate by a power of two in software — the decimation ratio stays clean and the result is still integer-aligned.
Rates to pick from (with FXTAL = 32 MHz): 31.25 kS/s, 62.5 kS/s, 125 kS/s, 250 kS/s, 500 kS/s, 1 MS/s, 2 MS/s, 4 MS/s, 8 MS/s, 16 MS/s, 32 MS/s, 64 MS/s. Anything else (48 kS/s, 48 MS/s, 62.5 MS/s, …) loses the integer-samples property and forces you into software resampling. Pick one from this list and stop thinking about it.
The ideal bench: one GPSDO at 32 MHz, fed to the chip's XTA and to the SDR's 10 MHz reference input (through a ÷3.2 divider, or a separate synthesizer channel that outputs 10 MHz phase-locked to the same OCXO). SDR sample rate = FXTAL/2k. Everything is coherent and integer-aligned by construction.
All values derived from T_sym = 2^SF / BW, μ = BW² / 2^SF, PRF = BW / 2^SF.
Every cell is shown to 4 decimals for consistency, even when the underlying value
terminates earlier.
| SF\BW | 7.8125 kHz | 10.4167 kHz | 15.6250 kHz | 20.8333 kHz | 31.2500 kHz | 41.6667 kHz | 62.5000 kHz | 125.0000 kHz | 250.0000 kHz | 500.0000 kHz |
|---|---|---|---|---|---|---|---|---|---|---|
| 6 | 122.0703 | 162.7604 | 244.1406 | 325.5208 | 488.2812 | 651.0417 | 976.5625 | 1953.1250 | 3906.2500 | 7812.5000 |
| 7 | 61.0352 | 81.3802 | 122.0703 | 162.7604 | 244.1406 | 325.5208 | 488.2812 | 976.5625 | 1953.1250 | 3906.2500 |
| 8 | 30.5176 | 40.6901 | 61.0352 | 81.3802 | 122.0703 | 162.7604 | 244.1406 | 488.2812 | 976.5625 | 1953.1250 |
| 9 | 15.2588 | 20.3451 | 30.5176 | 40.6901 | 61.0352 | 81.3802 | 122.0703 | 244.1406 | 488.2812 | 976.5625 |
| 10 | 7.6294 | 10.1725 | 15.2588 | 20.3451 | 30.5176 | 40.6901 | 61.0352 | 122.0703 | 244.1406 | 488.2812 |
| 11 | 3.8147 | 5.0863 | 7.6294 | 10.1725 | 15.2588 | 20.3451 | 30.5176 | 61.0352 | 122.0703 | 244.1406 |
| 12 | 1.9073 | 2.5431 | 3.8147 | 5.0863 | 7.6294 | 10.1725 | 15.2588 | 30.5176 | 61.0352 | 122.0703 |
| SF\BW | 7.8125 kHz | 10.4167 kHz | 15.6250 kHz | 20.8333 kHz | 31.2500 kHz | 41.6667 kHz | 62.5000 kHz | 125.0000 kHz | 250.0000 kHz | 500.0000 kHz |
|---|---|---|---|---|---|---|---|---|---|---|
| 6 | 0.9537 | 1.6954 | 3.8147 | 6.7817 | 15.2588 | 27.1267 | 61.0352 | 244.1406 | 976.5625 | 3906.2500 |
| 7 | 0.4768 | 0.8477 | 1.9073 | 3.3908 | 7.6294 | 13.5634 | 30.5176 | 122.0703 | 488.2812 | 1953.1250 |
| 8 | 0.2384 | 0.4239 | 0.9537 | 1.6954 | 3.8147 | 6.7817 | 15.2588 | 61.0352 | 244.1406 | 976.5625 |
| 9 | 0.1192 | 0.2119 | 0.4768 | 0.8477 | 1.9073 | 3.3908 | 7.6294 | 30.5176 | 122.0703 | 488.2812 |
| 10 | 0.0596 | 0.1060 | 0.2384 | 0.4239 | 0.9537 | 1.6954 | 3.8147 | 15.2588 | 61.0352 | 244.1406 |
| 11 | 0.0298 | 0.0530 | 0.1192 | 0.2119 | 0.4768 | 0.8477 | 1.9073 | 7.6294 | 30.5176 | 122.0703 |
| 12 | 0.0149 | 0.0265 | 0.0596 | 0.1060 | 0.2384 | 0.4239 | 0.9537 | 3.8147 | 15.2588 | 61.0352 |
| SF\BW | 7.8125 kHz | 10.4167 kHz | 15.6250 kHz | 20.8333 kHz | 31.2500 kHz | 41.6667 kHz | 62.5000 kHz | 125.0000 kHz | 250.0000 kHz | 500.0000 kHz |
|---|---|---|---|---|---|---|---|---|---|---|
| 6 | 8.1920 | 6.1440 | 4.0960 | 3.0720 | 2.0480 | 1.5360 | 1.0240 | 0.5120 | 0.2560 | 0.1280 |
| 7 | 16.3840 | 12.2880 | 8.1920 | 6.1440 | 4.0960 | 3.0720 | 2.0480 | 1.0240 | 0.5120 | 0.2560 |
| 8 | 32.7680 | 24.5760 | 16.3840 | 12.2880 | 8.1920 | 6.1440 | 4.0960 | 2.0480 | 1.0240 | 0.5120 |
| 9 | 65.5360 | 49.1520 | 32.7680 | 24.5760 | 16.3840 | 12.2880 | 8.1920 | 4.0960 | 2.0480 | 1.0240 |
| 10 | 131.0720 | 98.3040 | 65.5360 | 49.1520 | 32.7680 | 24.5760 | 16.3840 | 8.1920 | 4.0960 | 2.0480 |
| 11 | 262.1440 | 196.6080 | 131.0720 | 98.3040 | 65.5360 | 49.1520 | 32.7680 | 16.3840 | 8.1920 | 4.0960 |
| 12 | 524.2880 | 393.2160 | 262.1440 | 196.6080 | 131.0720 | 98.3040 | 65.5360 | 32.7680 | 16.3840 | 8.1920 |
| SF | BT = 2SF | Gpc (dB) |
|---|---|---|
| 6 | 64 | 18.0618 |
| 7 | 128 | 21.0721 |
| 8 | 256 | 24.0824 |
| 9 | 512 | 27.0927 |
| 10 | 1024 | 30.1030 |
| 11 | 2048 | 33.1133 |
| 12 | 4096 | 36.1236 |
| BW (kHz) | Δr = c / (2·BW) |
|---|---|
| 7.8125 | 19 186.40 m |
| 10.4167 | 14 389.80 m |
| 15.6250 | 9 593.20 m |
| 20.8333 | 7 194.90 m |
| 31.2500 | 4 796.60 m |
| 41.6667 | 3 597.45 m |
| 62.5000 | 2 398.30 m |
| 125.0000 | 1 199.15 m |
| 250.0000 | 599.58 m |
| 500.0000 | 299.79 m |
The 70-cell parameter grid is really three zones with different personalities. Pick the zone that matches your target class and propagation channel — then fine-tune SF / BW inside it.
- Pulse length 0.128 – 1.024 ms (fast, many pulses/s)
- PRF ~500 Hz – 7.8 kHz → unambiguous Doppler ±250 Hz to ±3.9 kHz
- Range resolution 300 m – 1.2 km
- Unambiguous range 19 – 153 km
- Processing gain 18 – 21 dB
- Good for: close-in ranging, drones, aircraft at moderate distance, transverter ranging at 10 / 24 GHz (where tiny Δr pays off), demo experiments on a bench with a dummy load as the target.
- Watch out for: the 4 GHz/s slope at SF6 / BW = 500 kHz is steep — your SDR needs to sample fast enough (≥ BW with margin) and your dechirp FFT window must be short enough that the slope doesn't smear across bins within a chirp.
- Pulse length 4 – 33 ms
- PRF ~30 – 500 Hz → unambiguous Doppler ±15 Hz to ±250 Hz
- Range resolution 1.2 – 9.6 km
- Unambiguous range 600 km – 5 000 km
- Processing gain 24 – 27 dB
- Good for: 6 m meteor radar (the canonical fit), airborne traffic at 100 – 500 km, weather balloon tracking, general experiment-bench "where is that echo coming from" work. This is the sweet spot — reasonable resolution, reasonable Doppler, plenty of processing gain, pulse lengths short enough that air-side coherence isn't a problem.
- Watch out for: second-time-around echoes from distant reflectors (mountains on HF, the ionosphere on VHF). If you see a "target" that always sits at the same range and never Doppler-shifts, it's probably a tail from the previous chirp.
Zone C — Long range, slow targets, or propagation sounding. High SF (10–12) × narrow BW (7.8125 – 31.25 kHz)
- Pulse length 33 ms – 524 ms (long!)
- PRF ~2 – 30 Hz → unambiguous Doppler ±1 Hz to ±15 Hz
- Range resolution 4.8 – 19.2 km (coarse — you trade range for gain)
- Unambiguous range 5 000 km – ~78 600 km (≈ 2× geostationary altitude; about one-fifth of the Earth–Moon distance at the extreme)
- Processing gain 30 – 36 dB
- Good for: HF backscatter / chirpsounder-lite (narrow BW matches the propagation channel — you don't gain anything from resolving finer than the ionosphere's coherence bandwidth), coherent propagation beacons (run SF12 continuously, correlate days of IQ at a remote SDR), bistatic rangefinding over continental baselines, Doppler-only studies of meteor trails or ionospheric drifts.
- Watch out for: at half-second pulses, even modest radial velocity walks a target across several range bins during one chirp — this is range-Doppler coupling in FMCW, and it distorts the single-chirp FFT peak into a ramp. Compensate by estimating velocity from the slow-time FFT and correcting, or accept some SNR loss.
- Want range resolution? Crank BW up. It is the only knob that buys you Δr.
- Want processing gain? Crank SF up. Each step is ~3 dB.
- Want unambiguous range? Crank SF up or BW down (both lengthen Tsym).
- Want unambiguous Doppler? Crank BW up or SF down (both raise PRF).
- Everything is a tradeoff. Rua · vua = c² / (8 · f0) is a hard wall; you cannot get both high unambiguous range and high unambiguous velocity on one carrier.
The whole point. You recorded IQ of your own chirp plus whatever bounced off the world. Now:
-
Tune the SDR to the chirp center frequency f0. Set the IQ sample rate Fs ≥ 1.2·BW, picked from the clean-rate list in The Big Deal. Share the reference clock with the chirp source.
-
Align to symbol boundaries. Either trigger on a PPS pulse from the GPSDO, or find the direct-path peak in the first dechirp and use it as t0.
-
Generate the local reference — the conjugate of one upchirp, at baseband:
s_ref(t) = exp( -j·π·μ·t² ), 0 ≤ t < T_sym -
Dechirp by multiplying, per symbol:
y[n] = x_rx[n] · s_ref[ n mod N_sym ]A target at round-trip delay τ becomes a tone at beat frequency fb = μ·τ.
-
Fast-time FFT per symbol: peaks at fb give range:
R = c · f_b / (2 · μ) -
Slow-time FFT across symbols: peaks give Doppler (radial velocity):
v = c · f_d / (2 · f_0) -
Stack the result as a range-Doppler map. Each pixel is one resolution cell.
Practical notes.
- The direct leakage from TX to RX will be the biggest peak in every frame — put a notch, or simply subtract the median across slow-time (it's stationary) before the Doppler FFT.
- For bistatic geometry (TX and RX separated), the beat frequency maps to path-length difference, not range. Same math, different geometry.
- LoRa upchirps wrap around the band edge; the SX1262 produces a clean sawtooth — but a tiny retrace artifact appears every Tsym. Discard the last ~1 % of each symbol before the FFT.
- Processing gain is 10·log10(BT) = 3.01·SF dB per symbol. Coherent integration over M symbols adds another 10·log10(M) dB, provided your target and clocks stay coherent across that dwell. A GPSDO usually gives you minutes.
All four plots below come from examples/chirp_plots.py —
a single self-contained script (numpy + scipy + matplotlib). They use the
HF / VHF / 6 m sweet spot: SF = 10, BW = 15.625 kHz, Fs = 31.25 kS/s
(a clean FXTAL/1024 rate → exactly 2048 samples per chirp, a pure
211). Pulse length is 65.5 ms, PRF = 15.26 Hz, processing gain
30.1 dB. Carrier set to 14 MHz for an HF-backscatter flavour. Range resolution is
coarse (Δr = 9.6 km) but that is exactly the right granularity for ionospheric and
long-baseline propagation work. Re-run the script with your own parameters; every
number below updates itself.
Top: the I and Q of one 65.5 ms upchirp — the sinusoid frequency visibly rises across the pulse. Bottom: spectrogram of the same waveform. The diagonal line is the linear FM ramp. That's it. That's the entire radar waveform.
The auto-ambiguity function |χ(τ,ν)|² is the classic radar-waveform fingerprint: how a single pulse correlates with a time- and frequency-shifted copy of itself. For a linear FM chirp, the result is a diagonal ridge along ν = μ·τ. That ridge is the fundamental reason FMCW radar can't tell a distant stationary target apart from a near target moving toward you — they produce the same beat frequency. The only way out is to break the symmetry: alternate up and down chirps, vary the slope, or use slow-time FFTs across many identical chirps (the method in the next plot). This one picture explains 80 % of what makes FMCW radar interesting.
Analytical form, rect-enveloped LFM pulse of length T and slope μ (Levanon & Mozeson, Radar Signals, eq. 4.19):
χ(τ,ν) = (1 − |τ|/T) · sinc[ (ν − μτ) · (T − |τ|) ] for |τ| ≤ T
Left: 65.5 ms of received IQ with a target echo at R = 400 km buried in noise 10 dB stronger than the signal — the chirp is invisible in the time domain. Right: the same data, dechirped (multiply by local conjugate upchirp), Hann-windowed, FFT'd, and coherently summed over 8 chirps. A ~30 dB peak pops out at the expected beat frequency −μ·τ = −640 Hz, corresponding to R = 400 km. Theoretical gain is 10·log10(BT·M) = 10·log10(1024·8) = 39.1 dB — we get essentially all of it.
128 chirps coherently integrated (8.4 s total dwell), three injected "targets" mimicking HF ionospheric reflections at 14 MHz: (150 km, 0 m/s) — E-layer bounce, (400 km, +15 m/s) — F-layer drift, (1200 km, −25 m/s) — multi-hop. Dechirp on every chirp → fast-time FFT → stack → slow-time FFT across chirps → magnitude in dB. Each target lands on its own (range, velocity) cell with the predicted resolution (Δr = 9.6 km from BW = 15.625 kHz; ΔV ≈ c / (2·f0·Tdwell) = 1.3 m/s at f0 = 14 MHz, Tdwell = 8.4 s). This is what an amateur chirpsounder display looks like. You are one bandpass filter and one GPSDO away from making one.
6 m meteor radar. Put the SX1262 at 50 MHz — well below its intended band but the PLL still locks — feed an external LPF and driver amp to boost to a watt or two, and point a simple dipole or yagi at the sky. Meteor ionization trails reflect VHF for a few hundred milliseconds. Forward-scatter off commercial 50 MHz TV carriers is a classic; doing it with your own chirp gives you range resolution the commercial beacons can't. Use SF8–SF10, BW 62.5–125 kHz: km-class range bins, hundreds of Hz Doppler resolution, pulse-compression gain around 24–30 dB. Easy on the ears of VUSHF weak-signal ops — the chirp looks like a local digimode signal, not a jammer, because it is.
HF backscatter / chirpsounder-lite. The SX1262 itself only goes down to ~50 MHz but its output can drive a mixer to translate anywhere. Feed an RF mixer with the SX1262 as LO or IF, and you're chirping at 7, 14, 21, 28 MHz. Point a horizontal loop or folded dipole, dechirp in the SDR, and you'll see your own signal come back via F-layer reflection — virtual heights, hop structure, E-layer sporadic blobs, the full ionogram. The BBC and the ionospheric community have been doing this with $10k chirpsounders for decades; you're doing it with a $20 bill of materials. Use SF11–SF12, BW 7.8125–31.25 kHz for the narrow pulses this kind of propagation study wants.
IF source for microwave transverters. 144 or 432 MHz IF into a 10 GHz / 24 GHz transverter, chirp lands on target with full slope preserved. Great for short-range ranging experiments — the mm-wave bands give you big antennas in small boxes and real radar-like resolution. Works as-is for the amateur 3, 5.7, 10, 24 GHz bands; every transverter is just a frequency shift.
Passive / cooperative range finder. Two nodes, same GPSDO, one transmits, one receives. The one-way geometry measures distance between the two boxes to within c/(2·BW) — 300 m at 500 kHz BW, an order of magnitude better with long coherent integration. Fun outdoor demo.
Coherent propagation beacon. Run SF12 / BW = 7.8125 kHz. Each symbol is ~524 ms. PRF is 1.9 Hz. A distant SDR, GPSDO-locked, can dechirp in software and record amplitude/phase over days. You have built a QRP propagation monitor with 36 dB of processing gain baked in.
Open index.html in a browser for a live calculator:
- Pick SF and BW from dropdowns → see Tsym, μ, PRF, BT, Gpc, range resolution, unambiguous range.
- Plots of slope and pulse length across all 70 SF × BW combinations, on log axes.
- An "integer-samples helper": enter your IQ sample rate Fs, see whether it lands on clean samples-per-chirp or not — and pick a recommended rate with one click.
The page is a single static HTML file with Plotly loaded from CDN — drop it on GitHub Pages or open it from disk.
- Spectrum. The SX1262 will happily transmit on frequencies you are not licensed to use. Hold a license, operate within your license class, and know which bands the continuous-chirp-looks-like-radar waveform is welcome on. In most amateur bands, unmodulated chirp experimentation is fine if you identify and coordinate.
- Power. The chip alone puts out ~22 dBm. Anything with a driver/PA after it needs attention: filter harmonics, avoid sensitive receivers nearby, and don't cook your SDR's front-end with direct-path leakage.
- Unintentional radar. Even ~100 mW of chirp at 915 MHz will light up every LoRaWAN gateway within a kilometer and they will not be amused. Pick quiet frequencies, or dummy-load your experiments, or both.
- Health. Near-field exposure to antennas matters at VHF/UHF with real power. Standard RF safety practice applies — distance is your friend.
- Ask forgiveness, not permission is a joke. Sort of. Be smart, be curious, do not be the reason a band gets worse for everyone.
- Semtech SX1261/SX1262 Datasheet — the opcode tables (§13) are the real API.
- Semtech AN1200.22 "LoRa Modulation Basics" — the modulation itself.
- Any FMCW / stretch-processing tutorial covers the dechirp math in detail.
- The Leo Bodnar Mini-Precision GPS Reference Clock — programmable GPSDO (400 Hz – 810 MHz), the tool of choice for feeding the SX1262's XTA and/or disciplining your SDR from the same reference.
If you build something fun with this, tag me on X — @ibelings.




