
Enabled console commands via normal terminal emulator Added menu for calibration of filters
552 lines
17 KiB
C++
552 lines
17 KiB
C++
|
|
/*
|
|
* RX sweep fixes the LO and sweeps the receiver from a start value to an stop value to
|
|
* measure the RX FIR bandpass pass filter response. This eliminates any effect from the SAW
|
|
* and low pass filters before the receiver.
|
|
* It requires a fixed strength and frequency signal, and this is provided
|
|
* by setting the reference output to say 15MHz. Avoid 30Mhz due to some stray clock signal
|
|
* The input should be connected to the reference signal output with an external cable
|
|
*/
|
|
void initRX_Sweep()
|
|
{
|
|
// 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();
|
|
SetRXsweepSpan (setting.Bandwidth10 * 1000); // 10 * bandwidth
|
|
|
|
tinySA_mode = RX_SWEEP;
|
|
setting.Mode = tinySA_mode;
|
|
|
|
ResetRXsweepMenuStack(); // Put menu stack back to root level
|
|
|
|
}
|
|
|
|
|
|
void doRX_Sweep()
|
|
{
|
|
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 uint16_t currentRBW10;
|
|
static uint16_t bpFilterIndex; // used to loop through the SI4432 bandpass filters when calibrating
|
|
|
|
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 uint16_t currentPointRSSI;
|
|
static uint16_t peakRSSI;
|
|
static uint16_t centreRSSI; // RSSI at centre of sweep (ie at IF)
|
|
static uint16_t prevPointRSSI;
|
|
static uint32_t peakFreq;
|
|
static uint16_t peakIndex;
|
|
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 jsonDocInitialised = false;
|
|
static uint16_t chunkIndex;
|
|
|
|
static uint16_t bpfCalFirstSweepDone;
|
|
/*
|
|
* If paused and at the start of a sweep then do nothing
|
|
*/
|
|
if (!sweepStartDone && paused)
|
|
return;
|
|
|
|
|
|
if (( !sweepStartDone || initSweep || changedSetting ) )
|
|
{
|
|
if ( initSweep || changedSetting || (bpfCalibrate && !sweepStartDone) ) // Something has changed, or a first start, so need to work out some basic things
|
|
{
|
|
//Serial.println("Init RXSweep or changedSetting");
|
|
|
|
if (bpfCalibrate)
|
|
{
|
|
setting.LevelOffset = 0;
|
|
if (bpfCalFirstSweepDone)
|
|
{
|
|
// max value for this bpFilter index (previous sweep) is in peaks[0].Level
|
|
// value at the IF is in myActual[displayPoints/2]
|
|
// use the average of the two values
|
|
uint16_t rssi_average = ( peaks[0].Level + myActual[displayPoints/2] ) / 2;
|
|
bpfCalibrations[bpFilterIndex] = CAL_POWER - rssiTodBm( rssi_average );
|
|
Serial.printf("bpfCalibration: filter: %i, rbw10=%i , cal=%f, rssi av = %i\n",
|
|
bpFilterIndex, currentRBW10, bpfCalibrations[bpFilterIndex], rssi_average);
|
|
|
|
bpFilterIndex ++;
|
|
if (bpFilterIndex >= bpfCount) // end of calibration
|
|
{
|
|
bpfCalibrate = false;
|
|
currentRBW10 = setting.Bandwidth10;
|
|
SaveBpfCalibration();
|
|
WriteSettings();
|
|
Serial.println("bpfCalibration done");
|
|
}
|
|
else // not reached end of filters
|
|
{
|
|
currentRBW10 = rcvr.GetBandpassFilter10(bpFilterIndex);
|
|
}
|
|
}
|
|
else // first sweep not done
|
|
{
|
|
bpfCalFirstSweepDone = true;
|
|
currentRBW10 = rcvr.GetBandpassFilter10(bpFilterIndex);
|
|
}
|
|
}
|
|
else // not in calibrate
|
|
{
|
|
currentRBW10 = setting.Bandwidth10;
|
|
bpFilterIndex = 0;
|
|
bpfCalFirstSweepDone = false;
|
|
}
|
|
|
|
|
|
SetRXsweepSpan (currentRBW10 * 1000); // 10 * bandwidth - sets up stopFreq_RX and startFreq_RX
|
|
|
|
autoSweepFreqStep = ( stopFreq_RX - startFreq_RX ) / displayPoints;
|
|
|
|
vbw = autoSweepFreqStep / 1000.0; // Set the video resolution
|
|
|
|
/*
|
|
* Use the RBW setting. If zero then it is meaningless, use smallest instead
|
|
*/
|
|
bandwidth = rcvr.SetRBW ( currentRBW10, &delaytime, &bpfIndex ); // Set it in the receiver Si4432. delaytime is returned
|
|
|
|
sweepPoints = displayPoints;
|
|
|
|
sweepFreqStep = ( stopFreq_RX - startFreq_RX ) / sweepPoints; // Step for each reading
|
|
|
|
att.SetAtten ( 0 ); // Set the internal attenuator
|
|
|
|
// pre-calculate adjustment for RSSI values
|
|
dBadjust = -120.0 - setting.ExternalGain; // No Level Offset correction applied for this mode
|
|
//Serial.printf("RXSweep dBadjust = %f; ext gain = %f\n", dBadjust, setting.ExternalGain);
|
|
|
|
xmit.SetPowerReference ( setting.ReferenceOut ); // Set the GPIO reference output
|
|
|
|
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);
|
|
|
|
pushRXSweepSettings();
|
|
#endif // #ifdef USE_WIFI
|
|
|
|
|
|
} // initSweep || changedSetting
|
|
|
|
autoSweepStep = 0; // Set the step counter to zero
|
|
sweepStep = 0;
|
|
autoSweepFreq = startFreq_RX; // Set the start frequency.
|
|
|
|
nextPointFreq = autoSweepFreq + autoSweepFreqStep;
|
|
|
|
|
|
while (( micros() - setFreqMicros ) < delaytime ) // Make sure enough time has elasped since previous frequency write
|
|
{
|
|
}
|
|
|
|
//Serial.printf("set rcvr Freq get:%u, tempIF:%u\n", rcvr.GetFrequency(), tempIF);
|
|
rcvr.SetFrequency ( autoSweepFreq ); // Set the RX Si4432 to the start of the sweep
|
|
|
|
setFreqMicros = micros(); // Store the time the frequency was changed
|
|
xmit.SetFrequency ( sigFreq_RX + setting.IF_Freq ); // set the LO frequency to the IF plus reference
|
|
|
|
// Serial.printf("autoSweepFreq init: %u\n", autoSweepFreq);
|
|
|
|
|
|
#ifdef USE_WIFI
|
|
|
|
if ( numberOfWebsocketClients > 0 ) // Start off the json document for the scan
|
|
{
|
|
chunkIndex = 0;
|
|
initChunkSweepDoc (sweepStep);
|
|
Points = jsonDocument.createNestedArray ( "Points" ); // Add Points array
|
|
jsonDocInitialised = true;
|
|
}
|
|
else
|
|
jsonDocInitialised = false;
|
|
|
|
#endif // #ifdef USE_WIFI
|
|
|
|
startFreq = startFreq_RX; // Start freq for the RX
|
|
stopFreq = stopFreq_RX; // Stop freq for the RX
|
|
|
|
pointMinGain = 100; // Reset min/max values
|
|
pointMaxRSSI = 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
|
|
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
pointsPastPeak = 0; // Avoid possible peak detection at start of sweep
|
|
peakRSSI = 0;
|
|
|
|
sweepStartDone = true; // Make sure this initialize is only done once per sweep
|
|
initSweep = false;
|
|
changedSetting = false;
|
|
|
|
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 = autoSweepFreq;
|
|
|
|
/*
|
|
* Wait until time to take the next reading. If a long wait 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 ( "Freq: %s - RSSI: %03d\n",
|
|
FormatFrequency ( autoSweepFreq) , 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
|
|
|
|
// Serial.printf("autoSweepFreq: %u Step: %u\n", autoSweepFreq, sweepStep);
|
|
|
|
|
|
/*
|
|
* Change the transmitter frequency for the next reading and record the time for
|
|
* the RBW required settling delay.
|
|
*
|
|
* Not we also set the lo here to simulate the effect on the filters in a real sweep
|
|
*/
|
|
|
|
setFreqMicros = micros(); // Store the time the LO frequency was changed
|
|
xmit.SetFrequency ( sigFreq_RX + setting.IF_Freq ); // set the LO frequency to the IF plus reference
|
|
rcvr.SetFrequency ( autoSweepFreq ); // Set the RX Si4432 to the IF frequency
|
|
|
|
// Serial.printf("Step: %i Required: %i Actual %i\n", sweepStep, autoSweepFreq, rcvr.GetFrequency());
|
|
|
|
#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
|
|
// 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() );
|
|
unsigned long s = millis();
|
|
webSocket.broadcastTXT ( wsBuffer ); // Send to all connected websocket clients
|
|
if (millis() - s > 1000)
|
|
{
|
|
Serial.println("webSocketTimeout");
|
|
Serial.println(wsBuffer);
|
|
numberOfWebsocketClients = 0;
|
|
}
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
#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 maximum
|
|
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
|
|
|
|
DisplayPoint ( myActual, oldSweepStep, DB_COLOR );
|
|
|
|
if ( setting.ShowGain )
|
|
displayGainPoint ( myGain, oldSweepStep, GAIN_COLOR );
|
|
|
|
/*
|
|
* Record the peak values
|
|
*/
|
|
|
|
if ( oldSweepStep > 0)
|
|
{
|
|
if ( peakLevel < myActual[oldSweepStep] )
|
|
{
|
|
peakIndex = oldSweepStep;
|
|
peakLevel = myActual[oldSweepStep];
|
|
peakFreq = 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 = myActual[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
|
|
*/
|
|
|
|
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;
|
|
|
|
if ( sweepCount < 2 )
|
|
sweepCount++; // Used to disable wifi at start
|
|
|
|
oldPeakLevel = peakLevel; //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 )"
|
|
|
|
|
|
}
|