From c10343e853e77186ceef19f8f55968077c70a341 Mon Sep 17 00:00:00 2001 From: jgromes Date: Mon, 27 Mar 2023 18:48:32 +0200 Subject: [PATCH] [SX126x] Added spectral scan in frequency --- .../SX126x_Spectrum_Scan.ino | 2 +- .../SX126x_Spectrum_Scan_Frequency.ino | 129 ++++++++++++++++++ extras/SX126x_Spectrum_Scan/SpectrumScan.py | 54 +++++++- src/modules/SX126x/SX126x.cpp | 8 +- src/modules/SX126x/SX126x.h | 5 +- 5 files changed, 186 insertions(+), 12 deletions(-) create mode 100644 examples/SX126x/SX126x_Spectrum_Scan_Frequency/SX126x_Spectrum_Scan_Frequency.ino diff --git a/examples/SX126x/SX126x_Spectrum_Scan/SX126x_Spectrum_Scan.ino b/examples/SX126x/SX126x_Spectrum_Scan/SX126x_Spectrum_Scan.ino index 320c7e19..dbf4a9d2 100644 --- a/examples/SX126x/SX126x_Spectrum_Scan/SX126x_Spectrum_Scan.ino +++ b/examples/SX126x/SX126x_Spectrum_Scan/SX126x_Spectrum_Scan.ino @@ -80,7 +80,7 @@ void loop() { // start spectral scan // number of scans in each line is 2048 - // fewer scans leads to better temporal resolution, + // number of samples: 2048 (fewer samples = better temporal resolution) int state = radio.spectralScanStart(2048); if(state == RADIOLIB_ERR_NONE) { Serial.println(F("success!")); diff --git a/examples/SX126x/SX126x_Spectrum_Scan_Frequency/SX126x_Spectrum_Scan_Frequency.ino b/examples/SX126x/SX126x_Spectrum_Scan_Frequency/SX126x_Spectrum_Scan_Frequency.ino new file mode 100644 index 00000000..e7d6b33a --- /dev/null +++ b/examples/SX126x/SX126x_Spectrum_Scan_Frequency/SX126x_Spectrum_Scan_Frequency.ino @@ -0,0 +1,129 @@ +/* + RadioLib SX126x Spectrum Scan Example + + This example shows how to perform a spectrum power scan using SX126x. + The output is in the form of scan lines, each line has 33 power bins. + First power bin corresponds to -11 dBm, the second to -15 dBm and so on. + Higher number of samples in a bin corresponds to more power received + at that level. The example performs frequency sweep over a given range. + + To show the results in a plot, run the Python script + RadioLib/extras/SX126x_Spectrum_Scan/SpectrumScan.py + + WARNING: This functionality is experimental and requires a binary patch + to be uploaded to the SX126x device. There may be some undocumented + side effects! + + For default module settings, see the wiki page + https://github.com/jgromes/RadioLib/wiki/Default-configuration#sx126x---lora-modem + + For full API reference, see the GitHub Pages + https://jgromes.github.io/RadioLib/ +*/ + +// include the library +#include + +// this file contains binary patch for the SX1262 +#include + +// SX1262 has the following connections: +// NSS pin: 10 +// DIO1 pin: 2 +// NRST pin: 3 +// BUSY pin: 9 +SX1262 radio = new Module(10, 2, 3, 9); + +// frequency range in MHz to scan +const float freqStart = 431; +const float freqEnd = 435; + +void setup() { + Serial.begin(115200); + + // initialize SX1262 FSK modem at the initial frequency + Serial.print(F("[SX1262] Initializing ... ")); + int state = radio.beginFSK(freqStart); + if(state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } + + // upload a patch to the SX1262 to enable spectral scan + // NOTE: this patch is uploaded into volatile memory, + // and must be re-uploaded on every power up + Serial.print(F("[SX1262] Uploading patch ... ")); + state = radio.uploadPatch(sx126x_patch_scan, sizeof(sx126x_patch_scan)); + if(state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } + + // configure scan bandwidth to 234.4 kHz + // and disable the data shaping + Serial.print(F("[SX1262] Setting scan parameters ... ")); + state = radio.setRxBandwidth(234.3); + state |= radio.setDataShaping(RADIOLIB_SHAPING_NONE); + if(state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } +} + +void loop() { + // perform scan over the entire frequency range + float freq = freqStart; + while(freq <= freqEnd) { + Serial.print("FREQ "); + Serial.println(freq, 2); + + // start spectral scan + // number of samples: 2048 (fewer samples = better temporal resolution) + Serial.print(F("[SX1262] Starting spectral scan ... ")); + int state = radio.spectralScanStart(2048); + if(state == RADIOLIB_ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while(true); + } + + // wait for spectral scan to finish + while(radio.spectralScanGetStatus() != RADIOLIB_ERR_NONE) { + delay(10); + } + + // read the results + uint16_t results[RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE]; + state = radio.spectralScanGetResult(results); + if(state == RADIOLIB_ERR_NONE) { + // we have some results, print it + Serial.print("SCAN "); + for(uint8_t i = 0; i < RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE; i++) { + Serial.print(results[i]); + Serial.print(','); + } + Serial.println(" END"); + } + + // wait a little bit before the next scan + delay(5); + + // set the next frequency + // the frequency step should be slightly smaller + // or the same as the Rx bandwidth set in setup + freq += 0.2; + radio.setFrequency(freq); + } + +} diff --git a/extras/SX126x_Spectrum_Scan/SpectrumScan.py b/extras/SX126x_Spectrum_Scan/SpectrumScan.py index 59eb9d63..435a27a8 100644 --- a/extras/SX126x_Spectrum_Scan/SpectrumScan.py +++ b/extras/SX126x_Spectrum_Scan/SpectrumScan.py @@ -16,6 +16,7 @@ SCAN_WIDTH = 33 # scanline Serial start/end markers SCAN_MARK_START = 'SCAN ' +SCAN_MARK_FREQ = 'FREQ ' SCAN_MARK_END = ' END' # output path @@ -82,22 +83,48 @@ def main(): default=DEFAULT_RSSI_OFFSET, type=int, help=f'Default RSSI offset in dBm (defaults to {DEFAULT_RSSI_OFFSET})') + parser.add_argument('--freq', + default=-1, + type=float, + help=f'Default starting frequency in MHz') args = parser.parse_args() + freq_mode = False + scan_len = args.len + if (args.freq != -1): + freq_mode = True + scan_len = 1000 + # create the color map and the result array - arr = np.zeros((SCAN_WIDTH, args.len)) + arr = np.zeros((SCAN_WIDTH, scan_len)) # scanline counter row = 0 + # list of frequencies in frequency mode + freq_list = [] + # open the COM port with serial.Serial(args.port, args.speed, timeout=None) as com: while(True): # update the progress bar - printProgressBar(row, args.len) + if not freq_mode: + printProgressBar(row, scan_len) # read a single line - line = com.readline().decode('utf-8') + try: + line = com.readline().decode('utf-8') + except: + continue + + if SCAN_MARK_FREQ in line: + new_freq = float(line.split(' ')[1]) + if (len(freq_list) > 1) and (new_freq < freq_list[-1]): + break + + freq_list.append(new_freq) + print('{:.3f}'.format(new_freq), end = '\r') + continue # check the markers if (SCAN_MARK_START in line) and (SCAN_MARK_END in line): @@ -110,21 +137,34 @@ def main(): row = row + 1 # check if we're done - if row >= args.len: + if (not freq_mode) and (row >= scan_len): break + # scale to the number of scans (sum of any given scanline) + num_samples = arr.sum(axis=0)[0] + arr *= (num_samples/arr.max()) + + if freq_mode: + scan_len = len(freq_list) + # create the figure fig, ax = plt.subplots() # display the result as heatmap - extent = [0, args.len, -4*(SCAN_WIDTH + 1), args.offset] - im = ax.imshow(arr, cmap=args.map, extent=extent) + extent = [0, scan_len, -4*(SCAN_WIDTH + 1), args.offset] + if freq_mode: + extent[0] = freq_list[0] + extent[1] = freq_list[-1] + im = ax.imshow(arr[:,:scan_len], cmap=args.map, extent=extent) fig.colorbar(im) # set some properites and show timestamp = datetime.now().strftime('%y-%m-%d %H-%M-%S') title = f'RadioLib SX126x Spectral Scan {timestamp}' - plt.xlabel("Time [sample]") + if freq_mode: + plt.xlabel("Frequency [Hz]") + else: + plt.xlabel("Time [sample]") plt.ylabel("RSSI [dBm]") ax.set_aspect('auto') fig.suptitle(title) diff --git a/src/modules/SX126x/SX126x.cpp b/src/modules/SX126x/SX126x.cpp index ceb9bc6b..8e3c2343 100644 --- a/src/modules/SX126x/SX126x.cpp +++ b/src/modules/SX126x/SX126x.cpp @@ -1464,7 +1464,7 @@ int16_t SX126x::uploadPatch(const uint32_t* patch, size_t len, bool nonvolatile) return(state); } -int16_t SX126x::spectralScanStart(uint16_t numScans, uint8_t window, uint8_t interval) { +int16_t SX126x::spectralScanStart(uint16_t numSamples, uint8_t window, uint8_t interval) { // abort first - not sure if this is strictly needed, but the example code does this spectralScanAbort(); @@ -1476,7 +1476,7 @@ int16_t SX126x::spectralScanStart(uint16_t numScans, uint8_t window, uint8_t int RADIOLIB_ASSERT(state); // now set the actual spectral scan parameters - uint8_t data[3] = { (uint8_t)((numScans >> 8) & 0xFF), (uint8_t)(numScans & 0xFF), interval }; + uint8_t data[3] = { (uint8_t)((numSamples >> 8) & 0xFF), (uint8_t)(numSamples & 0xFF), interval }; return(_mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_SPECTR_SCAN_PARAMS, data, 3)); } @@ -1567,6 +1567,10 @@ int16_t SX126x::setDio2AsRfSwitch(bool enable) { return(_mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_DIO2_AS_RF_SWITCH_CTRL, &data, 1)); } +int16_t SX126x::setFs() { + return(_mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_FS, NULL, 0)); +} + int16_t SX126x::setTx(uint32_t timeout) { uint8_t data[] = { (uint8_t)((timeout >> 16) & 0xFF), (uint8_t)((timeout >> 8) & 0xFF), (uint8_t)(timeout & 0xFF)} ; return(_mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_TX, data, 3)); diff --git a/src/modules/SX126x/SX126x.h b/src/modules/SX126x/SX126x.h index f79bc854..4c04483b 100644 --- a/src/modules/SX126x/SX126x.h +++ b/src/modules/SX126x/SX126x.h @@ -1086,7 +1086,7 @@ class SX126x: public PhysicalLayer { /*! \brief Start spectral scan. Requires binary path to be uploaded. - \param numScans Number of scans for each iteration. Fewer scans = better temporal resolution, but fewer power samples. + \param numSamples Number of samples for each scan. Fewer samples = better temporal resolution. \param window RSSI averaging window size. @@ -1094,7 +1094,7 @@ class SX126x: public PhysicalLayer { \returns \ref status_codes */ - int16_t spectralScanStart(uint16_t numScans, uint8_t window = RADIOLIB_SX126x_SPECTRAL_SCAN_WINDOW_DEFAULT, uint8_t interval = RADIOLIB_SX126X_SCAN_INTERVAL_8_20_US); + int16_t spectralScanStart(uint16_t numSamples, uint8_t window = RADIOLIB_SX126x_SPECTRAL_SCAN_WINDOW_DEFAULT, uint8_t interval = RADIOLIB_SX126X_SCAN_INTERVAL_8_20_US); /*! \brief Abort an ongoing spectral scan. @@ -1121,6 +1121,7 @@ class SX126x: public PhysicalLayer { protected: #endif // SX126x SPI command implementations + int16_t setFs(); int16_t setTx(uint32_t timeout = 0); int16_t setRx(uint32_t timeout); int16_t setCad(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin);