/* * Initialise variables and SI4432 for the low frequency sweep */ void initSweepLow() { // set up checkerboard sizes gridHeight = GRID_HEIGHT; gridWidth = DISPLAY_POINTS; yGrid = Y_GRID; // no of grid divisions yDelta = gridHeight / yGrid; // no of points/division xGrid = X_GRID; xOrigin = X_ORIGIN; yOrigin = Y_ORIGIN; displayPoints = DISPLAY_POINTS; xDelta = displayPoints / xGrid; init_sweep(); tinySA_mode = SA_LOW_RANGE; setting.Mode = tinySA_mode; ResetSAMenuStack(); // Put menu stack back to root level } /* * This function section handles the low freq range sweep */ void doSweepLow() { static uint32_t autoSweepStep = 0; static uint32_t autoSweepFreq = 0; static uint32_t autoSweepFreqStep = 0; static uint32_t nextPointFreq = 0; // Frequency for the next display point. Used for substeps static unsigned long setFreqMicros; static unsigned long nowMicros; static uint32_t sweepStep; // Step count static uint32_t sweepFreqStep; static int16_t pointMinGain; // to record minimum gain for the current display point static int16_t pointMaxRSSI; // to record max RSSI of the samples in the current display point static uint32_t pointMaxFreq; // record frequency where maximum occurred static int16_t lastMode; // Record last operating mode (sig gen, normal) static uint32_t lastIF; static bool spurToggle; static uint32_t actualFreq; // actual frequency static uint32_t actualIF; // actual IF (Rx frequency) static uint16_t currentPointRSSI; static uint16_t peakRSSI; static uint16_t prevPointRSSI; static uint32_t peakFreq; static uint16_t peakIndex; static uint16_t maxRSSI; static uint32_t maxFreq; static uint16_t maxIndex; static uint16_t pointsPastPeak; static uint16_t pointsPastDip; static uint16_t minRSSI; // Minimum level for the sweep static uint16_t lastMinRSSI; // Minimum level for the previous sweep static bool resetAverage; // Flag to indicate a setting has changed and average valuesneeds to be reset static bool jsonDocInitialised = false; static uint16_t chunkIndex; static uint32_t offsetIF; // IF frequency offset by half the bandwidth to position in the centre of the filter static uint32_t tgIF; // Track gen IF - SA IF plus any offset if both SI4432 defined /* * If paused and at the start of a sweep then do nothing */ if (!sweepStartDone && paused) return; /* * If the "sweepStartDone" flag is false or if the "initSweep" flag is true, we need * to set things up for the sweep. */ if (( !sweepStartDone || initSweep || changedSetting ) ) { if ( initSweep || changedSetting ) // Something has changed, or a first start, so need to owrk out some basic things { //Serial.println("InitSweep or changedSetting"); autoSweepFreqStep = ( setting.ScanStop - setting.ScanStart ) / displayPoints; vbw = autoSweepFreqStep / 1000.0; // Set the video resolution requiredRBW10 = setting.Bandwidth10; // and the resolution bandwidth if ( requiredRBW10 == 0 ) // If the bandwidth is on "Auto" work out the required RBW requiredRBW10 = (( setting.ScanStop - setting.ScanStart )) / 29000; // 290 points on display, kHz if ( requiredRBW10 < 26 ) // If it's less than 2.6KHz requiredRBW10 = 26; // set it to 2.6KHz if ( requiredRBW10 > 6207 ) requiredRBW10 = 6207; if ( requiredRBW10 != old_requiredRBW10 ) { bandwidth = rcvr.SetRBW ( requiredRBW10, &delaytime, &bpfIndex ); // Set it in the receiver Si4432 old_requiredRBW10 = requiredRBW10; } /* * The FIR filters in the SI4432 are centred above the nominal IF frequency as that is where the signals are meant to be. * To make sure we are not on the edge of the filters offset the IF frequency down by half the bandwidth * This needs to be optimised ********* */ // offsetIF = setting.IF_Freq + setting.Bandwidth10 * 50; // bW10 is in kHz * 10, so * 100-> kHz, halved offsetIF = setting.IF_Freq + RX_PASSBAND_OFFSET; // half of narrowest RBW /* * Need multiple readings for each pixel in the display to avoid missing signals. * Work out how many points needed for the whole sweep: */ sweepPoints = (uint32_t)(( setting.ScanStop - setting.ScanStart ) / bandwidth / 1000.0 * OVERLAP + 0.5); // allow for some overlap (filters will have 3dB roll off at edge) and round up if ( sweepPoints < displayPoints ) sweepPoints = displayPoints; // At least the right number of points for the display sweepFreqStep = ( setting.ScanStop - setting.ScanStart ) / sweepPoints; // Step for each reading if ( setting.Attenuate != old_settingAttenuate ) { if ( !att.SetAtten ( setting.Attenuate )) // Set the internal attenuator setting.Attenuate = att.GetAtten (); // Read back if limited (setting.Attenuate was outside range) old_settingAttenuate = setting.Attenuate; } // pre-calculate adjustment for RSSI values dBadjust = (double)setting.Attenuate - 120.0 + setting.LevelOffset - setting.ExternalGain + bpfCalibrations[bpfIndex]; Serial.printf("SweepLo dBadjust = %f; leveloffset = %f; attenuate = %i, ext gain = %f, bpfCal = %f\n", dBadjust, setting.LevelOffset, setting.Attenuate, setting.ExternalGain, bpfCalibrations[bpfIndex]); resetAverage = changedSetting; xmit.SetPowerReference ( setting.ReferenceOut ); // Set the GPIO reference output if wanted maxGrid = setting.MaxGrid; minGrid = setting.MinGrid; #ifdef USE_WIFI // Vary number of points to send in each chunk depending on delaytime // A chunk is sent at the end of each sweep regardless wiFiPoints = wiFiTargetTime / delaytime; if (wiFiPoints > MAX_WIFI_POINTS) wiFiPoints = MAX_WIFI_POINTS; if (wiFiPoints > displayPoints*OVERLAP) wiFiPoints = displayPoints*OVERLAP; //Serial.printf("No of wifiPoints set to %i\n", wiFiPoints); if ( numberOfWebsocketClients > 0 ) pushSettings (); #endif // #ifdef USE_WIFI #if defined( TG_IF_INSTALLED ) && !defined( TG_LO_INSTALLED ) if (tgIF_OK && (trackGenSetting.Mode == 1) ) { tg_if.TxMode ( trackGenSetting.IF_Drive ); // Set tracking generator IF on Serial.println("tgif turned on"); delayMicroseconds(300); } else tg_if.RxMode(); #endif #if defined(TG_IF_INSTALLED) && defined(TG_LO_INSTALLED) if (tgLO_OK && tgIF_OK) { switch ( trackGenSetting.Mode ) { case 0: // off tg_if.RxMode(); tg_lo.RxMode(); break; case 1: // tracking tg_if.TxMode ( trackGenSetting.IF_Drive ); // Set tracking generator IF on tg_lo.TxMode ( trackGenSetting.LO_Drive ); // Set tracking generator LO on break; case 2: // generator mode tg_lo.TxMode ( trackGenSetting.LO_Drive ); // Set tracking generator LO on tg_if.TxMode ( trackGenSetting.IF_Drive ); // Set tracking generator IF on delayMicroseconds(300); tg_lo.SetFrequency ( setting.IF_Freq + trackGenSetting.Offset + trackGenSetting.Frequency ); break; default: Serial.println("Invalid track gen mode in Sweeplo"); } } #endif } // initSweep || changedSetting autoSweepStep = 0; // Set the step counter to zero sweepStep = 0; autoSweepFreq = setting.ScanStart; // Set the start frequency. nextPointFreq = autoSweepFreq + autoSweepFreqStep; /* Spur reduction offsets the IF from its normal value. LO also has to be offset same amount * Offset should be more than half the RX bandwidth to ensure spur is still not in the RX filter passband * but not so big that the frequencies fall outside the SAW filter passband. * Use the average trace set to minimum to see the result. Spurs if any will be visible * at different frequencies. * Real signals will be present at the same frequency, so a min trace will show only real signals * How well this works depends on how flat the SAW filter (and SI4432 filter) response is */ if (setting.Spur && spurToggle) { uint32_t IF_Shift = requiredRBW10 * 100; // bandwidth in Hz if (IF_Shift > MAX_IF_SHIFT) IF_Shift = MAX_IF_SHIFT; tempIF = offsetIF - IF_Shift; } else { tempIF = offsetIF; } // track gen IF follows LO if only one SI4432, otherwise its offset by an amount to reduce blow by if (tgLO_OK) tgIF = tempIF + trackGenSetting.Offset; else tgIF = tempIF; spurToggle = !spurToggle; //Serial.printf("tempIF %u, spurOffset=%i, spur:%i, Toggle:%i\n", tempIF, tempIF - setting.IF_Freq, setting.Spur, spurToggle); while (( micros() - setFreqMicros ) < delaytime ) // Make sure enough time has elasped since previous frequency write { } if ( ( lastIF != tempIF ) || initSweep || changedSetting ) { //Serial.printf("set rcvr Freq get:%u, tempIF:%u\n", rcvr.GetFrequency(), tempIF); rcvr.SetFrequency ( tempIF ); // Set the RX Si4432 to the IF frequency lastIF = tempIF; actualIF = rcvr.GetFrequency(); #ifdef TG_IF_INSTALLED if (tgIF_OK && (trackGenSetting.Mode == 1) ) { tg_if.SetFrequency ( tgIF ); // Set tracking generator IF for the sweep //Serial.printf("tgif set to %i Hz\n", tgIF); } #endif } xmit.SetFrequency ( tempIF + autoSweepFreq ); // set the LO frequency, tempIF is offset if spur reduction on #ifdef TG_LO_INSTALLED if (tgLO_OK && (trackGenSetting.Mode == 1) ) { tg_lo.SetFrequency ( tgIF + autoSweepFreq ); // Set tracking generator LO //Serial.printf("tglo set to %i Hz at start of sweep\n", tgIF + autoSweepFreq); } #endif setFreqMicros = micros(); // Store the time the frequency was changed /* * Actual frequency in the SI4432 is rounded and is limited by the possible resolution */ actualFreq = xmit.GetFrequency() - actualIF + RX_PASSBAND_OFFSET; // Used for next RSSI command and JSON entry #ifdef USE_WIFI if ( numberOfWebsocketClients > 0 ) // Start off the json document for the scan { jsonDocument.clear (); chunkIndex = 0; initChunkSweepDoc (sweepStep); Points = jsonDocument.createNestedArray ( "Points" ); // Add Points array jsonDocInitialised = true; } else jsonDocInitialised = false; #endif // #ifdef USE_WIFI startFreq = setting.ScanStart + tempIF; // Start freq for the LO stopFreq = setting.ScanStop + tempIF; // Stop freq for the LO pointMinGain = 100; // Reset min/max values pointMaxRSSI = 0; /* * Copy the values for the peaks (marker positions) to the old versions. No need to * reset the indicies or frequencies; just the "Level". */ for ( int i = 0; i < MARKER_COUNT; i++ ) { oldPeaks[i].Level = peaks[i].Level; oldPeaks[i].Index = peaks[i].Index; oldPeaks[i].Freq = peaks[i].Freq; peaks[i].Level = 0; } DisplayInfo (); // Display axis, top and side bar text peakLevel = 0; // Reset the peak values for the sweep peakFreq = 0.0; peakGain = 100; // Set to higher than gain can ever be lastMinRSSI = minRSSI; minRSSI = 300; // Higher than it can be pointsPastPeak = 0; // Avoid possible peak detection at start of sweep peakRSSI = 0; maxRSSI = 0; sweepStartDone = true; // Make sure this initialize is only done once per sweep initSweep = false; changedSetting = false; if ( setActualPowerRequested ) { SetPowerLevel ( actualPower ); setActualPowerRequested = false; // Serial.printf ( "Setting actual Power %f \n", actualPower ); } lastSweepStartMicros = sweepStartMicros; // Set last time we got here sweepStartMicros = micros(); // Current time sweepMicros = sweepStartMicros - lastSweepStartMicros; // Calculate sweep time (no rollover handling) } // End of "if ( !sweepStartDone ) || initSweep || changedSetting )" /* * Here we do the actual sweep. Save the current step and frequencies for the next time * through, then wait the required amount of time based on the RBW before taking the * signal strength reading and changing the transmitter (LO) frequency. */ uint16_t oldSweepStep = autoSweepStep; uint32_t oldSweepFreq = actualFreq; /* * Wait until time to take the next reading. If a long enough wait left * then check the touchscreen and Websockets while we are waiting * to improve response */ nowMicros = micros(); while (( nowMicros - setFreqMicros ) < delaytime ) { if ( ( nowMicros - setFreqMicros + delaytime > MIN_DELAY_WEBSOCKETS ) && ( (nowMicros - lastWebsocketMicros > websocketInterval) || (numberOfWebsocketClients > 0) ) ) { // Serial.print("W"); webSocket.loop (); // Check websockets - includes Yield() to allow other events to run // Serial.println("w"); lastWebsocketMicros = nowMicros; } if ( nowMicros - setFreqMicros > 100 ) // Wait some time to allow DMA sprite write to finish! UiProcessTouch (); // Check the touch screen // Serial.println("w"); nowMicros = micros(); } int rxRSSI = rcvr.GetRSSI (); // Read the RSSI from the RX SI4432 /* * Note that there are two different versions of the print statement to send the * RSSI readings to the serial output. You can change which one is commented out. * * The first one produces a tab separated list of just the frequency and RSSI * reading. That format can be easily read inte something like Excel. * * The second one produces a listing more fit for human consumption! */ if ( showRSSI ) // Displaying RSSI? { // Serial.printf ( "%s\t%03d\n", // FormatFrequency ( autoSweepFreq) , rxRSSI ); // Send it to the serial output Serial.printf ( "Actual IF: %s", FormatFrequency ( rcvr.GetFrequency() ) ); Serial.printf ( " LO Freq: %s", FormatFrequency ( xmit.GetFrequency() ) ); Serial.printf ( " Sweep Freq: %s", FormatFrequency ( autoSweepFreq) ); Serial.printf ( " Actual Freq %s - RSSI: %03d\n", FormatFrequency ( actualFreq ), rxRSSI ); // Send it to the serial output } if ( (numberOfWebsocketClients > 0) || (setting.ShowGain) ) gainReading = GetPreampGain ( &AGC_On, &AGC_Reg ); // Record the preamp/lna gains autoSweepFreq += sweepFreqStep; // Increment the frequency sweepStep++; // and increment the step count /* * Change the transmitter frequency for the next reading and record the time for * the RBW required settling delay. */ uint32_t f = tempIF + autoSweepFreq; xmit.SetFrequency ( f ); // Set the new LO frequency as soon as RSSI read // Serial.printf("LO Required: %i Actual %i\n", tempIF+autoSweepFreq, xmit.GetFrequency()); #ifdef TG_LO_INSTALLED if (tgLO_OK && (trackGenSetting.Mode == 1) ) { tg_lo.SetFrequency ( f + trackGenSetting.Offset ); // Set tracking generator LO } #endif setFreqMicros = micros(); // Store the time the LO frequency was changed #ifdef TG_LO_INSTALLED // if (trackGenSetting.Mode == 1) // Serial.printf("tglo %i f=%i, lo=%02X, if=%02X\n", tg_lo.ReadFrequency()- tg_if.ReadFrequency(), autoSweepFreq, tg_lo.ReadByte(REG_OFC1) & 0x0F, tg_if.ReadByte(REG_OFC1) & 0x0F ); #endif #ifdef USE_WIFI if ( numberOfWebsocketClients > 0 ) { if ( jsonDocInitialised ) { JsonObject dataPoint = Points.createNestedObject (); // Add an object to the Json array to be pushed to the client dataPoint["x"] = oldSweepFreq/1000000.0; // Set the x(frequency) value dataPoint["y"] = rxRSSI; // Set the y (RSSI) value dataPoint["g"] = gainReading; // Set the y (gain) value // Serial.printf ( "Add point chunkIndex %u, sweepStep %u of %u \n", chunkIndex, sweepStep, sweepPoints); chunkIndex++; // increment no of data points in current WiFi chunk if ( chunkIndex >= wiFiPoints ) // Send the chunk of data and start new jSon document { String wsBuffer; if ( wsBuffer ) { // Serial.print("D"); serializeJson ( jsonDocument, wsBuffer ); // Serial.printf("J%u", wsBuffer.length() ); // Serial.println(wsBuffer); unsigned long s = millis(); webSocket.broadcastTXT ( wsBuffer ); // Send to all connected websocket clients if (millis() - s > 1000) { Serial.println("webSocketTimeout"); Serial.println(wsBuffer); websocketFailCount++; if (websocketFailCount > 2) numberOfWebsocketClients = 0; } else { websocketFailCount = 0; // reset if OK } // Serial.print("j"); } else Serial.println("No buffer :("); } } if ( ( chunkIndex >= wiFiPoints ) || !jsonDocInitialised ) // Start new jSon document { chunkIndex = 0; initChunkSweepDoc (sweepStep); Points = jsonDocument.createNestedArray ( "Points" ); // Add Points array jsonDocInitialised = true; } } /* * Actual frequency in the SI4432 is rounded and is limited by the possible resolution */ actualFreq = xmit.GetFrequency() - actualIF + RX_PASSBAND_OFFSET; // Used for next RSSI command and JSON entry #endif // #ifdef USE_WIFI if ( rxRSSI > pointMaxRSSI ) // RSSI > maximum value for this point so far? { myActual[autoSweepStep] = rxRSSI; // Yes, save it pointMaxRSSI = rxRSSI; // Added by G3ZQC - Remember new maximim pointMaxFreq = oldSweepFreq; } if ( gainReading < pointMinGain ) // Gain < minimum gain for this point so far? { myGain[autoSweepStep] = gainReading; // Yes, save it pointMinGain = gainReading; // Added by G3ZQC - Remember new minimum } if (rxRSSI < minRSSI) // Detect minimum for sweep minRSSI = rxRSSI; /* * Have we enough readings for this display point? If yes, so do any averaging etc, reset * the values so peak in the frequency step is recorded and update the display. */ if ( autoSweepFreq >= nextPointFreq ) { nextPointFreq = nextPointFreq + autoSweepFreqStep; // Next display point frequency autoSweepStep++; // Increment the index pointMinGain = 100; // Reset min/max values pointMaxRSSI = 0; DrawCheckerBoard ( oldSweepStep ); // Draw the grid for the point in the sweep we have just read if ( resetAverage || setting.Average == AV_OFF ) // Store data, either as read or as rolling average myData[oldSweepStep] = myActual[oldSweepStep]; else { switch ( setting.Average ) { case AV_MIN: if ( myData[oldSweepStep] > myActual[oldSweepStep] ) myData[oldSweepStep] = myActual[oldSweepStep]; break; case AV_MAX: if ( myData[oldSweepStep] < myActual[oldSweepStep] ) myData[oldSweepStep] = myActual[oldSweepStep]; break; case AV_2: myData[oldSweepStep] = ( myData[oldSweepStep] + myActual[oldSweepStep] ) / 2; break; case AV_4: myData[oldSweepStep] = ( myData[oldSweepStep]*3 + myActual[oldSweepStep] ) / 4; break; case AV_8: myData[oldSweepStep] = ( myData[oldSweepStep]*7 + myActual[oldSweepStep] ) / 8; break; } DisplayPoint ( myData, oldSweepStep, AVG_COLOR ); } if ( setting.ShowSweep ) DisplayPoint ( myActual, oldSweepStep, DB_COLOR ); if ( setting.ShowGain ) displayGainPoint ( myGain, oldSweepStep, GAIN_COLOR ); if ( setting.ShowStorage ) DisplayPoint ( myStorage, oldSweepStep, STORAGE_COLOR ); if ( setting.SubtractStorage ) rxRSSI = 128 + rxRSSI - myStorage[oldSweepStep]; /* * Record the peak values but not if freq low enough to detect the LO */ if (( autoSweepFreq > MARKER_MIN_FREQUENCY ) && (oldSweepStep > 0)) { if ( maxRSSI <= myActual[oldSweepStep] ) { maxIndex = oldSweepStep; maxRSSI = myActual[oldSweepStep]; maxFreq = oldSweepFreq; // Serial.printf( "peakLevel set %i, index %i\n", peakLevel, oldSweepStep); // displayPeakData (); } /* * Save values used by peak detection. Need to save the previous value as we only * know we have a peak once past it! */ prevPointRSSI = currentPointRSSI; currentPointRSSI = myData[oldSweepStep]; /* * Peak point detection. Four peaks, used to position the markers */ if ( currentPointRSSI >= prevPointRSSI ) // Level or ascending { pointsPastDip ++; if ( pointsPastDip == PAST_PEAK_LIMIT ) { pointsPastPeak = 0; } if ( currentPointRSSI > peakRSSI ) { peakRSSI = currentPointRSSI; // Store values peakFreq = oldSweepFreq; peakIndex = oldSweepStep; } } else { pointsPastPeak ++; // only a true peak if value decreased for a number of consecutive points if ( pointsPastPeak == PAST_PEAK_LIMIT ) // We have a peak { pointsPastDip = 0; /* * Is this peak bigger than previous ones? Only check if bigger than smallest peak so far */ if ( peakRSSI > peaks[MARKER_COUNT-1].Level ) { for ( uint16_t p = 0; p < MARKER_COUNT; p++ ) { if ( peakRSSI > peaks[p].Level ) { for ( uint16_t n = 3; n > p; n-- ) // Shuffle lower level peaks down memcpy ( &peaks[n], &peaks[n-1], sizeof ( peak_t )); peaks[p].Level = peakRSSI; // Save the peak values peaks[p].Freq = peakFreq; peaks[p].Index = peakIndex; break; } } } peakRSSI = 0; // Reset peak values ready for next peak } // We have a peak } // Descending } // if (( autoSweepFreq > 1000000 ) && (oldSweepStep > 0)) /* * Draw the markers if main sweep is displayed. The markers know if they are enabled or not * Only paint if sweep step is in range where there will be a marker */ if ( setting.ShowSweep || setting.Average != AV_OFF ) { for ( int p = 0; p < MARKER_COUNT; p++ ) if (( abs ( oldSweepStep - oldPeaks[p].Index ) <= MARKER_SPRITE_HEIGHT / 2 ) && ( oldPeaks[p].Level > (lastMinRSSI + MARKER_NOISE_LIMIT) )) marker[p].Paint ( &img, oldPeaks[p].Index - oldSweepStep, rssiToImgY ( oldPeaks[p].Level ) ); } // If in the last few points and gain trace is displayed show the gain scale if ( setting.ShowGain && (oldSweepStep > displayPoints - 2 * CHAR_WIDTH) ) { int16_t scaleX = displayPoints - 2 * CHAR_WIDTH - oldSweepStep + 1; // relative to the img sprite img.setPivot( scaleX, 0); gainScaleSprite.pushRotated ( &img, 0, TFT_BLACK ); // Send the sprite to the target sprite, with transparent colour } if ( oldSweepStep > 0 ) // Only push if not first point (two pixel wide img) img.pushSprite ( xOrigin+oldSweepStep-1, yOrigin ); // myFreq[oldSweepStep] = oldSweepFreq; // Store the frequency for XML file creation } // End of "if ( autoSweepFreq >= nextPointFreq )" if ( sweepStep >= sweepPoints ) // If we have got to the end of the sweep { // autoSweepStep = 0; sweepStartDone = false; resetAverage = false; if ( sweepCount < 2 ) sweepCount++; // Used to disable wifi at start // Serial.printf("MaxRSSI = %i, freq = %i\n", maxRSSI, maxFreq); oldPeakLevel = maxRSSI; //Save value of peak level for use by the "SetPowerLevel" function if ( myActual[displayPoints-1] == 0 ) // Ensure a value in last data point { myActual[displayPoints-1] = rxRSSI; // Yes, save it myGain[displayPoints-1] = gainReading; // myFreq[displayPoints-1] = oldSweepFreq; } if ( showRSSI == 1 ) // Only show it once? showRSSI = 0; // Then turn it off #ifdef USE_WIFI if (( numberOfWebsocketClients > 0) && jsonDocInitialised && (chunkIndex > 0) ) { String wsBuffer; if (wsBuffer) { serializeJson ( jsonDocument, wsBuffer ); webSocket.broadcastTXT ( wsBuffer ); // Send to all connected websocket clients } else Serial.println ( "No buffer :("); } #endif // #ifdef USE_WIFI } // End of "if ( sweepStep >= sweepPoints )" } // End of "doSweepLow"