GNU Radio Out-of-Tree module providing: - Complete TX chain: PHYEncode → FrameGen → CSSMod - Complete RX chain: CSSDemod → FrameSync → PHYDecode - NETWORKID extraction/encoding (0-255 range) - All SF (7-12) and CR (4/5-4/8) combinations - Loopback tested with 24/24 configurations passing Key features: - Fractional SFD (2.25 downchirp) handling - Gray encode/decode with proper inverse operations - gr-lora_sdr compatible decode modes - GRC block definitions and example flowgraphs - Comprehensive documentation Discovered RYLR998 sync word mapping: sync_bin_1 = (NETWORKID >> 4) * 8 sync_bin_2 = (NETWORKID & 0x0F) * 8
113 lines
3.6 KiB
Python
113 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""BladeRF RYLR998 transmitter example.
|
|
|
|
Transmits LoRa frames compatible with RYLR998 modules via BladeRF SDR.
|
|
|
|
Usage:
|
|
python3 bladerf_tx.py --freq 915e6 --networkid 18 --payload "Hello"
|
|
python3 bladerf_tx.py --output frame.raw --sf 9 --payload "Test"
|
|
"""
|
|
|
|
import sys
|
|
import argparse
|
|
import numpy as np
|
|
|
|
# Add parent directory for imports
|
|
sys.path.insert(0, '../python')
|
|
|
|
from rylr998 import PHYEncode, FrameGen
|
|
|
|
|
|
def generate_frame(payload: bytes, sf: int = 9, cr: int = 1,
|
|
networkid: int = 18, preamble_len: int = 8,
|
|
verbose: bool = False) -> np.ndarray:
|
|
"""Generate a LoRa frame as IQ samples.
|
|
|
|
Args:
|
|
payload: Payload bytes
|
|
sf: Spreading factor
|
|
cr: Coding rate (1-4)
|
|
networkid: RYLR998 NETWORKID
|
|
preamble_len: Number of preamble symbols
|
|
verbose: Print debug info
|
|
|
|
Returns:
|
|
Complex64 IQ samples
|
|
"""
|
|
fs = 125e3 # Sample rate = bandwidth
|
|
N = 1 << sf
|
|
|
|
if verbose:
|
|
print(f"Generating frame: SF{sf} CR4/{cr+4} NETWORKID={networkid}")
|
|
print(f" Payload ({len(payload)}B): {payload!r}")
|
|
|
|
# PHY encode
|
|
encoder = PHYEncode(sf=sf, cr=cr, has_crc=True)
|
|
data_bins = encoder.encode(payload)
|
|
|
|
if verbose:
|
|
print(f" Encoded: {len(data_bins)} symbols")
|
|
|
|
# Frame generate
|
|
frame_gen = FrameGen(sf=sf, sample_rate=fs, networkid=networkid,
|
|
preamble_len=preamble_len)
|
|
iq = frame_gen.generate_frame(data_bins)
|
|
|
|
if verbose:
|
|
duration_ms = len(iq) / fs * 1000
|
|
print(f" Generated: {len(iq)} samples ({duration_ms:.2f} ms)")
|
|
|
|
return iq
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="BladeRF RYLR998 LoRa transmitter")
|
|
parser.add_argument("--freq", type=float, default=915e6,
|
|
help="Center frequency (Hz)")
|
|
parser.add_argument("--sf", type=int, default=9,
|
|
help="Spreading factor (7-12)")
|
|
parser.add_argument("--cr", type=int, default=1,
|
|
help="Coding rate (1-4)")
|
|
parser.add_argument("--networkid", type=int, default=18,
|
|
help="NETWORKID (0-255)")
|
|
parser.add_argument("--preamble", type=int, default=8,
|
|
help="Preamble length (symbols)")
|
|
parser.add_argument("--payload", type=str, default="Hello, LoRa!",
|
|
help="Payload string")
|
|
parser.add_argument("--output", "-o", type=str, default=None,
|
|
help="Output file (complex64 raw IQ)")
|
|
parser.add_argument("--verbose", "-v", action="store_true",
|
|
help="Verbose output")
|
|
args = parser.parse_args()
|
|
|
|
# Generate frame
|
|
iq = generate_frame(
|
|
args.payload.encode(),
|
|
sf=args.sf,
|
|
cr=args.cr,
|
|
networkid=args.networkid,
|
|
preamble_len=args.preamble,
|
|
verbose=args.verbose
|
|
)
|
|
|
|
if args.output:
|
|
# Save to file
|
|
iq.tofile(args.output)
|
|
print(f"Wrote {len(iq)} samples to {args.output}")
|
|
print(f"\nTo transmit with bladeRF-cli:")
|
|
print(f" bladeRF-cli -e 'set frequency tx {args.freq/1e6:.1f}M; "
|
|
f"set samplerate 125k; set bandwidth 125k; "
|
|
f"tx config file={args.output} format=bin; tx start; tx wait'")
|
|
else:
|
|
# Live transmit (requires bladeRF Python bindings)
|
|
print("Live transmit mode requires bladeRF Python bindings")
|
|
print("Install with: pip install pybladerf")
|
|
print(f"\nAlternatively, save to file with: {sys.argv[0]} -o frame.raw")
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|