Two fixes for the frame sync timing bug reported by uart-agent:
1. CFO Overwritten by Timing Refinement
- The _refine_symbol_boundary() returns a bin that reflects timing
offset, not CFO. For aligned loopback signals, any timing shift k
produces bin=k, incorrectly interpreted as CFO.
- Fix: Keep CFO from state machine instead of overwriting.
2. SFD Correlation Noise Issues
- For perfectly aligned signals, skip SFD correlation and use known
frame structure offset (preamble_count + 4.25 symbols).
- For real captures, use SFD correlation with adjusted search start.
Also updates SFD search start from (preamble_count + 1) to
(preamble_count + 3) for real captures to match existing decoder.
Loopback test: 50/50 seeds pass (100%)
Real SDR capture: All 10 bins match existing decoder
Implement precision timing recovery functions:
- _refine_symbol_boundary(): Scans at 1/32-symbol resolution to find
exact chirp boundary by maximizing dechirped SNR
- _find_sfd_boundary(): FFT-based correlation with downchirp template
to find exact data start position
Bug fixes:
- Fix _is_downchirp() false positives by comparing both correlations
- Fix _estimate_cfo() to return values in [0, N) range
The improved sync_from_samples() now produces bins identical to the
reference lora_decode_gpu decoder.
- Add Channelizer class for wideband capture processing (2 MHz → 125 kHz)
- FIR low-pass filter with scipy.firwin (or fallback windowed-sinc)
- Proper decimation for anti-aliasing
- Fix FrameSync preamble detection to accept any CFO
- Real captures have significant carrier frequency offset
- Preamble bins appear at arbitrary values, not just near 0
- Now accepts any strong signal as first preamble, validates consistency
- Add decode_capture.py example script for processing raw BladeRF captures
- PHYDecode verified to match existing lora_phy decoder output