Add files via upload
This commit is contained in:
commit
b04a218586
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
586
Bandscope.ino
Normal file
586
Bandscope.ino
Normal file
@ -0,0 +1,586 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Initialise variables and SI4432 for the low frequency sweep
|
||||||
|
*/
|
||||||
|
void initBandscope()
|
||||||
|
{
|
||||||
|
ClearDisplay ();
|
||||||
|
/*
|
||||||
|
* Set up the "img" Sprite. This is the image for the graph. It makes for faster display
|
||||||
|
* updates and less flicker.
|
||||||
|
*
|
||||||
|
* 16 bit colour depth is faster than 8 and much faster than 4 bit! BUT - sprites
|
||||||
|
* pushed to it do not have correct colour - 8 bit and it is fine.
|
||||||
|
*
|
||||||
|
* All marker sprites are WHITE for now.
|
||||||
|
*/
|
||||||
|
tft.unloadFont();
|
||||||
|
|
||||||
|
img.unloadFont();
|
||||||
|
img.deleteSprite();
|
||||||
|
|
||||||
|
img.setTextSize ( 1 );
|
||||||
|
img.setColorDepth ( 16 );
|
||||||
|
img.setAttribute ( PSRAM_ENABLE, false ); // Don't use the PSRAM on the WROVERs
|
||||||
|
img.createSprite ( 2, GRID_HEIGHT + 1 ); // Only 2 columns wide
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The "tSprite" is used for displaying the data above the scan grid.
|
||||||
|
*/
|
||||||
|
|
||||||
|
tSprite.deleteSprite();
|
||||||
|
tSprite.setRotation ( 0 );
|
||||||
|
tSprite.setTextSize ( 1 );
|
||||||
|
tSprite.setColorDepth ( 16 );
|
||||||
|
tSprite.setAttribute ( PSRAM_ENABLE, false ); // Don't use the PSRAM on the WROVERs
|
||||||
|
tSprite.createSprite ( tft.width() - X_ORIGIN, Y_ORIGIN );
|
||||||
|
|
||||||
|
sSprite.deleteSprite();
|
||||||
|
/*
|
||||||
|
* Create and draw the sprite for the gain scale
|
||||||
|
*/
|
||||||
|
CreateGainScale ();
|
||||||
|
|
||||||
|
// Make sure everything will be reset
|
||||||
|
old_settingAttenuate = -1000;
|
||||||
|
old_settingPowerGrid = -1000;
|
||||||
|
old_settingMax = -1;
|
||||||
|
old_settingMin = -1;
|
||||||
|
old_startFreq = -1;
|
||||||
|
old_stopFreq = -1;
|
||||||
|
old_ownrbw = -1;
|
||||||
|
old_vbw = -1;
|
||||||
|
old_settingAverage = -1;
|
||||||
|
old_settingSpur = -100;
|
||||||
|
old_bandwidth = 0;
|
||||||
|
|
||||||
|
SetRX ( 0 ); // LO to transmit, RX to receive
|
||||||
|
|
||||||
|
xmit.SetDrive ( setting.Drive ); // Set transmitter power level
|
||||||
|
rcvr.SetPreampGain ( setting.PreampGain );
|
||||||
|
|
||||||
|
sweepStartDone = false; // Make sure this initialize is only done once per sweep
|
||||||
|
initSweep = true;
|
||||||
|
|
||||||
|
tinySA_mode = BANDSCOPE;
|
||||||
|
setting.Mode = tinySA_mode;
|
||||||
|
Serial.println("before reset bandscope stack");
|
||||||
|
ResetBandscopeMenuStack(); // Put menu stack back to root level
|
||||||
|
Serial.println("End of initBandscope");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function section handles the fast bandscope sweep
|
||||||
|
* The display is split and shows a waterfall
|
||||||
|
* Number of points is reduced, and frequency change is done using an offset to aallow the delay time between
|
||||||
|
* changing frequency and taking a reading to be reduced
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void doBandscope()
|
||||||
|
{
|
||||||
|
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 uint16_t currentPointRSSI;
|
||||||
|
static uint16_t peakRSSI;
|
||||||
|
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 resetAverage; // Flag to indicate a setting has changed and average valuesneeds to be reset
|
||||||
|
|
||||||
|
static bool jsonDocInitialised = false;
|
||||||
|
static uint16_t chunkIndex;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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("InitBandscope or changedSetting");
|
||||||
|
sweepPoints = setting.BandscopePoints;
|
||||||
|
autoSweepFreqStep = ( setting.BandscopeSpan ) / sweepPoints;
|
||||||
|
|
||||||
|
vbw = autoSweepFreqStep / 1000.0; // Set the video resolution
|
||||||
|
ownrbw = 2.6; // and fix the resolution bandwidth to 2.6kHz
|
||||||
|
|
||||||
|
bandwidth = rcvr.SetRBW ( ownrbw * 10.0, &delaytime ); // Set it in the receiver Si4432
|
||||||
|
|
||||||
|
//Serial.printf("set rcvr Freq get:%u, tempIF:%u\n", rcvr.GetFrequency(), tempIF);
|
||||||
|
rcvr.SetFrequency ( setting.IF_Freq ); // Set the RX Si4432 to the IF frequency
|
||||||
|
|
||||||
|
|
||||||
|
sweepFreqStep = autoSweepFreqStep; // 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetAverage = changedSetting;
|
||||||
|
|
||||||
|
|
||||||
|
#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 > setting.BandscopePoints)
|
||||||
|
wiFiPoints = setting.BandscopePoints;
|
||||||
|
Serial.printf("No of wifiPoints set to %i\n", wiFiPoints);
|
||||||
|
|
||||||
|
if ( numberOfWebsocketClients > 0 )
|
||||||
|
pushBandscopeSettings ();
|
||||||
|
#endif // #ifdef USE_WIFI
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} // initSweep || changedSetting
|
||||||
|
|
||||||
|
autoSweepStep = 0; // Set the step counter to zero
|
||||||
|
autoSweepFreq = setting.BandscopeStart; // Set the start frequency.
|
||||||
|
|
||||||
|
nextPointFreq = autoSweepFreq + autoSweepFreqStep;
|
||||||
|
|
||||||
|
|
||||||
|
while (( micros() - setFreqMicros ) < delaytime ) // Make sure enough time has elasped since previous frequency write
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
setFreqMicros = micros(); // Store the time the frequency was changed
|
||||||
|
xmit.SetFrequency ( setting.IF_Freq + autoSweepFreq ); // set the LO frequency, tempIF is offset if spur reduction on
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef USE_WIFI
|
||||||
|
|
||||||
|
if ( numberOfWebsocketClients > 0 ) // Start off the json document for the scan
|
||||||
|
{
|
||||||
|
jsonDocument.clear ();
|
||||||
|
chunkIndex = 0;
|
||||||
|
|
||||||
|
jsonDocument["PreAmp"] = setting.PreampGain;
|
||||||
|
jsonDocument["mType"] = "chunkSweep";
|
||||||
|
jsonDocument["StartIndex"] = 0;
|
||||||
|
jsonDocument["sweepPoints"] = sweepPoints;
|
||||||
|
jsonDocument["sweepTime"] = (uint32_t)(sweepMicros/1000);
|
||||||
|
Points = jsonDocument.createNestedArray ( "Points" ); // Add Points array
|
||||||
|
jsonDocInitialised = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
jsonDocInitialised = false;
|
||||||
|
|
||||||
|
#endif // #ifdef USE_WIFI
|
||||||
|
|
||||||
|
sweepStep = 0;
|
||||||
|
startFreq = setting.BandscopeStart + setting.IF_Freq; // Start freq for the LO
|
||||||
|
stopFreq = setting.BandscopeSpan + startFreq; // Stop freq for the LO
|
||||||
|
|
||||||
|
Serial.printf(" start %i; stop %i; points %i \n", startFreq, stopFreq, sweepPoints );
|
||||||
|
|
||||||
|
if ( setActualPowerRequested )
|
||||||
|
{
|
||||||
|
SetPowerLevel ( actualPower );
|
||||||
|
setActualPowerRequested = false;
|
||||||
|
|
||||||
|
// Serial.printf ( "Setting actual Power %f \n", actualPower );
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 > 200 ) &&
|
||||||
|
( (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
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change the transmitter frequency for the next reading and record the time for
|
||||||
|
* the RBW required settling delay.
|
||||||
|
*/
|
||||||
|
uint32_t f = setting.IF_Freq + autoSweepFreq;
|
||||||
|
|
||||||
|
setFreqMicros = micros(); // Store the time the LO frequency was changed
|
||||||
|
xmit.SetFrequency ( f ); // Set the new LO frequency as soon as RSSI read
|
||||||
|
|
||||||
|
|
||||||
|
#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
|
||||||
|
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;
|
||||||
|
jsonDocument.clear();
|
||||||
|
jsonDocument["mType"] = "chunkSweep";
|
||||||
|
jsonDocument["StartIndex"] = sweepStep;
|
||||||
|
jsonDocument["sweepPoints"] = sweepPoints;
|
||||||
|
jsonDocument["sweepTime"] = (uint32_t)(sweepMicros/1000);
|
||||||
|
Points = jsonDocument.createNestedArray ("Points" ); // Add Points array
|
||||||
|
jsonDocInitialised = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // #ifdef USE_WIFI
|
||||||
|
|
||||||
|
myActual[autoSweepStep] = rxRSSI;
|
||||||
|
|
||||||
|
myGain[autoSweepStep] = gainReading;
|
||||||
|
|
||||||
|
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 ( peakLevel < myData[oldSweepStep] )
|
||||||
|
{
|
||||||
|
peakIndex = oldSweepStep;
|
||||||
|
peakLevel = myData[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 = 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
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 > setting.BandscopePoints - 2 * CHAR_WIDTH) )
|
||||||
|
{
|
||||||
|
int16_t scaleX = setting.BandscopePoints - 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 ( X_ORIGIN+oldSweepStep-1, Y_ORIGIN );
|
||||||
|
|
||||||
|
myFreq[oldSweepStep] = oldSweepFreq; // Store the frequency for XML file creation
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
oldPeakLevel = peakLevel; //Save value of peak level for use by the "SetPowerLevel" function
|
||||||
|
|
||||||
|
if ( myActual[setting.BandscopePoints-1] == 0 ) // Ensure a value in last data point
|
||||||
|
{
|
||||||
|
myActual[setting.BandscopePoints-1] = rxRSSI; // Yes, save it
|
||||||
|
myGain[setting.BandscopePoints-1] = gainReading;
|
||||||
|
myFreq[setting.BandscopePoints-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"
|
503
IFsweep.ino
Normal file
503
IFsweep.ino
Normal file
@ -0,0 +1,503 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* IF sweep sweeps the IF Frequency from a start value to an stop value to
|
||||||
|
* measure the SAW and RX low pass filter response.
|
||||||
|
* It requires a fixed strength and frequency signal, and this is provided
|
||||||
|
* by setting the reference output to 30MHz.
|
||||||
|
* The input should be connected to the reference signal output with an external cable
|
||||||
|
*/
|
||||||
|
void initIF_Sweep()
|
||||||
|
{
|
||||||
|
|
||||||
|
init_sweep();
|
||||||
|
|
||||||
|
tinySA_mode = IF_SWEEP;
|
||||||
|
setting.Mode = tinySA_mode;
|
||||||
|
|
||||||
|
ResetIFsweepMenuStack(); // Put menu stack back to root level
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void doIF_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 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 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;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If paused and at the start of a sweep then do nothing
|
||||||
|
*/
|
||||||
|
if (!sweepStartDone && paused)
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
if (( !sweepStartDone || initSweep || changedSetting ) )
|
||||||
|
{
|
||||||
|
if ( initSweep || changedSetting ) // Something has changed, or a first start, so need to work out some basic things
|
||||||
|
{
|
||||||
|
Serial.println("Init IFSweep or changedSetting");
|
||||||
|
autoSweepFreqStep = ( stopFreq_IF - startFreq_IF ) / DISPLAY_POINTS;
|
||||||
|
|
||||||
|
vbw = autoSweepFreqStep / 1000.0; // Set the video resolution
|
||||||
|
// ownrbw = ((float) ( stopFreq_IF - startFreq_IF )) / DISPLAY_POINTS / 1000.0; // kHz
|
||||||
|
//
|
||||||
|
// if ( ownrbw < 2.6 ) // If it's less than 2.6KHz
|
||||||
|
// ownrbw = 2.6; // set it to 2.6KHz
|
||||||
|
//
|
||||||
|
// if ( ownrbw > 620.7 )
|
||||||
|
// ownrbw = 620.7;
|
||||||
|
//
|
||||||
|
// if ( ownrbw != old_ownrbw )
|
||||||
|
// {
|
||||||
|
// bandwidth = rcvr.SetRBW ( ownrbw * 10.0, &delaytime ); // Set it in the receiver Si4432
|
||||||
|
// old_ownrbw = ownrbw;
|
||||||
|
// }
|
||||||
|
|
||||||
|
bandwidth = rcvr.SetRBW ( 106.0, &delaytime ); // Set it in the receiver Si4432. delaytime is returned
|
||||||
|
|
||||||
|
sweepPoints = DISPLAY_POINTS; // At least the right number of points for the display
|
||||||
|
|
||||||
|
sweepFreqStep = ( stopFreq_IF - startFreq_IF ) / sweepPoints; // Step for each reading
|
||||||
|
|
||||||
|
att.SetAtten ( 0 ); // Set the internal attenuator
|
||||||
|
|
||||||
|
xmit.SetPowerReference ( setting.ReferenceOut ); // Set the GPIO reference output
|
||||||
|
|
||||||
|
#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 > DISPLAY_POINTS*OVERLAP)
|
||||||
|
wiFiPoints = DISPLAY_POINTS*OVERLAP;
|
||||||
|
// Serial.printf("No of wifiPoints set to %i\n", wiFiPoints);
|
||||||
|
|
||||||
|
pushIFSweepSettings();
|
||||||
|
#endif // #ifdef USE_WIFI
|
||||||
|
|
||||||
|
|
||||||
|
} // initSweep || changedSetting
|
||||||
|
|
||||||
|
autoSweepStep = 0; // Set the step counter to zero
|
||||||
|
autoSweepFreq = startFreq_IF; // 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 IF frequency
|
||||||
|
|
||||||
|
setFreqMicros = micros(); // Store the time the frequency was changed
|
||||||
|
xmit.SetFrequency ( sigFreq_IF + autoSweepFreq ); // set the LO frequency to the IF plus 30Mhz ref out
|
||||||
|
|
||||||
|
// Serial.printf("autoSweepFreq init: %u\n", autoSweepFreq);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef USE_WIFI
|
||||||
|
|
||||||
|
if ( numberOfWebsocketClients > 0 ) // Start off the json document for the scan
|
||||||
|
{
|
||||||
|
jsonDocument.clear ();
|
||||||
|
chunkIndex = 0;
|
||||||
|
|
||||||
|
jsonDocument["PreAmp"] = setting.PreampGain; // Fixed gain
|
||||||
|
jsonDocument["mType"] = "chunkSweep";
|
||||||
|
jsonDocument["StartIndex"] = 0;
|
||||||
|
jsonDocument["sweepPoints"] = sweepPoints;
|
||||||
|
jsonDocument["sweepTime"] = (uint32_t)(sweepMicros/1000);
|
||||||
|
Points = jsonDocument.createNestedArray ( "Points" ); // Add Points array
|
||||||
|
jsonDocInitialised = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
jsonDocInitialised = false;
|
||||||
|
|
||||||
|
#endif // #ifdef USE_WIFI
|
||||||
|
|
||||||
|
sweepStep = 0;
|
||||||
|
startFreq = startFreq_IF + sigFreq_IF; // Start freq for the LO
|
||||||
|
stopFreq = stopFreq_IF + sigFreq_IF; // Stop freq for the LO
|
||||||
|
|
||||||
|
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 > 200 ) &&
|
||||||
|
( (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.
|
||||||
|
*/
|
||||||
|
uint32_t f = sigFreq_IF + autoSweepFreq;
|
||||||
|
|
||||||
|
setFreqMicros = micros(); // Store the time the LO frequency was changed
|
||||||
|
rcvr.SetFrequency ( autoSweepFreq ); // Set the RX Si4432 to the IF frequency
|
||||||
|
|
||||||
|
xmit.SetFrequency ( f ); // Set the new LO frequency as soon as RSSI read
|
||||||
|
// Serial.printf("Required: %i Actual %i\n", tempIF+autoSweepFreq, xmit.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;
|
||||||
|
jsonDocument.clear();
|
||||||
|
jsonDocument["mType"] = "chunkSweep";
|
||||||
|
jsonDocument["StartIndex"] = sweepStep;
|
||||||
|
jsonDocument["sweepPoints"] = sweepPoints;
|
||||||
|
jsonDocument["sweepTime"] = (uint32_t)(sweepMicros/1000);
|
||||||
|
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 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
|
||||||
|
|
||||||
|
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 > DISPLAY_POINTS - 2 * CHAR_WIDTH) )
|
||||||
|
{
|
||||||
|
int16_t scaleX = DISPLAY_POINTS - 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 ( X_ORIGIN+oldSweepStep-1, Y_ORIGIN );
|
||||||
|
|
||||||
|
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[DISPLAY_POINTS-1] == 0 ) // Ensure a value in last data point
|
||||||
|
{
|
||||||
|
myActual[DISPLAY_POINTS-1] = rxRSSI; // Yes, save it
|
||||||
|
myGain[DISPLAY_POINTS-1] = gainReading;
|
||||||
|
myFreq[DISPLAY_POINTS-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 )"
|
||||||
|
|
||||||
|
|
||||||
|
}
|
674
LICENSE
Normal file
674
LICENSE
Normal file
@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
28
README.md
Normal file
28
README.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# simpleSA
|
||||||
|
|
||||||
|
Simple Spectrum Analyser, based on 2 off SI4432 modules, an ADE25 mixer,
|
||||||
|
a programmable attenuator and some filters.
|
||||||
|
This version is intended for homebrew by those experienced in amateur radio or
|
||||||
|
similar techniques, or those wishing to explore these and learn the hard way!
|
||||||
|
No support is available for the code or the hardware.
|
||||||
|
|
||||||
|
|
||||||
|
The code was initially based on Arduino code for STM "Blue Pill" developed by
|
||||||
|
Erik PD0EK and ported to run on an ESP32 by Dave M0WID.
|
||||||
|
|
||||||
|
|
||||||
|
Subsequently the code has been extensively reorganised, modified and developed,
|
||||||
|
with much of the work done by John WA2FZW.
|
||||||
|
Additional features have been added, including the ability to view the
|
||||||
|
trace and change settings from web clients, and later additional modes
|
||||||
|
such as signal generator, IFSweep and the ability to control a tracking
|
||||||
|
generator.
|
||||||
|
|
||||||
|
Glenn VK3PE has developed some boards for this version - see his website.
|
||||||
|
|
||||||
|
Erik has since gone on to produce a commercial version called TinySA.
|
||||||
|
The commercial version has quite a lot of additional features, but does not
|
||||||
|
include the wifi features of this version, or the integrated tracking
|
||||||
|
generator.
|
||||||
|
|
||||||
|
Dave M0WID
|
290
SigLo.ino
Normal file
290
SigLo.ino
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* ########################################################################
|
||||||
|
*
|
||||||
|
* Initialise variables and SI4432 for sig gen mode
|
||||||
|
*
|
||||||
|
* ########################################################################
|
||||||
|
*/
|
||||||
|
void initSigLow()
|
||||||
|
{
|
||||||
|
|
||||||
|
// Use the TFT_eSPI buttons for now.
|
||||||
|
// This could be changed to use something similar to the main menu
|
||||||
|
|
||||||
|
tft.fillScreen(SIG_BACKGROUND_COLOR);
|
||||||
|
|
||||||
|
//img.unloadFont(); // Free up memory from any previous incarnation of img
|
||||||
|
img.deleteSprite();
|
||||||
|
img.setColorDepth ( 16 );
|
||||||
|
img.setAttribute ( PSRAM_ENABLE, false ); // Don't use the PSRAM on the WROVERs
|
||||||
|
img.createSprite ( 320, 55 ); // used for frequency display
|
||||||
|
img.loadFont(SA_FONT_LARGE);
|
||||||
|
|
||||||
|
SetRX ( 1 ); // LO and RX both in receive until turned on by UI
|
||||||
|
|
||||||
|
#ifdef SI_TG_IF_CS
|
||||||
|
if (tgIF_OK) {
|
||||||
|
tg_if.RxMode ( ); // turn off the IF oscillator in tracking generator
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef SI_TG_LO_CS
|
||||||
|
if (tgLO_OK) {
|
||||||
|
tg_lo.RxMode ( ); // turn off the Local Oscillator in tracking generator
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int showUpDownButtons = 0;
|
||||||
|
#ifdef SHOW_FREQ_UP_DOWN_BUTTONS
|
||||||
|
showUpDownButtons = 1;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
xmit.SetDrive ( sigGenSetting.LO_Drive ); // Set Local Oscillator power level
|
||||||
|
rcvr.SetDrive ( sigGenSetting.RX_Drive ); // Set receiver SI4432 power level
|
||||||
|
|
||||||
|
tinySA_mode = SIG_GEN_LOW_RANGE;
|
||||||
|
setting.Mode = tinySA_mode;
|
||||||
|
|
||||||
|
tft.unloadFont();
|
||||||
|
tft.setCursor ( X_ORIGIN + 50, SCREEN_HEIGHT - CHAR_HEIGHT );
|
||||||
|
tft.setTextSize(1);
|
||||||
|
tft.setTextColor ( YELLOW );
|
||||||
|
tft.printf ( "Mode:%s", modeText[setting.Mode] );
|
||||||
|
tft.setTextColor ( WHITE );
|
||||||
|
|
||||||
|
tft.loadFont(KEY_FONT);
|
||||||
|
|
||||||
|
// draw the buttons
|
||||||
|
for (int i = 0; i < SIG_KEY_COUNT; i++)
|
||||||
|
{
|
||||||
|
if ( showUpDownButtons || ( i > 13 ))
|
||||||
|
{
|
||||||
|
key[i].initButton(&tft,
|
||||||
|
// x, y, w, h, outline, fill, text
|
||||||
|
sig_keys[i].x,
|
||||||
|
sig_keys[i].y,
|
||||||
|
sig_keys[i].width,
|
||||||
|
sig_keys[i].height,
|
||||||
|
DARKGREY, // outline colour
|
||||||
|
sig_keys[i].color, // fill
|
||||||
|
TFT_BLACK, // Text colour
|
||||||
|
"", // 10 Byte Label
|
||||||
|
2); // font size multiplier (not used when font loaded)
|
||||||
|
|
||||||
|
// setLabelDatum(uint16_t x_delta, uint16_t y_delta, uint8_t datum)
|
||||||
|
key[i].setLabelDatum(1, 1, MC_DATUM);
|
||||||
|
|
||||||
|
// Draw button and specify label string
|
||||||
|
// Specifying label string here will allow more than the default 10 byte label
|
||||||
|
key[i].drawButton(false, sig_keys[i].text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw the slider to control output level
|
||||||
|
// we re-purpose the sSprite for this
|
||||||
|
|
||||||
|
sSprite.deleteSprite();
|
||||||
|
sSprite.setColorDepth ( 16 );
|
||||||
|
sSprite.setAttribute ( PSRAM_ENABLE, false ); // Don't use the PSRAM on the WROVERs
|
||||||
|
sSprite.createSprite ( SLIDER_WIDTH + 2 * SLIDER_KNOB_RADIUS + 60, 2 * SLIDER_KNOB_RADIUS ); // used for slider and value
|
||||||
|
sSprite.setTextColor(TFT_ORANGE);
|
||||||
|
sSprite.loadFont(KEY_FONT);
|
||||||
|
|
||||||
|
// Slider range will be something like -60 to -10dBm for low frequency range
|
||||||
|
// (to be changed once I have worked out what the real values should be)
|
||||||
|
// Parameter passed in are x, y and slider knob position in %
|
||||||
|
|
||||||
|
float sPercent = (float)(sigGenSetting.Power - sigGenSetting.Calibration + ATTENUATOR_RANGE) * 100.0
|
||||||
|
/ (float)(ATTENUATOR_RANGE);
|
||||||
|
|
||||||
|
drawSlider(SLIDER_X, SLIDER_Y, sPercent, sigGenSetting.Power, "dBm");
|
||||||
|
|
||||||
|
att.SetAtten ( sigGenSetting.Calibration - sigGenSetting.Power ); // set attenuator to give required output
|
||||||
|
|
||||||
|
oldFreq = 0; // Force write of frequency on first loop
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
||||||
|
* Low frequency range signal generator
|
||||||
|
* '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
||||||
|
*/
|
||||||
|
|
||||||
|
void doSigGenLow ()
|
||||||
|
{
|
||||||
|
static uint32_t oldIF; // to store the current IF
|
||||||
|
|
||||||
|
uint16_t t_x = 0, t_y = 0; // To store the touch coordinates
|
||||||
|
|
||||||
|
int showUpDownButtons = 0;
|
||||||
|
#ifdef SHOW_FREQ_UP_DOWN_BUTTONS
|
||||||
|
showUpDownButtons = 1;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// Get current touch state and coordinates
|
||||||
|
boolean pressed = tft.getTouch(&t_x, &t_y); // Just uses standard TFT_eSPI function as not bothered about speed
|
||||||
|
|
||||||
|
// Adjust press state of each key appropriately
|
||||||
|
for (uint8_t b = 0; b < SIG_KEY_COUNT; b++) {
|
||||||
|
if (pressed && key[b].contains(t_x, t_y))
|
||||||
|
key[b].press(true); // tell the button it is pressed
|
||||||
|
else
|
||||||
|
key[b].press(false); // tell the button it is NOT pressed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any key has changed state
|
||||||
|
for (uint8_t b = 0; b < SIG_KEY_COUNT; b++)
|
||||||
|
{
|
||||||
|
if ( showUpDownButtons || ( b > 13 ))
|
||||||
|
{
|
||||||
|
if ( key[b].justPressed() ) {
|
||||||
|
switch (b) {
|
||||||
|
case 0: // Increment buttons
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
case 6:
|
||||||
|
incrementFreq( pow(10, 8-b) );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 7: // Decrement buttons
|
||||||
|
case 8:
|
||||||
|
case 9:
|
||||||
|
case 10:
|
||||||
|
case 11:
|
||||||
|
case 12:
|
||||||
|
case 13:
|
||||||
|
decrementFreq( pow(10, 15-b) );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 14: // Return to SAlo mode
|
||||||
|
key[b].drawButton(true, sig_keys[b].activeText);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 15: // toggle the output on/off
|
||||||
|
sigGenOutputOn = !sigGenOutputOn;
|
||||||
|
if (sigGenOutputOn) {
|
||||||
|
SetRX(3); // both LO and RX in transmit. Output levels have been set in the init function
|
||||||
|
key[b].drawButton(true, sig_keys[b].activeText);
|
||||||
|
tft.setCursor(sig_keys[b].x + 30, sig_keys[b].y);
|
||||||
|
tft.fillRect(sig_keys[b].x + 30, sig_keys[b].y, sig_keys[b].x + 60, sig_keys[b].y + 10, SIG_BACKGROUND_COLOR);
|
||||||
|
tft.setTextColor(TFT_GREEN, SIG_BACKGROUND_COLOR );
|
||||||
|
tft.print(" ON");
|
||||||
|
} else {
|
||||||
|
SetRX(1); // Both in receive
|
||||||
|
key[b].drawButton(false, sig_keys[b].text);
|
||||||
|
tft.setCursor(sig_keys[b].x + 30, sig_keys[b].y);
|
||||||
|
tft.fillRect(sig_keys[b].x + 30, sig_keys[b].y, sig_keys[b].x + 60, sig_keys[b].y + 10, SIG_BACKGROUND_COLOR);
|
||||||
|
tft.setTextColor(TFT_WHITE, SIG_BACKGROUND_COLOR );
|
||||||
|
tft.print("OFF");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 16: // launch menu
|
||||||
|
key[b].drawButton(true, sig_keys[b].activeText);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Serial.printf("Button %i press not handled", b );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (key[b].isPressed()) { // button held
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// // If button was just released
|
||||||
|
if (key[b].justReleased())
|
||||||
|
{
|
||||||
|
switch (b) {
|
||||||
|
case 14: // Return to SAlo mode
|
||||||
|
WriteSigGenSettings ();
|
||||||
|
initSweepLow();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 16: // launch signal generator menu
|
||||||
|
StartSigGenMenu ( );
|
||||||
|
return;
|
||||||
|
|
||||||
|
case 17: // launch frequency keypad
|
||||||
|
StartSigGenFreq ( );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // end of keys loop
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Check if slider touched
|
||||||
|
if ( sliderPressed( pressed, t_x, t_y) )
|
||||||
|
{
|
||||||
|
float p = sliderPercent( t_x ); // position of slider in %
|
||||||
|
float pwr = p * (ATTENUATOR_RANGE)/100.0 + sigGenSetting.Calibration - ATTENUATOR_RANGE;
|
||||||
|
|
||||||
|
drawSlider ( SLIDER_X, SLIDER_Y, p, pwr, "dBm" );
|
||||||
|
att.SetAtten ( ( 100.0 - p ) / 100.0 * ATTENUATOR_RANGE ); // set attenuator to give required output
|
||||||
|
sigGenSetting.Power = pwr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// draw frequency. Uses a sprite to avoid flicker
|
||||||
|
img.fillSprite(SIG_BACKGROUND_COLOR);
|
||||||
|
img.setCursor(5,5);
|
||||||
|
img.setTextColor(TFT_ORANGE);
|
||||||
|
img.printf("%s",DisplayFrequency ( sigGenSetting.Frequency ) );
|
||||||
|
img.pushSprite(0,80);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* set RX to IF_Frequency and LO to IF plus required frequency
|
||||||
|
*
|
||||||
|
* but only if value has changed to avoid the SI4432 running its state change sequencer
|
||||||
|
* The mixer will produce IF + LO and IF - LO
|
||||||
|
* The Low pass filter will filter out the higher frequency and LO leakage
|
||||||
|
* The IF SAW filter will smooth out the waveform produced by the SI4432
|
||||||
|
*/
|
||||||
|
if ( (oldFreq != sigGenSetting.Frequency) || (oldIF != setting.IF_Freq) )
|
||||||
|
{
|
||||||
|
rcvr.SetFrequency ( setting.IF_Freq );
|
||||||
|
xmit.SetFrequency ( setting.IF_Freq + sigGenSetting.Frequency );
|
||||||
|
Serial.println("set frequency");
|
||||||
|
if (sigGenOutputOn)
|
||||||
|
{
|
||||||
|
delayMicroseconds(300);
|
||||||
|
SetRX(3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oldFreq = sigGenSetting.Frequency;
|
||||||
|
oldIF = setting.IF_Freq;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void incrementFreq(uint32_t amount) {
|
||||||
|
sigGenSetting.Frequency += amount;
|
||||||
|
if (sigGenSetting.Frequency > MAX_SIGLO_FREQ)
|
||||||
|
sigGenSetting.Frequency = MAX_SIGLO_FREQ;
|
||||||
|
}
|
||||||
|
|
||||||
|
void decrementFreq(uint32_t amount) {
|
||||||
|
|
||||||
|
if (sigGenSetting.Frequency > amount) {
|
||||||
|
sigGenSetting.Frequency -= amount;
|
||||||
|
if (sigGenSetting.Frequency < MIN_SIGLO_FREQ)
|
||||||
|
sigGenSetting.Frequency = MIN_SIGLO_FREQ;
|
||||||
|
} else {
|
||||||
|
sigGenSetting.Frequency = MIN_SIGLO_FREQ;
|
||||||
|
}
|
||||||
|
}
|
643
SweepLo.ino
Normal file
643
SweepLo.ino
Normal file
@ -0,0 +1,643 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Initialise variables and SI4432 for the low frequency sweep
|
||||||
|
*/
|
||||||
|
void initSweepLow()
|
||||||
|
{
|
||||||
|
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 uint16_t currentPointRSSI;
|
||||||
|
static uint16_t peakRSSI;
|
||||||
|
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 resetAverage; // Flag to indicate a setting has changed and average valuesneeds to be reset
|
||||||
|
|
||||||
|
static bool jsonDocInitialised = false;
|
||||||
|
static uint16_t chunkIndex;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 ) / DISPLAY_POINTS;
|
||||||
|
|
||||||
|
vbw = autoSweepFreqStep / 1000.0; // Set the video resolution
|
||||||
|
ownrbw = setting.Bandwidth10 / 10.0; // and the resolution bandwidth
|
||||||
|
|
||||||
|
if ( ownrbw == 0.0 ) // If the bandwidth is on "Auto" work out the required RBW
|
||||||
|
ownrbw = ((float) ( setting.ScanStop - setting.ScanStart )) / 290000.0; // 290 points on display
|
||||||
|
|
||||||
|
if ( ownrbw < 2.6 ) // If it's less than 2.6KHz
|
||||||
|
ownrbw = 2.6; // set it to 2.6KHz
|
||||||
|
|
||||||
|
if ( ownrbw > 620.7 )
|
||||||
|
ownrbw = 620.7;
|
||||||
|
|
||||||
|
if ( ownrbw != old_ownrbw )
|
||||||
|
{
|
||||||
|
bandwidth = rcvr.SetRBW ( ownrbw * 10.0, &delaytime ); // Set it in the receiver Si4432
|
||||||
|
old_ownrbw = ownrbw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 < DISPLAY_POINTS )
|
||||||
|
sweepPoints = DISPLAY_POINTS; // 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
resetAverage = changedSetting;
|
||||||
|
|
||||||
|
xmit.SetPowerReference ( setting.ReferenceOut ); // Set the GPIO reference output if wanted
|
||||||
|
|
||||||
|
#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 > DISPLAY_POINTS*OVERLAP)
|
||||||
|
wiFiPoints = DISPLAY_POINTS*OVERLAP;
|
||||||
|
//Serial.printf("No of wifiPoints set to %i\n", wiFiPoints);
|
||||||
|
|
||||||
|
if ( numberOfWebsocketClients > 0 )
|
||||||
|
pushSettings ();
|
||||||
|
#endif // #ifdef USE_WIFI
|
||||||
|
|
||||||
|
#ifdef SI_TG_IF_CS
|
||||||
|
if (tgIF_OK && (trackGenSetting.Mode == 1) )
|
||||||
|
tg_if.TxMode ( trackGenSetting.IF_Drive ); // Set tracking generator IF on
|
||||||
|
else
|
||||||
|
tg_if.RxMode();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef SI_TG_LO_CS
|
||||||
|
if (tgLO_OK && (trackGenSetting.Mode == 1) )
|
||||||
|
tg_lo.TxMode ( trackGenSetting.LO_Drive ); // Set tracking generator LO on
|
||||||
|
else
|
||||||
|
tg_lo.RxMode();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
} // initSweep || changedSetting
|
||||||
|
|
||||||
|
autoSweepStep = 0; // Set the step counter to zero
|
||||||
|
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 = ownrbw * 1000; // bandwidth in Hz
|
||||||
|
if (IF_Shift > MAX_IF_SHIFT)
|
||||||
|
IF_Shift = MAX_IF_SHIFT;
|
||||||
|
tempIF = setting.IF_Freq - IF_Shift;
|
||||||
|
} else {
|
||||||
|
tempIF = setting.IF_Freq;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
#ifdef SI_TG_IF_CS
|
||||||
|
if (tgIF_OK && (trackGenSetting.Mode == 1) )
|
||||||
|
tg_if.SetFrequency ( tempIF + trackGenSetting.Offset ); // Set tracking generator IF for the sweep
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
xmit.SetFrequency ( tempIF + autoSweepFreq ); // set the LO frequency, tempIF is offset if spur reduction on
|
||||||
|
|
||||||
|
#ifdef SI_TG_LO_CS
|
||||||
|
if (tgLO_OK && (trackGenSetting.Mode == 1) )
|
||||||
|
tg_lo.SetFrequency ( tempIF + autoSweepFreq + trackGenSetting.Offset ); // Set tracking generator LO
|
||||||
|
#endif
|
||||||
|
|
||||||
|
setFreqMicros = micros(); // Store the time the frequency was changed
|
||||||
|
|
||||||
|
// if (trackGenSetting.Mode == 1) // debug
|
||||||
|
// Serial.printf("tglo start %i at %i\n", tg_lo.GetFrequency(), setFreqMicros);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef USE_WIFI
|
||||||
|
|
||||||
|
if ( numberOfWebsocketClients > 0 ) // Start off the json document for the scan
|
||||||
|
{
|
||||||
|
jsonDocument.clear ();
|
||||||
|
chunkIndex = 0;
|
||||||
|
|
||||||
|
jsonDocument["PreAmp"] = setting.PreampGain; // Fixed gain
|
||||||
|
jsonDocument["mType"] = "chunkSweep";
|
||||||
|
jsonDocument["StartIndex"] = 0;
|
||||||
|
jsonDocument["sweepPoints"] = sweepPoints;
|
||||||
|
jsonDocument["sweepTime"] = (uint32_t)(sweepMicros/1000);
|
||||||
|
Points = jsonDocument.createNestedArray ( "Points" ); // Add Points array
|
||||||
|
jsonDocInitialised = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
jsonDocInitialised = false;
|
||||||
|
|
||||||
|
#endif // #ifdef USE_WIFI
|
||||||
|
|
||||||
|
sweepStep = 0;
|
||||||
|
startFreq = setting.ScanStart + tempIF; // Start freq for the LO
|
||||||
|
stopFreq = setting.ScanStop + tempIF; // Stop freq for the LO
|
||||||
|
|
||||||
|
if ( setActualPowerRequested )
|
||||||
|
{
|
||||||
|
SetPowerLevel ( actualPower );
|
||||||
|
setActualPowerRequested = false;
|
||||||
|
|
||||||
|
// Serial.printf ( "Setting actual Power %f \n", actualPower );
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 > 200 ) &&
|
||||||
|
( (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
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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("Required: %i Actual %i\n", tempIF+autoSweepFreq, xmit.GetFrequency());
|
||||||
|
|
||||||
|
#ifdef SI_TG_LO_CS
|
||||||
|
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 SI_TG_LO_CS
|
||||||
|
// if (trackGenSetting.Mode == 1)
|
||||||
|
// Serial.printf("tglo %i @ %i\n", tg_lo.GetFrequency(), setFreqMicros);
|
||||||
|
#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
|
||||||
|
// 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;
|
||||||
|
jsonDocument.clear();
|
||||||
|
jsonDocument["mType"] = "chunkSweep";
|
||||||
|
jsonDocument["StartIndex"] = sweepStep;
|
||||||
|
jsonDocument["sweepPoints"] = sweepPoints;
|
||||||
|
jsonDocument["sweepTime"] = (uint32_t)(sweepMicros/1000);
|
||||||
|
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 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 ( peakLevel < myData[oldSweepStep] )
|
||||||
|
{
|
||||||
|
peakIndex = oldSweepStep;
|
||||||
|
peakLevel = myData[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 = 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 > DISPLAY_POINTS - 2 * CHAR_WIDTH) )
|
||||||
|
{
|
||||||
|
int16_t scaleX = DISPLAY_POINTS - 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 ( X_ORIGIN+oldSweepStep-1, Y_ORIGIN );
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
oldPeakLevel = peakLevel; //Save value of peak level for use by the "SetPowerLevel" function
|
||||||
|
|
||||||
|
if ( myActual[DISPLAY_POINTS-1] == 0 ) // Ensure a value in last data point
|
||||||
|
{
|
||||||
|
myActual[DISPLAY_POINTS-1] = rxRSSI; // Yes, save it
|
||||||
|
myGain[DISPLAY_POINTS-1] = gainReading;
|
||||||
|
myFreq[DISPLAY_POINTS-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"
|
192
cmd.h
Normal file
192
cmd.h
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
/*
|
||||||
|
* "Cmd.h" was added in Version 2.0 by John Price (WA2FZW)
|
||||||
|
*
|
||||||
|
* This file contains definitions and function prototypes associated with
|
||||||
|
* the "Cmd.cpp" module.
|
||||||
|
*
|
||||||
|
* In Version 2.0 of the software, all of the serial input command handling
|
||||||
|
* was removed from the main program sile into these modules.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef _CMD_H_
|
||||||
|
#define _CMD_H_ // Prevent double inclusion
|
||||||
|
|
||||||
|
#include "tinySA.h" // General program definitions
|
||||||
|
#include "PE4302.h" // Class definition for thePE4302 attenuator
|
||||||
|
#include "Si4432.h" // Class definition for the Si4432 transceiver
|
||||||
|
#include "preferences.h" // For saving the "setting" structure
|
||||||
|
#include <SPI.h>
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These are numerical values which define the message being processed. We could just
|
||||||
|
* work off the ASCII string, but translating the strings to a numerical value makes
|
||||||
|
* the processing a bit easier and also keeps open the possibility of using more tha
|
||||||
|
* one ASCII string to implement a give command.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define MSG_NONE 0 // Unrecognized command
|
||||||
|
#define MSG_START 1 // Set sweep start frequency
|
||||||
|
#define MSG_STOP 2 // Set sweep stop frequency
|
||||||
|
#define MSG_CENTER 3 // Set sweep center frequency
|
||||||
|
#define MSG_SPAN 4 // Set sweep range
|
||||||
|
#define MSG_FOCUS 5 // Center frequency with narrow sweep
|
||||||
|
#define MSG_DRIVE 6 // Set transmitter (LO) output level
|
||||||
|
#define MSG_VFO_FREQ 7 // Set the frequency for the selected VFO
|
||||||
|
#define MSG_ATTEN 8 // Set the PE4301 attenuation
|
||||||
|
#define MSG_HELP 9 // Display the command menu
|
||||||
|
#define MSG_STEPS 10 // Set or get number of sweep points (not used)
|
||||||
|
#define MSG_DELAY 11 // Set or get delay time between sweep readings
|
||||||
|
#define MSG_VFO 12 // Set or get the currently selected VFO
|
||||||
|
#define MSG_RBW 13 // Set or get the current resolution bandwidth
|
||||||
|
#define MSG_REG_DUMP 14 // Print register values for the selected VFO
|
||||||
|
#define MSG_RSSI 15 // Show RSSI values
|
||||||
|
#define MSG_QUIT 16 // Stop RSSI readings
|
||||||
|
#define MSG_SET_REG 17 // Set or get the value of a specifix register for the selected VFO
|
||||||
|
#define MSG_SAVE 18 // Save scan configuration
|
||||||
|
#define MSG_RECALL 19 // Recall saved scan configuration
|
||||||
|
#define MSG_GPIO2 20 // Set transmitter GPIO2 reference frequency
|
||||||
|
#define MSG_TUNE 21 // Tune selected Si4432 (VFO)
|
||||||
|
#define MSG_CONFIG 22 // Save "config" structure
|
||||||
|
#define MSG_ACT_PWR 23 // Calibrate the observed power level
|
||||||
|
#define MSG_IF_FREQ 24 // Set the IF frequency
|
||||||
|
#define MSG_TRACES 25 // Turn things on the display on or off
|
||||||
|
#define MSG_PREAMP 26 // Set the receiver preamp gain
|
||||||
|
#define MSG_GRID 27 // Set the dB value for top line of the grid
|
||||||
|
#define MSG_SCALE 28 // Set the dB/horizontal line for the grid
|
||||||
|
#define MSG_PAUSE 29 // Pause (or resume) the sweep
|
||||||
|
#define MSG_SWEEPLO 30 // Set Analyse low range mode
|
||||||
|
#define MSG_SIGLO 31 // Signal generate low range mode
|
||||||
|
#define MSG_IF_SWEEP 32 // Set IF Sweep mode
|
||||||
|
#define MSG_MARKER 33 // Configure Markers
|
||||||
|
#define MSG_SPUR 34 // Set Spur reduction on or off
|
||||||
|
#define MSG_WIFI_UPDATE 35 // Set WiFi update target time in us
|
||||||
|
#define MSG_WIFI_POINTS 36 // Set WiFi chunk size
|
||||||
|
#define MSG_WEBSKT_INTERVAL 37 // Set interval between checking websocket for events if no client connected
|
||||||
|
#define MSG_SG_RX_DRIVE 38 // Set Signal Generator RX (IF) Drive level - don't go above the 10dBm rating of the SAW filters
|
||||||
|
#define MSG_SG_LO_DRIVE 39 // Set Signal Generator LO Drive level - limited by mixer/attenuators fitted
|
||||||
|
#define MSG_TG_IF_DRIVE 40 // Set Tracking Generator RX (IF) Drive level - don't go above the 10dBm rating of the SAW filters
|
||||||
|
#define MSG_TG_LO_DRIVE 41 // Set Tracking Generator LO Drive level - limited by mixer/attenuators fitted
|
||||||
|
#define MSG_IFSIGNAL 42 // Set frequency of injected signal for IF Sweep
|
||||||
|
#define MSG_TGOFFSET 43 // Offset TG IF frequency from SA IF
|
||||||
|
#define MSG_BANDSCOPE 44 // Set Bandscope Mode
|
||||||
|
#define MSG_IFSWEEP 45 // Set IF Sweep Mode
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct // Keeps everything together
|
||||||
|
{
|
||||||
|
char Name[20]; // ASCII Command string
|
||||||
|
uint8_t ID; // The internal message number
|
||||||
|
} msg_t;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This "enum" is used by the "SetSweepCenter" function to designate whether we want a
|
||||||
|
* "WIDE" span when setting the normal center frequency, or a "NARROW" span when setting
|
||||||
|
* a focus frequency.
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum { WIDE, NARROW };
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function prototypes for the help menu display and main command processing:
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ShowMenu (); // Displays the command menu
|
||||||
|
|
||||||
|
bool CheckCommand (); // Checks for a new command
|
||||||
|
bool ParseCommand ( char* inBuff ); // Separate command and data
|
||||||
|
bool ProcessCommand ( uint8_t command, char* dataBuff );
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function prototypes for general support functions:
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint32_t ParseFrequency ( char* freqString ); // Handles various frequency input formats
|
||||||
|
int32_t ParseSignedFrequency ( char* freqString ); // Handles various frequency input formats
|
||||||
|
char* FormatFrequency ( uint32_t freq ); // Neatly formats frequencies for output
|
||||||
|
char* FormatSignedFrequency ( int32_t freq ); // Neatly formats signed frequencies for output
|
||||||
|
char* DisplayFrequency ( uint32_t freq ); // Neatly formats frequencies for sig gen display
|
||||||
|
|
||||||
|
uint16_t xtoi ( char* hexString ); // Converts hexadecimal strings into integers
|
||||||
|
bool isHex ( char c ); // Tests if a character is a hexadecimal digit
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These functions all support the main command processing functions and are used to
|
||||||
|
* set the values of things either in the appropriate internal variables and/or in
|
||||||
|
* the PE4302 or Si4432 modules themselves.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void SetRefOutput ( int freq ); // Sets the GPIO2 frequency for the LO
|
||||||
|
|
||||||
|
void SetRefLevel ( int ref ); // Sets the decibel level for the top line of the graph
|
||||||
|
void SetPowerGrid ( int g ); // Sets the dB/vertical divison on the grid
|
||||||
|
|
||||||
|
void SetGenerate ( int8_t g ); // Puts the unit into or out of signal generator mode
|
||||||
|
|
||||||
|
bool SetIFFrequency ( int32_t f ); // Sets the IF frequency
|
||||||
|
|
||||||
|
void SetLoDrive ( uint8_t level ); // Sets LO Si4432 output level
|
||||||
|
void SetSGLoDrive ( uint8_t level );
|
||||||
|
void SetSGRxDrive ( uint8_t level );
|
||||||
|
|
||||||
|
void SetTracking ( int8_t m ); // set tracking generator mode
|
||||||
|
void SetTGLoDrive ( uint8_t level ); // set tracking generator drive
|
||||||
|
void SetTGIfDrive ( uint8_t level );
|
||||||
|
bool SetTGOffset ( int32_t offset); // set tracking generator offset - returns false if invalid
|
||||||
|
int32_t GetTGOffset (void );
|
||||||
|
|
||||||
|
void SetAttenuation ( int a ); // Sets the PE4302 attenuation
|
||||||
|
|
||||||
|
void SetStorage ( void ); // Saves the results of a scan
|
||||||
|
void SetClearStorage ( void ); // Logically erases the saved scan
|
||||||
|
void SetSubtractStorage(void); // Sets the "setting.SubtractStorage" flag
|
||||||
|
|
||||||
|
void RequestSetPowerLevel ( float o ); // Power level calibration
|
||||||
|
void SetPowerLevel ( int o ); // ???
|
||||||
|
|
||||||
|
void SetRBW ( int v ); // Sets the resolution bandwidth
|
||||||
|
|
||||||
|
void SetSpur (int v ); // Turns spurious signal supression on or off
|
||||||
|
|
||||||
|
void SetAverage ( int v ); // Sets the number of readings to average
|
||||||
|
|
||||||
|
void SetPreampGain ( uint8_t gain ); // Set and get the receiver preamp gain
|
||||||
|
uint8_t GetPreampGain ( bool* agc, uint8_t* reg );
|
||||||
|
|
||||||
|
void SetSweepStart ( uint32_t freq ); // Added in Version 2.3
|
||||||
|
uint32_t GetSweepStart ( void );
|
||||||
|
|
||||||
|
void SetSweepStop ( uint32_t freq ); // Added in Version 2.3
|
||||||
|
uint32_t GetSweepStop ( void );
|
||||||
|
|
||||||
|
void SetIFsweepStart ( uint32_t freq ); // Added in Version 3.0c
|
||||||
|
uint32_t GetIFsweepStart ( void );
|
||||||
|
|
||||||
|
void SetBandscopeStart ( uint32_t freq ); // Added in Version 3.0f
|
||||||
|
uint32_t GetBandscopeStart ( void );
|
||||||
|
|
||||||
|
void SetBandscopeSpan ( uint32_t freq ); // Added in Version 3.0f
|
||||||
|
uint32_t GetBandscopeSpan ( void );
|
||||||
|
|
||||||
|
void SetIFsweepStop ( uint32_t freq ); // Added in Version 3.0c
|
||||||
|
uint32_t GetIFsweepStop ( void );
|
||||||
|
|
||||||
|
void SetIFsweepSigFreq ( uint32_t freq ); // Added in Version 3.0c
|
||||||
|
uint32_t GetIFsweepSigFreq ( void );
|
||||||
|
|
||||||
|
void SetSweepCenter ( uint32_t freq, uint8_t span );
|
||||||
|
uint32_t GetSweepCenter ( void );
|
||||||
|
|
||||||
|
void SetSweepSpan ( uint32_t spanRange );
|
||||||
|
uint32_t GetSweepSpan ( void );
|
||||||
|
|
||||||
|
void SetFreq ( int vfo, uint32_t freq );
|
||||||
|
|
||||||
|
bool UpdateMarker ( uint8_t mkr, char action );
|
||||||
|
|
||||||
|
#endif // End of "Cmd.h"
|
304
marker.cpp
Normal file
304
marker.cpp
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
/*
|
||||||
|
* "Marker.cpp" Contains the code for the "Marker" class functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Marker.h" // Class definition
|
||||||
|
|
||||||
|
/*
|
||||||
|
* There are two constructors; the first simply creates an uninitialized
|
||||||
|
* object, and the second one fills in the address of the display object,
|
||||||
|
* the address of the marker's sprite and sets the marker's number. The
|
||||||
|
* "Init" function actually does the work.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
Marker::Marker () {} // Create an uninitialized object
|
||||||
|
|
||||||
|
Marker::Marker ( TFT_eSprite* spr, uint8_t marker )
|
||||||
|
{
|
||||||
|
Init ( spr, marker );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The "Init" function does all the work of the real constructor but can also
|
||||||
|
* be called to initialized a previously uninitialized object.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Marker::Init ( TFT_eSprite* spr, uint8_t marker )
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create our "sprite" and set it up. The actual sprites are created in the main
|
||||||
|
* program. It would make more sense to creat them in here, but I haven't figured
|
||||||
|
* out how to do that without causing crashes or other goofy behaviors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
_sprite = spr; // Pointer to our sprite object
|
||||||
|
|
||||||
|
_sprite->setAttribute ( PSRAM_ENABLE, false ); // Don't use PSRAM on the WROVERs
|
||||||
|
_sprite->createSprite ( MARKER_SPRITE_WIDTH, MARKER_SPRITE_HEIGHT ); // Set the size
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set some default conditions for the time being:
|
||||||
|
*/
|
||||||
|
|
||||||
|
_index = marker - 1; // Save marker number
|
||||||
|
_enabled = false; // Turn it off for now
|
||||||
|
_status = 0; // White, disabled & invisible
|
||||||
|
_x = 0; // No real position yet
|
||||||
|
_y = 0;
|
||||||
|
_frequency = 0; // Don't know the frequency
|
||||||
|
_mode = MKR_PEAK; // Assume peak frequency mode
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the pivot point for the marker - this is the point used when pushing
|
||||||
|
*/
|
||||||
|
|
||||||
|
_sprite->setPivot ( X_MARKER_OFFSET, Y_MARKER_OFFSET );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "Paint" Remembers the "x"and "y" coordinates and sends the marker to the display.
|
||||||
|
* Note x and y are relative to the target sprite
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Marker::Paint ( TFT_eSprite *target, uint16_t x, uint16_t y )
|
||||||
|
{
|
||||||
|
if ( _enabled )
|
||||||
|
{
|
||||||
|
_x = x + 1; // Remember location
|
||||||
|
_y = y;
|
||||||
|
|
||||||
|
target->setPivot ( _x, _y ); // Set pivot point in target.
|
||||||
|
// Push rotated checks the bounds of
|
||||||
|
// the target sprite
|
||||||
|
_sprite->pushRotated ( target, 0, BLACK ); // Send the sprite to the target sprite
|
||||||
|
}
|
||||||
|
|
||||||
|
else // Not enabled
|
||||||
|
return; // Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set or retreive the mode for the marker.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Marker::Mode ( uint8_t mode )
|
||||||
|
{
|
||||||
|
_mode = mode; // Simple enough!
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Marker::Mode ()
|
||||||
|
{
|
||||||
|
return _mode; // Also simple!
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The next three functions set or clear the "_enabled" indicator. There are two
|
||||||
|
* versions of "Enable"; the first simply marks the marker as enabled and the
|
||||||
|
* second enables the marker and sets the mode all at once.
|
||||||
|
*
|
||||||
|
* "Toggle" toggles the enabled/disabled status.
|
||||||
|
*
|
||||||
|
* These functions also manipulate the "MKR_ACTIVE" bit in the "_status" byte.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Marker::Enable () // Enable it
|
||||||
|
{
|
||||||
|
_enabled = true;
|
||||||
|
_status |= MKR_ACTIVE; // In the "_status" byte also
|
||||||
|
}
|
||||||
|
|
||||||
|
void Marker::Enable ( uint8_t mode ) // Enable & set mode all at once
|
||||||
|
{
|
||||||
|
Mode ( mode ); // Set the mode
|
||||||
|
Enable (); // and enable the marker
|
||||||
|
}
|
||||||
|
|
||||||
|
void Marker::Disable () // Disable it
|
||||||
|
{
|
||||||
|
_enabled = false;
|
||||||
|
_status &= ~MKR_ACTIVE; // In the "_status" byte also
|
||||||
|
}
|
||||||
|
|
||||||
|
void Marker::Toggle () // Toggle enabled/disabled status
|
||||||
|
{
|
||||||
|
_enabled = !_enabled;
|
||||||
|
_status ^= MKR_ACTIVE; // In the "_status" byte also
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "isEnabled" returns "true" if the marker is enabled; "false" if not.
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool Marker::isEnabled () // Request enabled/disabled status
|
||||||
|
{
|
||||||
|
return _enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set or get the marker's current frequency.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Marker::Frequency ( uint32_t freq ) // Set the marker's frequency
|
||||||
|
{
|
||||||
|
_frequency = freq;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Marker::Frequency () // Get the frequency
|
||||||
|
{
|
||||||
|
return _frequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Marker::Index () // Get marker's index
|
||||||
|
{
|
||||||
|
return _index;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This version of "Status" simply returns the current "_status" byte, which is updated
|
||||||
|
* whenever a new color for the marker is specified or the enabled/disabled changes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint8_t Marker::Status () // Return the "_status" byte
|
||||||
|
{
|
||||||
|
return _status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This version of "Status" is used to set the "_status" byte. Note, that we don't
|
||||||
|
* check for a legitimate color choice here as the expectation is that this capability
|
||||||
|
* is only used by the main program when the marker statuses saved in flash memory
|
||||||
|
* are recalled at startup.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Marker::Status ( uint8_t status) // Set the status byte
|
||||||
|
{
|
||||||
|
_color = _colors[status & MKR_COLOR];
|
||||||
|
Color ( _color );
|
||||||
|
|
||||||
|
if ( status & MKR_ACTIVE ) // Enable it?
|
||||||
|
Enable (); // Yes, do it
|
||||||
|
|
||||||
|
else // Otherwise
|
||||||
|
Disable (); // Turn it off
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This version of "Color" (do I need an alternate version named "Colour" for the
|
||||||
|
* Brits?) simply returns the 16 bit color assigned to the marker.
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint16_t Marker::Color () // Returns the marker's color
|
||||||
|
{
|
||||||
|
return _color;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This version of "Color" is used by the serial command handler and the touch screen
|
||||||
|
* menu system to specify the color for the marker.
|
||||||
|
*
|
||||||
|
* There are only six colors allowed. The number is based on the fact that the touch
|
||||||
|
* screen menu can only display that many choices at once. The legal colors are defined
|
||||||
|
* in the "_colors" array in the header file.
|
||||||
|
*
|
||||||
|
* The function checks to see if the specified color is in the list and if so, not only
|
||||||
|
* sets the "_color" variable, but also updates the "_status" byte.
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool Marker::Color ( uint16_t color ) // Sets the marker's color
|
||||||
|
{
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The "markerBitmap" shows where in the "sprite" the pixels should be of
|
||||||
|
* the specified color (1) and where the background color (0) should be used.
|
||||||
|
*
|
||||||
|
* Using the bitmap, we build the "sprite" using the specified color.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const uint8_t markerBitmap[] =
|
||||||
|
{
|
||||||
|
0xFE, 0xEE, 0xCE, 0xEE, 0xEE, 0xEE, 0xC6, 0x7C, 0X38, 0x10, // Marker 1
|
||||||
|
0xFE, 0xC6, 0xBA, 0xFA, 0xC6, 0xBE, 0x82, 0x7C, 0x38, 0x10, // Marker 2
|
||||||
|
0xFE, 0xC6, 0xBA, 0xE6, 0xFA, 0xBA, 0xC6, 0x7C, 0x38, 0x10, // Marker 3
|
||||||
|
0xFE, 0xF6, 0xE6, 0xD6, 0xB6, 0xB6, 0x82, 0x74, 0x38, 0x10, // Marker 4
|
||||||
|
};
|
||||||
|
|
||||||
|
int line; // Which horizontal line we're painting
|
||||||
|
int column; // and which column
|
||||||
|
int colorIx; // Loop index
|
||||||
|
uint8_t bits; // One byte from the marker definition
|
||||||
|
bool returnCode = false; // Assume bad color specification
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The first thing we do is to compare the requested color to the legitimate ones
|
||||||
|
* and if we don't find a match, return a "false" indication.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for ( colorIx = 0; colorIx < MKR_COLOR_COUNT; colorIx++ )
|
||||||
|
if ( color == _colors[colorIx] ) // Found a match?
|
||||||
|
{
|
||||||
|
returnCode = true; // Set good return code
|
||||||
|
break; // No need to look further
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !returnCode ) // If no match found
|
||||||
|
return false; // Return "false"
|
||||||
|
|
||||||
|
_color = color; // It's a legal color, so save it
|
||||||
|
_status = _status & ~MKR_COLOR; // Clear the previous color
|
||||||
|
_status = _status | colorIx; // Set the new one
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Next, we re-paint our sprite in the new color.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for ( line = 0; line < MARKER_HEIGHT; line++ ) // Paint one line at a time
|
||||||
|
{
|
||||||
|
bits = markerBitmap[_index * MARKER_HEIGHT + line]; // Get bitmap of the next line
|
||||||
|
|
||||||
|
for ( column = 0; column < MARKER_WIDTH; column++ ) // horizontally left to right
|
||||||
|
{
|
||||||
|
if ( bits & 0x80 ) // Next pixel turned on?
|
||||||
|
_sprite->drawPixel ( column, line, SwapBytes ( _color )); // Yes, use specified color
|
||||||
|
|
||||||
|
else
|
||||||
|
_sprite->drawPixel ( column, line, BACKGROUND );// Use display background color
|
||||||
|
|
||||||
|
bits <<= 1; // Shift the bitmap byte one place left
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // Good return code
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "SwapBytes" was added in Version 2.9. Bodmer did something in the TFT_eSPI library
|
||||||
|
* (version 2.2.5 and later) that makes it necessary to swap the bytes in the color
|
||||||
|
* word when using rotated sprites (as we do here).
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint16_t Marker::SwapBytes ( uint16_t color)
|
||||||
|
{
|
||||||
|
uint16_t low = ( color & 0x00FF ) << 8;
|
||||||
|
uint16_t high = ( color & 0xFF00 ) >> 8;
|
||||||
|
uint16_t swap = low | high;
|
||||||
|
|
||||||
|
return swap;
|
||||||
|
}
|
125
marker.h
Normal file
125
marker.h
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* "Markers.h" defines the "Marker" class.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _MARKERS_H_ // Prevent double include
|
||||||
|
#define _MARKERS_H_
|
||||||
|
|
||||||
|
#include "tinySA.h" // Definitions needed by the whole program
|
||||||
|
|
||||||
|
#define MARKER_COUNT 4 // MUST be set to '4' for now
|
||||||
|
|
||||||
|
#define MARKER_SPRITE_HEIGHT 10 // Reverse of marker width and height
|
||||||
|
#define MARKER_SPRITE_WIDTH 7
|
||||||
|
#define MARKER_WIDTH 7 // Markers are 7 pixels wide
|
||||||
|
#define MARKER_HEIGHT 10 // and 10 pixels high
|
||||||
|
#define X_MARKER_OFFSET 3 // Center offset from x position
|
||||||
|
#define Y_MARKER_OFFSET MARKER_SPRITE_HEIGHT // Top offset from Y position
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These definitions are intended to allow the marker to be set to particular
|
||||||
|
* places on the spectrum display, but we haven't exactly figured out how to do that
|
||||||
|
* yet. For now, marker #1 is always at the highest peak, marker #2 at the 2nd highest
|
||||||
|
* peak, etc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define MKR_PEAK 1 // Marker is at the maximum signal point
|
||||||
|
#define MKR_LEFT 2 // Marker is at first peak left of maximum signal
|
||||||
|
#define MKR_RIGHT 3 // Marker is at first peak right of maximum signal
|
||||||
|
#define MKR_START 4 // Marker is at sweep start point
|
||||||
|
#define MKR_STOP 5 // Marker is at sweep end point
|
||||||
|
#define MKR_CENTER 6 // Marker is at the center of the sweep range
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These definitions are for the bits in the "_status" byte. The "_status" byte
|
||||||
|
* contains the enabled status and the color of the marker. It provides a compact
|
||||||
|
* way of saving the marker's configuration and restoring the status of the marker
|
||||||
|
* when the program is restarted as we do for all the other sweep parameters.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define MKR_COLOR 0x07 // The bottom three bits hold an index to the color
|
||||||
|
#define MKR_ACTIVE 0x08 // Enabled/disabled status
|
||||||
|
#define MKR_VISIBLE 0x10 // Not used yet; I'll explain later!
|
||||||
|
#define MKR_COLOR_COUNT 6 // Limited by touch screen menu entries
|
||||||
|
|
||||||
|
|
||||||
|
class Marker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function prototypes available to the outside world:
|
||||||
|
*
|
||||||
|
* There are three constructors; the first simply creates an uninitialized object
|
||||||
|
* The second fills in the details by calling one version of the "Init" function.
|
||||||
|
* The third one is designed to setup the marker using the "status" byte saved in
|
||||||
|
* the flash memory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
explicit Marker (); // Do nothing constructor
|
||||||
|
explicit Marker ( TFT_eSprite* spr, uint8_t marker ); // Real constructor #1
|
||||||
|
|
||||||
|
void Init ( TFT_eSprite* spr, uint8_t marker ); // Psuudo constructor #1
|
||||||
|
|
||||||
|
void Paint ( TFT_eSprite* target, uint16_t x, uint16_t y ); // Put it on the target sprite
|
||||||
|
|
||||||
|
void Mode ( uint8_t mode ); // Set marker mode
|
||||||
|
uint8_t Mode (); // Request marker's mode
|
||||||
|
|
||||||
|
void Enable (); // Enable it
|
||||||
|
void Enable ( uint8_t mode ); // Enable & set mode all at once
|
||||||
|
void Disable (); // Disable it
|
||||||
|
void Toggle (); // Toggle enable/disable
|
||||||
|
bool isEnabled (); // Request enabled/disabled status
|
||||||
|
|
||||||
|
uint8_t Status (); // Return the "_status" byte
|
||||||
|
void Status ( uint8_t status); // Set the status byte
|
||||||
|
|
||||||
|
uint16_t Color (); // Returns the merker's color
|
||||||
|
bool Color ( uint16_t color ); // Sets the marker's color
|
||||||
|
|
||||||
|
void Frequency ( uint32_t freq ); // Set the marker's frequency
|
||||||
|
uint32_t Frequency (); // Get the frequency
|
||||||
|
|
||||||
|
uint8_t Index (); // Get marker's index
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
TFT_eSprite* _sprite ; // Sprite for the marker
|
||||||
|
|
||||||
|
uint32_t _frequency; // Marker's frequency
|
||||||
|
uint16_t _x; // Horizontal coordinate on the screen
|
||||||
|
uint16_t _y; // Vertical coordinate on the screen
|
||||||
|
uint16_t _color; // Marker's color
|
||||||
|
uint8_t _index; // Which marker?
|
||||||
|
uint8_t _mode; // Marker's mode
|
||||||
|
byte _status; // Color, enabled and visible all in a byte
|
||||||
|
bool _enabled; // Marker is or isn't enabled
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we ever figure out how to use different references for the markers,
|
||||||
|
* these define the labels for the top of the screen:
|
||||||
|
*/
|
||||||
|
|
||||||
|
const char* _text[6] = { "dBm", "dBc", "uV", "mV", "uW", "mW"};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The colors available are limited to the following six. Why six? Because that's the
|
||||||
|
* maximum number of touch screen menu selections we can display at once.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
const uint16_t _colors[MKR_COLOR_COUNT] =
|
||||||
|
{
|
||||||
|
WHITE, RED, BLUE, GREEN, YELLOW, ORANGE
|
||||||
|
};
|
||||||
|
|
||||||
|
uint16_t SwapBytes ( uint16_t color );
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
136
menu.cpp
Normal file
136
menu.cpp
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
* "Menu.cpp" implements the "Menuitem" class.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Menu.h" // Class definition
|
||||||
|
|
||||||
|
Menuitem::Menuitem () {} // Placeholder constructor
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In the original macro implementation, one could enter "text" as a string in the format:
|
||||||
|
* "\2line1\0line2" for 2 line button labels. In the original implementation, the string
|
||||||
|
* was parsed everytime the button was created.
|
||||||
|
*
|
||||||
|
* Here, we'll parse it into "_text1" and "_text2" when the object is created saving some
|
||||||
|
* work each time the object is used. If the "text" does not begin with the '\2', it's a
|
||||||
|
* one-line label and we set "_text2" to NULL.
|
||||||
|
*
|
||||||
|
* This constructor is used for "MT_FUNC" type objects where the third argument is a
|
||||||
|
* pointer to the function that process the menu item.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Menuitem::Menuitem ( uint8_t type, const char* text, fptr callback )
|
||||||
|
{
|
||||||
|
_type = type; // Menu item type
|
||||||
|
_callback = callback; // Save pointer to callback function
|
||||||
|
ParseLabel ( text ); // Parse two line labels
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This constructor is used for "MT_MENU" type objects. It's identical to the previous
|
||||||
|
* constructor except the third argument is the address of the sub-menu to be displayed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Menuitem::Menuitem ( uint8_t type, const char* text, Menuitem* submenu )
|
||||||
|
{
|
||||||
|
_type = type; // Menu item type
|
||||||
|
_submenu = submenu; // Save pointer to the sub-menu
|
||||||
|
ParseLabel ( text ); // Parse two line labels
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This constructor is for the "MT_BACK" type objects where we need only the type and
|
||||||
|
* a label, but no menu or function pointer.
|
||||||
|
*
|
||||||
|
* It is also used for the "MT_END" type objects where we don't need a label or a pointer.
|
||||||
|
* The "text" argument defaults to "NULL" if it is not specified. Note that if a button
|
||||||
|
* label is supplied we do allow a two-line label, but we really don't expect that to
|
||||||
|
* be used.
|
||||||
|
*/
|
||||||
|
|
||||||
|
Menuitem::Menuitem ( uint8_t type, const char* text )
|
||||||
|
{
|
||||||
|
_type = type; // Menu item type
|
||||||
|
ParseLabel ( text ); // Parse two line labels
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The "Call" member simply calls the callback function with the specified argument,
|
||||||
|
* which is it's position in the menu list. If the object isn't an "MT_FUNC" type, it
|
||||||
|
* does nothing, ensuring we don't try to call some bogus address.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Menuitem::Call ( int num )
|
||||||
|
{
|
||||||
|
if ( _type == MT_FUNC ) // "Call" only works for "FUNC" type
|
||||||
|
_callback ( num );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These two functions return the text for line1 and (if a multiline label), the text
|
||||||
|
* for line2 of the label. If it's a single line label, "Text2" returns NULL.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const char* Menuitem::Text1 ()
|
||||||
|
{
|
||||||
|
return _text1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* Menuitem::Text2 ()
|
||||||
|
{
|
||||||
|
return _text2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the menu item type ( MT_MENU, MT_FUNC, MT_CLOSE, MT_BACK, or MT_END )
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint8_t Menuitem::Type ()
|
||||||
|
{
|
||||||
|
return _type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns "true" if the button label has 2 lines; false if it's a single-line label.
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool Menuitem::isMultiline ()
|
||||||
|
{
|
||||||
|
return ( _text2 != NULL ); // If "_text2" isn't "NULL", the answer is "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
Menuitem* Menuitem::GetSubmenu () // For "MENU" type object, returns the sub-menu pointer
|
||||||
|
{
|
||||||
|
if ( _type == MT_MENU ) // Only allowed for "MENU" types
|
||||||
|
return _submenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This private function handles parsing the two-line buttopn labels into "_text1" and
|
||||||
|
* "_text2". If it's a single line label, "_text2" is set to "NULL".
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Menuitem::ParseLabel ( const char* text ) // Parse two line labels
|
||||||
|
{
|
||||||
|
if ( text == NULL ) // If NULL pointer
|
||||||
|
return; // Nothing to see here!
|
||||||
|
|
||||||
|
if ( text[0] == '\2' ) // It's a two-line label
|
||||||
|
{
|
||||||
|
_text1 = &text[1]; // Address of 1st line
|
||||||
|
_text2 = &text[1] + strlen ( &text[1] ) + 1; // Address of line 2
|
||||||
|
}
|
||||||
|
|
||||||
|
else // Single line label
|
||||||
|
{
|
||||||
|
_text1 = text; // Only one line
|
||||||
|
_text2 = NULL; // and no second line
|
||||||
|
}
|
||||||
|
}
|
67
menu.h
Normal file
67
menu.h
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* "Menu.h" defines the classe "Menuitem", which replaces the original macros used to
|
||||||
|
* instantiate the menus in the original code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _MENU_H_ // Prevent double include
|
||||||
|
#define _MENU_H_
|
||||||
|
|
||||||
|
#include <Arduino.h> // Basic Arduino stuff
|
||||||
|
|
||||||
|
typedef void (*fptr)( int ); // Functions all take an integer argument
|
||||||
|
|
||||||
|
enum { MT_FUNC, MT_MENU, MT_CLOSE, MT_BACK, MT_END }; // Symbols for "type" of menu object
|
||||||
|
|
||||||
|
|
||||||
|
class Menuitem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Four constructors; the first just creates a placeholder instance of the object.
|
||||||
|
*
|
||||||
|
* The second fills in the "type", button label and callback function address for
|
||||||
|
* "MT_FUNC" type objects.
|
||||||
|
*
|
||||||
|
* The third constructor is used for creating "MT_MENU" type objects. Its third
|
||||||
|
* argument is a pointer to a sub-menu to be displayed.
|
||||||
|
*
|
||||||
|
* The fourth constructor is used for the "MT_BACK" and "MT_END" type objects where
|
||||||
|
* we need only the type and maybe a label, but no menu or function pointer. Note
|
||||||
|
* that the "text" argument defaults to a NULL pointer for the "MT_END" type
|
||||||
|
* object.
|
||||||
|
*/
|
||||||
|
|
||||||
|
explicit Menuitem ();
|
||||||
|
explicit Menuitem ( uint8_t type, const char* text, fptr callback );
|
||||||
|
explicit Menuitem ( uint8_t type, const char* text, Menuitem* submenu );
|
||||||
|
explicit Menuitem ( uint8_t type, const char* text = NULL );
|
||||||
|
|
||||||
|
void Call ( int num ); // Executes the callback function
|
||||||
|
|
||||||
|
const char* Text1 (); // Returns the button label - line 1
|
||||||
|
const char* Text2 (); // Returns the button label - line 2 (or NULL)
|
||||||
|
|
||||||
|
uint8_t Type (); // Returns the menu item "type"
|
||||||
|
|
||||||
|
bool isMultiline (); // Returns "true" if a multiline label
|
||||||
|
|
||||||
|
Menuitem* GetSubmenu (); // For "MT_MENU" type object, returns the sub-menu pointer
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void ParseLabel ( const char* text ); // Parse two line labels
|
||||||
|
|
||||||
|
|
||||||
|
private: // All the data is private
|
||||||
|
|
||||||
|
uint8_t _type = -1; // Menu item type (invalid default)
|
||||||
|
const char* _text1 = NULL; // Label line 1
|
||||||
|
const char* _text2 = NULL; // Label line 2
|
||||||
|
fptr _callback = NULL; // Address of callback function
|
||||||
|
Menuitem* _submenu = NULL; // Address of a sub-menu
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
300
my_SA.h
Normal file
300
my_SA.h
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
/*
|
||||||
|
* "My_SA.h"
|
||||||
|
*
|
||||||
|
* Added in Version 2.2 by John Price (WA2FZW):
|
||||||
|
*
|
||||||
|
* This file contains all the user settable parameters for the TinySA spectrum
|
||||||
|
* analyzer software. If tou change anything in any of the header files, you've
|
||||||
|
* just become a test pilot!
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _MY_SA_H_
|
||||||
|
#define _MY_SA_H_ // Prevent double inclusion
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The following definitions are all related to the WiFi interface. If you don't want
|
||||||
|
* to use the WiFi interface, set the "USE_WIFI" definition to 'false'.
|
||||||
|
*
|
||||||
|
* If you are going to use the WiFi interface, you'll need to set the "WIFI_SSID"
|
||||||
|
* and "WIFI_PASSWORD" symbols to the appropriate values for your network.
|
||||||
|
*
|
||||||
|
* Don't comment out the SSID and password definitions. Doing so will cause compiler
|
||||||
|
* errors.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define USE_WIFI true // Changed in Version 2.6 to true/false
|
||||||
|
// #define USE_ACCESS_POINT // Comment out if want to connect to SSID, leave in to use access point
|
||||||
|
|
||||||
|
#define WIFI_SSID ":)" // SSID of your WiFi if not using access point
|
||||||
|
#define WIFI_PASSWORD "S0ftR0ckRXTX" // Password for your WiFi
|
||||||
|
|
||||||
|
|
||||||
|
#define MAX_WIFI_POINTS 150 // Number of sample points to send to clients in each wifi chunk
|
||||||
|
// Some clients cannot handle too few (too frequent chart refresh)
|
||||||
|
// ** IMPORTANT ** Must be less than or equal to DISPLAY_POINTS (290)
|
||||||
|
|
||||||
|
#define WIFI_UPDATE_TARGET_TIME 500000 // No of microseconds to target chart updates.
|
||||||
|
#define WEBSOCKET_INTERVAL 2000 // Microseconds between check for websocket events if no client connected
|
||||||
|
/*
|
||||||
|
* You can set your own colors for the various traces, but you can only choose
|
||||||
|
* from the following colors:
|
||||||
|
*
|
||||||
|
* WHITE YELLOW ORANGE GREEN RED BLUE PINK LT_BLUE MAGENTA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DB_COLOR YELLOW
|
||||||
|
#define GAIN_COLOR GREEN
|
||||||
|
#define AVG_COLOR MAGENTA
|
||||||
|
#define STORAGE_COLOR LT_BLUE
|
||||||
|
|
||||||
|
|
||||||
|
#define SIG_BACKGROUND_COLOR TFT_DARKGREY
|
||||||
|
#define SLIDER_BOX_HEIGHT 8
|
||||||
|
#define SLIDER_BOX_COLOR TFT_LIGHTGREY
|
||||||
|
#define SLIDER_FILL_COLOR TFT_ORANGE
|
||||||
|
#define SLIDER_KNOB_RADIUS 15 // Sprite is twice this high
|
||||||
|
#define SLIDER_WIDTH 200 // Sprite width is this plus twice knob radius
|
||||||
|
#define SLIDER_KNOB_COLOR TFT_WHITE
|
||||||
|
#define SLIDER_X 10
|
||||||
|
#define SLIDER_Y 195
|
||||||
|
//#define SHOW_FREQ_UP_DOWN_BUTTONS // Comment out if you don't like them!
|
||||||
|
|
||||||
|
// #define SLIDER_MIN_POWER -43.0 // in dBm
|
||||||
|
// #define SLIDER_MAX_POWER -13.0
|
||||||
|
#define ATTENUATOR_RANGE 30 // in dB
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These definitions control the values that get set when you select "AUTO SETTINGS" from
|
||||||
|
* the main touch screen menu; feel free to change them as you wish, but the legal values
|
||||||
|
* for some of them won't be obvious, so do your research first!
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define AUTO_SWEEP_START 0 // Default sewwp start is 0Hz
|
||||||
|
#define AUTO_SWEEP_STOP 100000000 // Default stop is 100MHz
|
||||||
|
#define AUTO_PWR_GRID 10 // 10 dB per horizontal division
|
||||||
|
#define AUTO_LNA 0x60 // Receiver Si4432 AGC on
|
||||||
|
#define AUTO_REF_LEVEL -10 // Top line of the grid is at -10dB
|
||||||
|
#define AUTO_REF_OUTPUT 1 // Transmitter GPIO2 is 15MHz
|
||||||
|
#define AUTO_ATTEN 0 // No attenuation
|
||||||
|
#define AUTO_RBW 0 // Automatic RBW
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Some definitions for IF-Sweep to test the internal SAW filters
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define IF_SWEEP_START 432000000
|
||||||
|
#define IF_SWEEP_STOP 435000000
|
||||||
|
|
||||||
|
// Limits for keypad entry of IF sweep frequency start/stop
|
||||||
|
#define IF_STOP_MAX 500000000 // 500MHz
|
||||||
|
#define IF_START_MIN 400000000 // 400MHz
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Spur reduction shifts the IF down from its normal setting every other scan
|
||||||
|
* Set MAX_IF_SHIFT so that the IF cannot go outside the flat top of the SAW filter passband
|
||||||
|
*/
|
||||||
|
#define MAX_IF_SHIFT 300000 // Maximum shift of IF (Hz) in spur reduction mode
|
||||||
|
|
||||||
|
#define PAST_PEAK_LIMIT 3 // number of consecutive lower values to detect a peak
|
||||||
|
#define MARKER_NOISE_LIMIT 20 // Don't display marker if its RSSI value is < (min for sweep + this)
|
||||||
|
#define MARKER_MIN_FREQUENCY 750000 // Ignore values at lower frequencies as probably IF bleed through.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* There are three possible implementations for the PE4302 attenuator:
|
||||||
|
*
|
||||||
|
* The parallel version using a PCF8574 I2C GPIO expander interface to the processor
|
||||||
|
* The parallel version using six separate GPIO pins to interface with the processor
|
||||||
|
* The serial version.
|
||||||
|
*
|
||||||
|
* Change the following definition to select the version you are using. The options are:
|
||||||
|
*
|
||||||
|
* PE4302_PCF
|
||||||
|
* PE4302_GPIO
|
||||||
|
* PE4302_SERIAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define PE4302_TYPE PE4302_SERIAL
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If you're using the "PE4302_PCF" mode, you need to define the I2C address for
|
||||||
|
* the PCF8574:
|
||||||
|
*/
|
||||||
|
|
||||||
|
// #define PCF8574_ADDRESS 0x38 // Change for your device if necessary
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If you're using the "PE4302_SERIAL" mode, you need to define the chip select (LE)
|
||||||
|
* pin number:
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define PE4302_LE 21 // Chip Select for the serial attenuator
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If you're using the "PE4302_GPIO" mode, you need to define the GPIO pins used for
|
||||||
|
* the six data inputs to the PE4302:
|
||||||
|
*/
|
||||||
|
|
||||||
|
// #define DATA_0 nn // Replace the "nn" with real pin numbers
|
||||||
|
// #define DATA_1 nn
|
||||||
|
// #define DATA_2 nn
|
||||||
|
// #define DATA_3 nn
|
||||||
|
// #define DATA_4 nn
|
||||||
|
// #define DATA_5 nn
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If you're using the PE4302 attenuator, the 'ATTEN' command will set the attenuation.
|
||||||
|
* The maximum setting for the PE4302 is 31dB. If you have some other attenuator arrangement
|
||||||
|
* you can change this value appropriately so that the software can take into account
|
||||||
|
* a higher attenuation; but note only the PE4302 will actually be congigured by the code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define PE4302_MAX 31
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The Si4432 is an SPI device and we have it set up to use the ESP32's VSPI bus.
|
||||||
|
*
|
||||||
|
* Here, we define the chip select GPIO pins for the two Si4432 modules and the GPIO
|
||||||
|
* pins for the clock and data lines.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define SI_RX_CS 4 // Receiver chip select pin
|
||||||
|
#define SI_TX_CS 5 // Transmitter chip select pin
|
||||||
|
|
||||||
|
#define V_SCLK 18 // VSPI clock pin
|
||||||
|
#define V_SDI 23 // VSPI SDI (aka MOSI) pin
|
||||||
|
#define V_SDO 19 // VSPI SDO (aka MISO) pin
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* An option is add more SI4432 devices to make a tracking generator
|
||||||
|
* Options for a single one where the LO is tapped off from the TinySA LO
|
||||||
|
* or two where the track gen LO is generated with another SI4432 to give improved
|
||||||
|
* isolation and therefore better dynamic range.
|
||||||
|
*
|
||||||
|
* Fit a pull down resistor to the TinySa and and pull up to the track gen
|
||||||
|
* During power up if the pin is low then TinySA knows the track gen is not connected
|
||||||
|
* if the pin is pulled high the the trackin gen is present and the TinySA will initialise it
|
||||||
|
* and set the appropriate frequency.
|
||||||
|
*
|
||||||
|
* Comment both lines out if you will never use the tracking generator or have not fitted
|
||||||
|
* the pull down resistors
|
||||||
|
*/
|
||||||
|
#define SI_TG_IF_CS 2 // pin to use for tracking gen IF SI4432
|
||||||
|
#define SI_TG_LO_CS 25 // pin to use for tracking gen LO SI4432
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These two definitions are used to tune the Si4432 module frequencies. Once you have
|
||||||
|
* performed the frequency calibration as explained in the documentation, you should
|
||||||
|
* change the values defined here to the values you determined appropriate in the
|
||||||
|
* calibration procedure or they may be lost when new software versions are released.
|
||||||
|
*
|
||||||
|
* The values here were Erik's original values
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define TX_CAPACITANCE 0x64 // Transmitter (LO) crystal load capacitance
|
||||||
|
#define RX_CAPACITANCE 0x64 // Receiver crystal load capacitance
|
||||||
|
|
||||||
|
#define TG_LO_CAPACITANCE 0x64 // Tracking generator LO crystal load capacitance
|
||||||
|
#define TG_IF_CAPACITANCE 0x62 // Tracking generator IF crystal load capacitance
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "CAL_POWER" is the calibrated power level determined when doing the calibration
|
||||||
|
* procedure. As is the case with the crystal capacitances, you should change the
|
||||||
|
* value here to prevent the calibration from being lost when new versions of the
|
||||||
|
* code are released.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define CAL_POWER -30 // Si4432 GPIO2 normal reference level
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The default transmitter Si4432 power output is based on the type of mixer being used.
|
||||||
|
* For the ADE-1 mixer should be +7dBm and +17dBm for the ADE-25H mixer.
|
||||||
|
*
|
||||||
|
* The definition of "MIXER" can be set to one of the following symbols, or you can
|
||||||
|
* define a power setting numerically (refer to A440).
|
||||||
|
*
|
||||||
|
* MIX_ADE_1 // For the ADE-1 mixer module
|
||||||
|
* MIX_ADE_25H // For the ADE-25H mixer module
|
||||||
|
* MIX_MINIMUM // Minimum power for testing
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define MIXER MIX_ADE_25H // High power
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These define the sweep range limits. The unit is capable of sweeping up to 430MHz,
|
||||||
|
* but if you're using Glenn's PCBx, there is a 200MHz LPF on the input, so anyone
|
||||||
|
* using that (or some other input LPF) might want to change the maximum frequency
|
||||||
|
* accordingly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define STOP_MAX 250000000 // 250MHz
|
||||||
|
#define START_MIN 0 // 0MHz
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The "FOCUS_FACTOR" is used in conjunction with the "FOCUS" command (from either
|
||||||
|
* the serial interface or touch screen). The requested focus frequency is divided
|
||||||
|
* by the "FOCUS_FACTOR" to set the frequency span.
|
||||||
|
*
|
||||||
|
* So for example, if you request a focus frequency of 50MHz, and the "FOCUS_FACTOR"
|
||||||
|
* is set to 1000, 50MHz / 1000 = 25KHz which would be the total span. In other words,
|
||||||
|
* the sweep frequency range would be from 49.975MHz to 50.025MHz. But since the
|
||||||
|
* readings on the grid are only good to 2 decimal places, the display may not indicate
|
||||||
|
* the exact span.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define FOCUS_FACTOR 1000UL
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "TS_MINIMUM_Z" is the minimum touch pressure that will be considered to indicate a
|
||||||
|
* valid touch screen touch. It was originally hard-coded in the "ui.cpp" module as
|
||||||
|
* '600', but that proved to be too much for some displays, so now you can set it to
|
||||||
|
* see what works for your particular display.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define TS_MINIMUM_Z 600 // The original setting
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The "BACKLIGHT_LEVEL" setting can be used to control the brightness of the backlight
|
||||||
|
* of the TFT display. Presently, there is no place in the menu system where this can be
|
||||||
|
* adjusted, so if using the adjustable option, you have to set the symbol appropriately
|
||||||
|
*
|
||||||
|
* Glenn's PCB provides the option to simply connect the backlight to 3V3 through an
|
||||||
|
* appropriate resistor.
|
||||||
|
* Comment out if not using the PWM backlight (uses GPIO25 which can then be used for
|
||||||
|
* a tracking generator, but not both)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// #define BACKLIGHT_LEVEL 50 // Level setting
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We haven't implemented the use of a rotary encoder to control the menu selections
|
||||||
|
* yet, but if we ever do, the GPIO pins that it uses need to be defined. The ones
|
||||||
|
* defined here are based on Glenn's PCB design.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef USE_ROTARY // Not defined anywhere!
|
||||||
|
|
||||||
|
#define ENC_PB 32 // Encoder pushbutton switch
|
||||||
|
#define ENC_BB 33 // Back button pushbutton switch (or TS_INT)
|
||||||
|
#define ENC_B 34 // Encoder pin "B" (input only pin)
|
||||||
|
#define ENC_A 35 // Encoder pin "A" (input only pin)
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // #ifndef _MY_SA_H_
|
232
pE4302.cpp
Normal file
232
pE4302.cpp
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
/*
|
||||||
|
* "PE4302.cpp"
|
||||||
|
*
|
||||||
|
* Added to the "TinySA" program in Version 1.1 by John Price (WA2FZW):
|
||||||
|
* Updated in V2.5 by M0WID
|
||||||
|
*
|
||||||
|
* Functions to handle the PE4302 attenuator and support for using the PCF8574
|
||||||
|
* I2C GPIO expander to drive a parallel version of the PE4302 module.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PE4302.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a "PCF8574" object with the default I2C address
|
||||||
|
* for the basic PCF8574 (if using a PCF8574A, the default address would be
|
||||||
|
* 0x38).
|
||||||
|
*
|
||||||
|
* Please note, we also assume that the PCF8574 is on the standard I2C bus
|
||||||
|
* pins for the processor being used (21 & 22 for the ESP32; different for
|
||||||
|
* Arduinos).
|
||||||
|
*
|
||||||
|
* The address is updated by the PCF8574 constructor.
|
||||||
|
*/
|
||||||
|
|
||||||
|
PCF8574 _pcf ( 0x28 ); // Create the PCF8574 object
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The PE4302 chip can be operated in either a serial or parallel mode.
|
||||||
|
* Most pre-built modules are set for parallel but by changing some jumpers
|
||||||
|
* can be used in serial mode.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Serial interface constructor:
|
||||||
|
*
|
||||||
|
* The arguments are a pointer to the SPI class used and pin definition
|
||||||
|
* for Latch Enable "LE" pin. When "LE" is HIGH the data in the serial
|
||||||
|
* buffer is latched. "LE" must be LOW when the SPI bus is used for other
|
||||||
|
* objects (which it is).
|
||||||
|
*
|
||||||
|
* The SPI object can be shared with other objects, so is declared in the main sketch
|
||||||
|
*
|
||||||
|
* SPIClass* vspi = new SPIClass ( VSPI );
|
||||||
|
* or SPIClass* hspi = new SPIClass ( HSPI );
|
||||||
|
*
|
||||||
|
* The SPI object is initialized once in the main sketch setup, along with the
|
||||||
|
* relevant pins, e.g.:
|
||||||
|
*
|
||||||
|
* pinMode ( V_SCLK, OUTPUT ); // SPI Clock pin
|
||||||
|
* pinMode ( V_SDO, INPUT ); // SDO (MISO) pin
|
||||||
|
* pinMode ( V_SDI, OUTPUT ); // SDI (MOSI) pin
|
||||||
|
*
|
||||||
|
* digitalWrite ( V_SCLK, LOW ); // Make SPI clock LOW
|
||||||
|
* digitalWrite ( V_SDI, LOW ); // Along with MOSI
|
||||||
|
*
|
||||||
|
* vspi->begin ( V_SCLK, V_SDO, V_SDI ); // Start the VSPI: SCLK, MISO, MOSI
|
||||||
|
*/
|
||||||
|
|
||||||
|
PE4302::PE4302 ( SPIClass* spi, int le ) // Constructor for serial interface
|
||||||
|
{
|
||||||
|
_interface = S; // SPI (serial)interface
|
||||||
|
_le_Pin = le; // Enable (LE) pin number
|
||||||
|
|
||||||
|
pinMode ( _le_Pin, OUTPUT ); // Chip select pin is an output
|
||||||
|
digitalWrite ( _le_Pin, LOW ); // Deselect the module
|
||||||
|
|
||||||
|
_spi = spi; // Save SPI object pointer
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parallel GPIO interface constructor:
|
||||||
|
* arguments are the GPIO pin assignments that correspond to the "C16" to
|
||||||
|
* "C0.5" pins of the chip.
|
||||||
|
*/
|
||||||
|
|
||||||
|
PE4302::PE4302 ( int c16, int c8, int c4,
|
||||||
|
int c2, int c1, int c0 )
|
||||||
|
{
|
||||||
|
_interface = P; // Parallel interface
|
||||||
|
|
||||||
|
_parallel_Pins[0] = c0; // The actual GPIO pin numbers
|
||||||
|
_parallel_Pins[1] = c1; // can be random as opposed
|
||||||
|
_parallel_Pins[2] = c2; // to David's requirement that
|
||||||
|
_parallel_Pins[3] = c4; // they had to be consecutive.
|
||||||
|
_parallel_Pins[4] = c8; // The array order is LSB to MSB.
|
||||||
|
_parallel_Pins[5] = c16;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PCF8574 (I2C IO expander interface) constructor:
|
||||||
|
* The default address set at the beginning of the module assumes that the
|
||||||
|
* chip in use is a PCF8574. The PCF8574A may also be used, however its
|
||||||
|
* default address is 0x38, so you might need to provide a different address
|
||||||
|
* here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
PE4302::PE4302 ( int address ) // Constructor for PCF8574 interface
|
||||||
|
{
|
||||||
|
_interface = PCF; // PCF8574 interface
|
||||||
|
_pcf_I2C = address; // I2C bus address of the PCF8574
|
||||||
|
_pcf = PCF8574 ( address ); // Update the object with the address
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "PE4302_init" - Initialize the attenuator module.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void PE4302::Init ()
|
||||||
|
{
|
||||||
|
if ( _interface == S ) // If using the serial mode
|
||||||
|
{
|
||||||
|
// nothing to do!
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ( _interface == P ) // If using the parallel mode
|
||||||
|
{
|
||||||
|
for ( int i = 0; i < 6; i++ )
|
||||||
|
pinMode ( _parallel_Pins[i], OUTPUT ); // All 6 pins are outputs
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ( _interface == PCF ) // If using the PCF8574
|
||||||
|
{
|
||||||
|
_pcf.begin ( _pcf_I2C, 0 ); // Initialize the chip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "SetAtten" - Set the attenuation.
|
||||||
|
*
|
||||||
|
* The parameter is the required attenuation in dB.
|
||||||
|
* The PE4302 allows attenuation to be set in 0.5dB increments, but not here!
|
||||||
|
* If the requested attenuation is out of limits the function will return "false".
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool PE4302::SetAtten ( int8_t atten )
|
||||||
|
{
|
||||||
|
bool retCode = true; // Assume good number
|
||||||
|
|
||||||
|
if ( atten > PE4302_MAX ) // Maximum is defined in "My_SA.h"
|
||||||
|
{
|
||||||
|
atten = PE4302_MAX;
|
||||||
|
retCode = false; // Indicate bad number
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( atten < 0 ) // Can't be less than zero
|
||||||
|
{
|
||||||
|
atten = 0;
|
||||||
|
retCode = false; // Indicate bad number
|
||||||
|
}
|
||||||
|
|
||||||
|
_atten = atten << 1; // Double the number and save it
|
||||||
|
|
||||||
|
if ( _interface == S ) // If using the serial mode
|
||||||
|
{
|
||||||
|
// Serial.printf ( "SetAtten %i - clk:%i data:%i LE:%i \n",
|
||||||
|
// _atten, _clock_Pin, _data_Pin, _le_Pin);
|
||||||
|
|
||||||
|
uint32_t oldDivider = _spi->getClockDivider(); // run at 1MHz
|
||||||
|
|
||||||
|
_spi->setFrequency(1000000); // run at 1MHz
|
||||||
|
digitalWrite ( _le_Pin, LOW ); // Make the sure the attenuator is not using the shifted in data
|
||||||
|
_spi->transfer ( _atten ); // Send the attenution value bit pattern
|
||||||
|
|
||||||
|
digitalWrite ( _le_Pin, HIGH ); // Latch Enable pin HIGH
|
||||||
|
digitalWrite ( _le_Pin, LOW ); // Then immediately LOW
|
||||||
|
_spi->setClockDivider(oldDivider); // back to whatever speed it was running before
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ( _interface == P ) // If using the parallel mode
|
||||||
|
{
|
||||||
|
for ( int i = 0; i < 6; i++ )
|
||||||
|
digitalWrite ( _parallel_Pins[i], _atten & ( 1 << i ));
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ( _interface == PCF ) // If using the PCF8574
|
||||||
|
{
|
||||||
|
_pcf.write8 ( _atten );
|
||||||
|
}
|
||||||
|
|
||||||
|
return retCode; // Send back good/bad indication
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return current attenuation value.
|
||||||
|
* Bit pattern has to be divided by two to return dB
|
||||||
|
*/
|
||||||
|
|
||||||
|
int PE4302::GetAtten () // Send back stored attenuation
|
||||||
|
{
|
||||||
|
return _atten >> 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These functions implement the stripped down PCF8574 library:
|
||||||
|
*/
|
||||||
|
|
||||||
|
PCF8574::PCF8574 ( const uint8_t deviceAddress )
|
||||||
|
{
|
||||||
|
_address = deviceAddress;
|
||||||
|
_dataOut = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The "begin" function is modified from the original to allow us to change the
|
||||||
|
* I2C address as well as set an inital output value:
|
||||||
|
*/
|
||||||
|
|
||||||
|
void PCF8574::begin ( const uint8_t deviceAddress, uint8_t val )
|
||||||
|
{
|
||||||
|
_address = deviceAddress; // Save address
|
||||||
|
Wire.begin (); // Initialize the I2C interface
|
||||||
|
PCF8574::write8 ( val ); // Output the initial value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PCF8574::write8 ( const uint8_t value )
|
||||||
|
{
|
||||||
|
_dataOut = value; // Save value to be output
|
||||||
|
Wire.beginTransmission ( _address ); // Start the transmission process
|
||||||
|
Wire.write ( _dataOut ); // Send the data
|
||||||
|
Wire.endTransmission (); // Wasn't in original library
|
||||||
|
}
|
88
pE4302.h
Normal file
88
pE4302.h
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* "PE4302.h"
|
||||||
|
*
|
||||||
|
* Added to the "TinySA" program in Version 1.1 by John Price (WA2FZW):
|
||||||
|
*
|
||||||
|
* This is the header file for the PE4302 class.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef PE4302_H_
|
||||||
|
#define PE4302_H_ // Prevent double inclusion
|
||||||
|
|
||||||
|
#include <Arduino.h> // General Arduino definitions
|
||||||
|
#include <Wire.h> // I2C library
|
||||||
|
#include <SPI.h> // Serial Peripheral Interface library
|
||||||
|
#include "tinySA.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In order to save GPIO pins on the ESP32, Glenn (VK3PE) and I decided to use a
|
||||||
|
* PCF8574 GPIO expander chip between the processor and the parallel version of
|
||||||
|
* of the PE4302 attenuator. We know some PE4302 modules are available with a
|
||||||
|
* serial interface, but the ones we happen to have only support the parallel
|
||||||
|
* interface. This lets us control it over the I2C bus.
|
||||||
|
*
|
||||||
|
* It's not clear to me that the native serial interface to the serial module
|
||||||
|
* is an I2C interface, but we will also support that via a bit-banging techique.
|
||||||
|
* We will also support the parallel interface module without the PCF8574.
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum { PCF, P, S }; // Symbols for operational modes
|
||||||
|
|
||||||
|
class PE4302
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function prototypes for the PE4302 available to the outside world:
|
||||||
|
*
|
||||||
|
* Constructors:
|
||||||
|
*/
|
||||||
|
|
||||||
|
explicit PE4302 ( SPIClass* spi, int le ); // For serial interface
|
||||||
|
explicit PE4302 ( int address ); // For PCF8574 interface
|
||||||
|
explicit PE4302 ( int c16, int c8, int c4, // For parallel interface
|
||||||
|
int c2, int c1, int c0 );
|
||||||
|
|
||||||
|
void Init (); // Initialize the module
|
||||||
|
bool SetAtten ( int8_t atten ); // Set the attenuation
|
||||||
|
int GetAtten (); // Get the current attenuation
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
uint8_t _pcf_I2C; // I2C address for the PCF8574
|
||||||
|
|
||||||
|
uint8_t _interface; // Interface type ( PCF, P or S )
|
||||||
|
|
||||||
|
uint8_t _le_Pin; // Enable (LE) pin for serial or parallel interdace
|
||||||
|
|
||||||
|
uint8_t _parallel_Pins[6]; // Pin numbers ( c0 - c16 ) for parallel interface
|
||||||
|
|
||||||
|
int16_t _atten; // Current attenuator setting ( 0 - 31 dB ) x 2
|
||||||
|
SPIClass* _spi; // Pointer to the SPI object used for serial mode
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is a stripped down version of the PCF8574 library. We only need the
|
||||||
|
* constructor, the "begin" function (slightly modified from the original
|
||||||
|
* library), the "write8" functions and a couple of the variables.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PCF8574
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit PCF8574 ( const uint8_t deviceAddress );
|
||||||
|
void begin ( const uint8_t deviceAddress, uint8_t val );
|
||||||
|
void write8 ( const uint8_t value );
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
uint8_t _address; // PCF8574 I2C address
|
||||||
|
uint8_t _dataOut; // Data to be sent
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
325
preferences.cpp
Normal file
325
preferences.cpp
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
/*
|
||||||
|
* "preferences.cpp"
|
||||||
|
*
|
||||||
|
* This file has the functions that write and read the "config" and "setting"
|
||||||
|
* structures to and from the flash memory on the ESP32; similar to how EEPROM
|
||||||
|
* works on the Arduinos.
|
||||||
|
*
|
||||||
|
* The starting point for this version is the "tinySA_touch02" software developed
|
||||||
|
* by Dave (M0WID). That software is based on the original version by Erik Kaashoek.
|
||||||
|
*
|
||||||
|
* Modified by John Price (WA2FZW):
|
||||||
|
*
|
||||||
|
* Version 1.0 - Just add comments and try to figure out how it all works!
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Arduino.h" // Basic Arduino definitions
|
||||||
|
#include <Preferences.h> // Preferences library header
|
||||||
|
#include "preferences.h" // Our function prototypes
|
||||||
|
#include "tinySA.h" // General program-wide definitions and stuff
|
||||||
|
|
||||||
|
extern Preferences preferences; // The Preferences object - Created in the main file
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "ReadConfig" - Reads the "config" (see "tinySA.h") structure from flash memory
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ReadConfig ()
|
||||||
|
{
|
||||||
|
config_t tempConfig; // Temporary store to check data is valid
|
||||||
|
size_t bytes; // Amount of data
|
||||||
|
|
||||||
|
bytes = preferences.getBytes ( "config", &tempConfig, sizeof (tempConfig ));
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the "magic" entry is zero or what we read is the wrong size, the data is invalid.
|
||||||
|
*
|
||||||
|
* If nothing has yet been saved then nothing is retrieved and default values are used
|
||||||
|
*
|
||||||
|
* It might be better if "magic" was a specific number or maybe even a short string.
|
||||||
|
*
|
||||||
|
* If the size isn't correct, it could be that the size of the "config" structure was
|
||||||
|
* changed in a newer release of the code.
|
||||||
|
*
|
||||||
|
* If what we read is invalid, we store the default values in the flash memory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (( tempConfig.magic == 0 ) || ( bytes != sizeof ( tempConfig )))
|
||||||
|
{
|
||||||
|
Serial.printf ( "Bytes got = %i - aiming for %i. No config saved - Storing default values\n",
|
||||||
|
bytes, sizeof ( tempConfig ));
|
||||||
|
|
||||||
|
preferences.remove ( "config" ); // Clear any old data just in case size has changed
|
||||||
|
WriteConfig ();
|
||||||
|
}
|
||||||
|
|
||||||
|
else // Valid data was retrieved
|
||||||
|
{
|
||||||
|
// Serial.println ( "config retrieved" );
|
||||||
|
config = tempConfig; // Copy retrieved values to the real structure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "WriteConfig" - Writes the "config" structure to the flash memory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void WriteConfig ()
|
||||||
|
{
|
||||||
|
size_t bytes;
|
||||||
|
|
||||||
|
bytes = preferences.putBytes ( "config", &config, sizeof ( config ));
|
||||||
|
|
||||||
|
if ( bytes == 0 ) // Writing failed
|
||||||
|
Serial.println ( "Save of config failed" );
|
||||||
|
|
||||||
|
else
|
||||||
|
Serial.println ( "config saved" );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "ReadSettings" - Reads the "setting" structure from the flash memory
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ReadSettings()
|
||||||
|
{
|
||||||
|
settings_t tempSettings; // Temporary store to check data is valid
|
||||||
|
size_t bytes; // Amount of data read
|
||||||
|
|
||||||
|
bytes = preferences.getBytes ( "Settings", &tempSettings, sizeof ( tempSettings ));
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the "PowerGrid" entry is zero or what we read is the wrong size, the data is invalid.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* It might be better if we included a "magic" element that is a specific number or maybe
|
||||||
|
* even a short string.
|
||||||
|
*
|
||||||
|
* If the size isn't correct, it could be that the size of the "setting" structure was
|
||||||
|
* changed in a newer release of the code.
|
||||||
|
*
|
||||||
|
* If what we read is invalid, we store the default values in the flash memory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (( tempSettings.PowerGrid == 0 ) || ( bytes != sizeof ( tempSettings )))
|
||||||
|
{
|
||||||
|
Serial.printf ( "Bytes got = %i - aiming for %i. No Settings saved - Storing default values\n",
|
||||||
|
bytes, sizeof (tempSettings ));
|
||||||
|
|
||||||
|
preferences.remove ( "Settings" ); // Clear any old data just in case size has changed
|
||||||
|
WriteSettings (); // Write default values
|
||||||
|
}
|
||||||
|
|
||||||
|
else // Data retrieved looks valid
|
||||||
|
{
|
||||||
|
// Serial.println ( "Settings retrieved" );
|
||||||
|
setting = tempSettings; // Copy retrieved values to the real structure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "WriteSettings" - Writes the contents of the "setting" structure to the flash memory
|
||||||
|
*/
|
||||||
|
|
||||||
|
void WriteSettings ()
|
||||||
|
{
|
||||||
|
size_t bytes;
|
||||||
|
|
||||||
|
bytes = preferences.putBytes ( "Settings", &setting, sizeof ( setting ));
|
||||||
|
|
||||||
|
|
||||||
|
if ( bytes == 0 ) // WA2FZW - Should we compare to expected size?
|
||||||
|
Serial.println ( "Save of Settings failed" );
|
||||||
|
|
||||||
|
else
|
||||||
|
Serial.println ( "Settings saved" );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "Save" and "Recall" were added in Version 2.1 by WA2FZW.
|
||||||
|
*
|
||||||
|
* I reactivated the "Save" and "Recall" menu items on the touch screen so
|
||||||
|
* the user can save up to five scan configurations! Those commands will also
|
||||||
|
* be added to the serial command handler.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Save ( uint8_t loc )
|
||||||
|
{
|
||||||
|
char saveName[10];
|
||||||
|
|
||||||
|
sprintf ( saveName, "Save%d", loc );
|
||||||
|
|
||||||
|
if (( loc < 0 ) || ( loc > 4 ))
|
||||||
|
{
|
||||||
|
Serial.printf ( "Illegal location specification: %u\n", loc );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serial.print ( "saveName = " ); Serial.println ( saveName );
|
||||||
|
|
||||||
|
size_t bytes;
|
||||||
|
|
||||||
|
bytes = preferences.putBytes ( saveName, &setting, sizeof ( setting ));
|
||||||
|
|
||||||
|
if ( bytes == 0 ) // WA2FZW - Should we compare to expected size?
|
||||||
|
Serial.printf ( "Failed to save'%s'\n", saveName );
|
||||||
|
|
||||||
|
else
|
||||||
|
Serial.printf ( "Settings saved to '%s'\n", saveName );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Recall ( uint8_t loc )
|
||||||
|
{
|
||||||
|
char saveName[10]; // Place to construct the name of the data to recall
|
||||||
|
size_t bytes; // Number of bytes read from the flash
|
||||||
|
settings_t tempSettings; // Temporary store to check data is valid
|
||||||
|
|
||||||
|
if (( loc < 0 ) || ( loc > 4 ))
|
||||||
|
{
|
||||||
|
Serial.printf ( "Illegal location specification: %u\n", loc );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf ( saveName, "Save%d", loc ); // Construct the name of the data to be recalled
|
||||||
|
|
||||||
|
// Serial.print ( "saveName = " ); Serial.println ( saveName );
|
||||||
|
|
||||||
|
bytes = preferences.getBytes ( saveName, &tempSettings, sizeof ( tempSettings ));
|
||||||
|
|
||||||
|
if ( bytes != sizeof ( tempSettings )) // Incorrect amount of data read
|
||||||
|
Serial.printf ( "No data stored for '%s'\n", saveName );
|
||||||
|
|
||||||
|
else // Successful read!
|
||||||
|
{
|
||||||
|
Serial.printf ( "Settings recalled from '%s'\n", saveName );
|
||||||
|
setting = tempSettings; // Copy retrieved values to the real structure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "ReadSettings" - Reads the "setting" structure from the flash memory
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ReadSigGenSettings()
|
||||||
|
{
|
||||||
|
sigGenSettings_t tempSettings; // Temporary store to check data is valid
|
||||||
|
size_t bytes; // Amount of data read
|
||||||
|
|
||||||
|
bytes = preferences.getBytes ( "SigGenLo", &tempSettings, sizeof ( tempSettings ));
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the "PowerGrid" entry is zero or what we read is the wrong size, the data is invalid.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* It might be better if we included a "magic" element that is a specific number or maybe
|
||||||
|
* even a short string.
|
||||||
|
*
|
||||||
|
* If the size isn't correct, it could be that the size of the "setting" structure was
|
||||||
|
* changed in a newer release of the code.
|
||||||
|
*
|
||||||
|
* If what we read is invalid, we store the default values in the flash memory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (( tempSettings.Dummy != 123 ) || ( bytes != sizeof ( tempSettings )))
|
||||||
|
{
|
||||||
|
Serial.printf ( "Bytes got = %i - aiming for %i. No Sig Gen Settings saved - Storing default values\n",
|
||||||
|
bytes, sizeof (tempSettings ));
|
||||||
|
|
||||||
|
preferences.remove ( "SigGenLo" ); // Clear any old data just in case size has changed
|
||||||
|
WriteSigGenSettings (); // Write default values
|
||||||
|
}
|
||||||
|
|
||||||
|
else // Data retrieved looks valid
|
||||||
|
{
|
||||||
|
// Serial.println ( "SigGenLo Settings retrieved" );
|
||||||
|
sigGenSetting = tempSettings; // Copy retrieved values to the real structure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "WriteSettings" - Writes the contents of the "setting" structure to the flash memory
|
||||||
|
*/
|
||||||
|
|
||||||
|
void WriteSigGenSettings ()
|
||||||
|
{
|
||||||
|
size_t bytes;
|
||||||
|
|
||||||
|
bytes = preferences.putBytes ( "SigGenLo", &sigGenSetting, sizeof ( sigGenSetting ));
|
||||||
|
|
||||||
|
|
||||||
|
if ( bytes == 0 )
|
||||||
|
Serial.println ( "Save of sigGenLo failed" );
|
||||||
|
else
|
||||||
|
Serial.println ( "sigGenLo saved" );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "ReadSettings" - Reads the "setting" structure from the flash memory
|
||||||
|
*/
|
||||||
|
|
||||||
|
void ReadTrackGenSettings()
|
||||||
|
{
|
||||||
|
trackGenSettings_t tempSettings; // Temporary store to check data is valid
|
||||||
|
size_t bytes; // Amount of data read
|
||||||
|
|
||||||
|
bytes = preferences.getBytes ( "TrackGen", &tempSettings, sizeof ( tempSettings ));
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the "dummy" entry is zero or what we read is the wrong size, the data is invalid.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* It might be better if we included a "magic" element that is a specific number or maybe
|
||||||
|
* even a short string.
|
||||||
|
*
|
||||||
|
* If the size isn't correct, it could be that the size of the "setting" structure was
|
||||||
|
* changed in a newer release of the code.
|
||||||
|
*
|
||||||
|
* If what we read is invalid, we store the default values in the flash memory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (( tempSettings.Dummy != 99 ) || ( bytes != sizeof ( tempSettings )))
|
||||||
|
{
|
||||||
|
Serial.printf ( "Bytes got = %i - aiming for %i. No Track Gen Settings saved - Storing default values\n",
|
||||||
|
bytes, sizeof (tempSettings ));
|
||||||
|
|
||||||
|
preferences.remove ( "TrackGen" ); // Clear any old data just in case size has changed
|
||||||
|
WriteTrackGenSettings (); // Write default values
|
||||||
|
}
|
||||||
|
|
||||||
|
else // Data retrieved looks valid
|
||||||
|
{
|
||||||
|
// Serial.println ( "Track Settings retrieved" );
|
||||||
|
trackGenSetting = tempSettings; // Copy retrieved values to the real structure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "WriteTrackGenSettings" - Writes the contents of the "trackGenSetting" structure to the flash memory
|
||||||
|
*/
|
||||||
|
|
||||||
|
void WriteTrackGenSettings ()
|
||||||
|
{
|
||||||
|
size_t bytes;
|
||||||
|
|
||||||
|
bytes = preferences.putBytes ( "TrackGen", &trackGenSetting, sizeof ( trackGenSetting ));
|
||||||
|
|
||||||
|
|
||||||
|
if ( bytes == 0 )
|
||||||
|
Serial.println ( "Save of TrackGen failed" );
|
||||||
|
else
|
||||||
|
Serial.println ( "TrackGen saved" );
|
||||||
|
}
|
37
preferences.h
Normal file
37
preferences.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* "preferences.h"
|
||||||
|
*
|
||||||
|
* This file has the prototype functions for "preferences.cpp". Those functions
|
||||||
|
* write and read the "config" and "setting" structures to and from the flash
|
||||||
|
* memory on the ESP32; similar to how EEPROM works on the Arduinos.
|
||||||
|
*
|
||||||
|
* The starting point for this version is the "tinySA_touch02" software developed
|
||||||
|
* by Dave (M0WID). That software is based on the original version by Erik Kaashoek.
|
||||||
|
*
|
||||||
|
* Modified by John Price (WA2FZW):
|
||||||
|
*
|
||||||
|
* Version 1.0 - Just add comments and try to figure out how it all works!
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "tinySA.h" // Definitions for the entire program
|
||||||
|
|
||||||
|
extern config_t config; // Default colors, touch screen calibration, etc.
|
||||||
|
extern settings_t setting; // Scan limits and other scan parameters
|
||||||
|
extern sigGenSettings_t sigGenSetting; // parameters for sig gen mode
|
||||||
|
extern trackGenSettings_t trackGenSetting; // parameters for tracking gen mode
|
||||||
|
|
||||||
|
|
||||||
|
extern void ReadConfig ();
|
||||||
|
extern void WriteConfig ();
|
||||||
|
|
||||||
|
extern void ReadSettings ();
|
||||||
|
extern void WriteSettings ();
|
||||||
|
|
||||||
|
extern void ReadSigGenSettings ();
|
||||||
|
extern void WriteSigGenSettings ();
|
||||||
|
|
||||||
|
extern void ReadTrackGenSettings ();
|
||||||
|
extern void WriteTrackGenSettings ();
|
||||||
|
|
||||||
|
extern void Save ( uint8_t loc );
|
||||||
|
extern void Recall ( uint8_t loc );
|
824
si4432.cpp
Normal file
824
si4432.cpp
Normal file
@ -0,0 +1,824 @@
|
|||||||
|
/*
|
||||||
|
* "Si4432.cpp"
|
||||||
|
*
|
||||||
|
* Added to the "TinySA" program in Version 1.7 by John Price (WA2FZW):
|
||||||
|
*
|
||||||
|
* Functions to handle the Si4432 transceivers as an objects. Some of the
|
||||||
|
* functions in here apply only to the receiver and some apply only to the
|
||||||
|
* transmitter. The "Init" functions remember what type of module (transmitter
|
||||||
|
* or receiver) is being initialized.
|
||||||
|
*
|
||||||
|
* For further information on the register settings, please refer to the
|
||||||
|
* "Silicon Labs Si4430/31/32 - B1" data sheet and "Silicon Labs Technical
|
||||||
|
* Note A440".
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <SPI.h> // Serial Peripheral Interface library
|
||||||
|
#include "Si4432.h" // Our header file
|
||||||
|
#include <esp32-hal-spi.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The "bandpassFilters" array contains a selection of the standard bandpass settings.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
bandpassFilter_t Si4432::_bandpassFilters[]
|
||||||
|
{
|
||||||
|
// bw*10, settle, dwn3, ndec, filset
|
||||||
|
{ 26, 7500, 0, 5, 1 }, // 0 "AUTO" selection possibility
|
||||||
|
{ 31, 7000, 0, 5, 3 }, // 1 If user selects 3KHz -> 3.1KHz actual
|
||||||
|
{ 59, 3700, 0, 4, 3 }, // 2 "AUTO" selection possibility
|
||||||
|
{ 106, 2500, 0, 3, 2 }, // 3 If user selects 10KHz -> 10.6KHz actual
|
||||||
|
{ 322, 1000, 0, 2, 6 }, // 4 If user selects 30KHz -> 32.2KHz actual
|
||||||
|
{ 377, 1000, 0, 1, 1 }, // 5 "AUTO" selection possibility
|
||||||
|
{ 562, 700, 0, 1, 5 }, // 6 "AUTO" selection possibility
|
||||||
|
{ 832, 600, 0, 0, 2 }, // 7 "AUTO" selection possibility
|
||||||
|
{ 1121, 500, 0, 0, 5 }, // 8 If user selects 100KHz -> 112.1KHz actual
|
||||||
|
{ 1811, 500, 1, 1, 9 }, // 9 "AUTO" selection possibility
|
||||||
|
{ 2488, 450, 1, 0, 2 }, // 10 "AUTO" selection possibility
|
||||||
|
{ 3355, 400, 1, 0, 8 }, // 11 If user selects 300KHz -> 335.5KHz actual
|
||||||
|
{ 3618, 300, 1, 0, 9 }, // 12 "AUTO" selection possibility
|
||||||
|
{ 4685, 300, 1, 0, 11 }, // 13 "AUTO" selection possibility
|
||||||
|
{ 6207, 300, 1, 0, 14 } // 14 "AUTO" selection possibility
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t Si4432::_bpfCount = ELEMENTS ( Si4432::_bandpassFilters ); // Number of entries in the array
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The constructor takes two arguments which are the pointer to the SPI object
|
||||||
|
* and "Chip Select" pin for the module.
|
||||||
|
*
|
||||||
|
* The SPI object can be shared with other objects, so is declared in the main sketch as
|
||||||
|
*
|
||||||
|
* SPIClass* vspi = new SPIClass ( VSPI );
|
||||||
|
* or SPIClass* hspi = new SPIClass ( HSPI );
|
||||||
|
*
|
||||||
|
* The SPI object is initialized once in the main sketch setup, along with the relevant pins:
|
||||||
|
*
|
||||||
|
* pinMode ( V_SCLK, OUTPUT ); // SPI Clock pin
|
||||||
|
* pinMode ( V_SDO, INPUT ); // SDO (MISO) pin
|
||||||
|
* pinMode ( V_SDI, OUTPUT ); // SDI (MOSI) pin
|
||||||
|
*
|
||||||
|
* digitalWrite ( V_SCLK, LOW ); // Make SPI clock LOW
|
||||||
|
* digitalWrite ( V_SDI, LOW ); // Along with MOSI
|
||||||
|
*
|
||||||
|
* vspi->begin ( V_SCLK, V_SDO, V_SDI ); // Start the VSPI: SCLK, MISO, MOSI
|
||||||
|
* vspi->setFrequency(10000000); // Max speed according to datasheet
|
||||||
|
*/
|
||||||
|
|
||||||
|
Si4432::Si4432 ( SPIClass* spi, uint8_t cs, uint8_t id ) // Constructor (argument is chip select pin number)
|
||||||
|
{
|
||||||
|
_cs = cs; // Remember the chip select pin number
|
||||||
|
_bw = 3355; // Set bandwidth to 335.5KHz for now
|
||||||
|
_dt = 400; // Proper delay time for wide bandwidth
|
||||||
|
_spi = spi; // Pointer to SPI object
|
||||||
|
_type = id;
|
||||||
|
|
||||||
|
pinMode ( _cs, OUTPUT ); // Chip select pin is an output
|
||||||
|
digitalWrite ( _cs, HIGH ); // Deselect the module
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* There are two different initialization functions, as there are obviously some
|
||||||
|
* differences in how you set up the transmitter and receiver modules.
|
||||||
|
*
|
||||||
|
* ** Modified for version 3.0e by M0WID to be one Init as the SI4432 can be used
|
||||||
|
* for either RX aor TX depending on mode. All SI4432 are set as RX to start with
|
||||||
|
* with no GPIO2 reference output
|
||||||
|
*
|
||||||
|
* The "SubInit" function takes care of the register settings common to both.
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool Si4432::Init ( uint8_t cap )
|
||||||
|
{
|
||||||
|
|
||||||
|
if ( !Reset () ) // Reset the module
|
||||||
|
return false; // If that failed
|
||||||
|
SubInit (); // Things common to both modules
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* We turn on receive mode ("RXON"), the PLL ("PLLON") and ready mode
|
||||||
|
* ("XTON"). The Si4432 module does not have the 32.768 kHz crystal for
|
||||||
|
* the microcontroller, so we do not turn on the "X32KSEL" bit.
|
||||||
|
*
|
||||||
|
* The initial frequency is set to 433.92 MHz which is a typical IF
|
||||||
|
* Finally the GPIO-2 pin is set to ground.
|
||||||
|
*
|
||||||
|
* 03/24 - Logic verified against A440 register edscription document
|
||||||
|
*/
|
||||||
|
|
||||||
|
WriteByte ( REG_OFC1, ( RXON | PLLON | XTON )); // Receiver, PLL and "Ready Mode" all on
|
||||||
|
Tune ( cap ); // Set the crystal capacitance to fine tune frequency
|
||||||
|
|
||||||
|
SetFrequency ( 443920000 ); // 443.92MHz
|
||||||
|
delayMicroseconds ( 300 ); // Time to allow the SI4432 state machine to do its stuff
|
||||||
|
Serial.printf ( "End of Init - _cs = %i\n", _cs );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//bool Si4432::TX_Init ( uint8_t cap, uint8_t power )
|
||||||
|
//{
|
||||||
|
// _type = TX_4432; // We're a transmitter
|
||||||
|
//
|
||||||
|
// if ( !Reset () ) // Reset the module
|
||||||
|
// return false; // If that failed
|
||||||
|
// _pwr = power; // Set the transmitter power level
|
||||||
|
// SubInit (); // Things common to both modules
|
||||||
|
//
|
||||||
|
//
|
||||||
|
///*
|
||||||
|
// * Settings specific to the transmitter module.
|
||||||
|
// *
|
||||||
|
// * This is almost identical to how we set up the receiver except we turn
|
||||||
|
// * on the "TXON" bit (0x08) instead of the "RXON" bit. We also set the
|
||||||
|
// * transmitter power based on the mixer module being used. ("CalcPower"
|
||||||
|
// * function sets the value).
|
||||||
|
// *
|
||||||
|
// * GPIO-2 is handled differently; here, we set GPIO-2 to output the
|
||||||
|
// * microcontroller clock at maximum drive level (0xC0). We also set the
|
||||||
|
// * microcontroller clock speed to 10MHz.
|
||||||
|
// *
|
||||||
|
// * 03/24 - Logic verified against A440
|
||||||
|
// */
|
||||||
|
//
|
||||||
|
// Tune ( cap ); // Set the crystal capacitance
|
||||||
|
// WriteByte ( REG_TXPWR, _pwr ); // Power based on mixer in use
|
||||||
|
//
|
||||||
|
// WriteByte ( REG_GPIO2, 0xC0 ); // Maximum drive and microcontroller clock output
|
||||||
|
// WriteByte ( REG_MCOC, 0x02 ); // Set 10MHz clock output
|
||||||
|
//
|
||||||
|
// SetFrequency ( 443920000 ); // 433.92MHz (setting.IF_Freq???)
|
||||||
|
//
|
||||||
|
// delayMicroseconds ( 300 ); // Doesn't work without this!
|
||||||
|
//
|
||||||
|
// WriteByte ( REG_OFC1, ( TXON | PLLON | XTON )); // Transmitter, PLL and "Ready Mode" all on
|
||||||
|
//
|
||||||
|
//// Serial.println ( "End of TX_Init" );
|
||||||
|
//
|
||||||
|
// return true;
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "SubInit" is used by the "Init" function to set up
|
||||||
|
* all the registers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Si4432::SubInit ()
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "REG_FBS" is the frequency band select register. We select upper sideband
|
||||||
|
* (0x40) and the band is set to (0x06). What that means is dependent on the
|
||||||
|
* setting of the "HBSEL" bit (0x10), which is set to low band here.
|
||||||
|
*
|
||||||
|
* As near as I can tell from A440, this says we will be tuning in a range of
|
||||||
|
* 300 to 310MHz.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// WriteByte ( REG_FBS, 0x46 ); // Select high sideband & low freq range?
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The following two instructions seem to set the carrier frequency to zero
|
||||||
|
* per A440. The setting of "REG_NFC1" to 0x62 was commented out in the
|
||||||
|
* original code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// WriteByte ( REG_NCF1, 0x62 ); // Nominal Carrier Frequency 1
|
||||||
|
// WriteByte ( REG_NCF1, 0x00 ); // WE USE 433.92 MHz
|
||||||
|
// WriteByte ( REG_NCF0, 0x00 ); // Nominal Carrier Frequency 0
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the receiver modem IF bandwidth. In the original code, the bandwidth
|
||||||
|
* was set to 37.3KHz (0x81); I changed it to 335.5KHz (maximum for the
|
||||||
|
* application).
|
||||||
|
*/
|
||||||
|
|
||||||
|
// WriteByte ( REG_IFBW, 0x81 ); // RX Modem IF bandwidth (original value)
|
||||||
|
WriteByte ( REG_IFBW, 0x18 ); // IF bandwidth (for 335.5 KHz)
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the AFC loop gearshift override to minimum, turn off AFC
|
||||||
|
*/
|
||||||
|
|
||||||
|
WriteByte ( REG_AFCGSO, 0x00 ); // AFC Loop Gearshift Override
|
||||||
|
WriteByte ( REG_AFCTC, 0x02 ); // AFC Timing Control
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the "Clock Recovery Gearshift Value". The original code set it to 0x00,
|
||||||
|
* however, the recommended value from A440 is 0x05.
|
||||||
|
* We are not sending data so have no need to syncronise clocks between Tx and Rx
|
||||||
|
* so just leave at the default value
|
||||||
|
*/
|
||||||
|
|
||||||
|
WriteByte ( REG_CRGO, 0x03 ); // Recommended value from A440
|
||||||
|
|
||||||
|
|
||||||
|
// WriteByte ( REG_CROSR, 0x78 ); // Clock Recovery Oversampling Ratio
|
||||||
|
WriteByte ( REG_CRO2, 0x01 ); // Clock Recovery Offset 2
|
||||||
|
WriteByte ( REG_CRO1, 0x11 ); // Clock Recovery Offset 1
|
||||||
|
WriteByte ( REG_CRO0, 0x11 ); // Clock Recovery Offset 0
|
||||||
|
WriteByte ( REG_CRTLG1, 0x01 ); // Clock Recovery Timing Loop Gain 1
|
||||||
|
WriteByte ( REG_CRTLG0, 0x13 ); // Clock Recovery Timing Loop Gain 0
|
||||||
|
|
||||||
|
WriteByte ( REG_AFCLIM, 0xFF ); // AFC Limiter - Maximum
|
||||||
|
|
||||||
|
WriteByte ( REG_OOKC1, 0x28 ); // OOK Counter Value 1
|
||||||
|
WriteByte ( REG_OOKC2, 0x0C ); // OOK Counter Value 2
|
||||||
|
WriteByte ( REG_SPH, 0x28 ); // OOK Attack & Decay settings
|
||||||
|
|
||||||
|
WriteByte ( REG_DATAC, 0x61 ); // Disable packet handling
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The original code had all these choices for what to put into the "REG_AGCOR1"
|
||||||
|
* register (0x69) to control the LNA and pre-amp. Pick only one.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// WriteByte ( REG_AGCOR1, 0x00 ); // No AGC, min LNA
|
||||||
|
// WriteByte ( REG_AGCOR1, LNAGAIN ); // No AGC, max LNA of 20dB
|
||||||
|
// WriteByte ( REG_AGCOR1, AGCEN ); // AGC enabled, min LNA
|
||||||
|
WriteByte ( REG_AGCOR1, 0x60 ); // AGC, min LNA, Gain increase during signal reductions
|
||||||
|
// WriteByte ( REG_AGCOR1, 0x30 ); // AGC, max LNA
|
||||||
|
// WriteByte ( REG_AGCOR1, 0x70 ); // AGC, max LNA, Gain increase during signal reductions
|
||||||
|
|
||||||
|
WriteByte ( REG_GPIO0, 0x12 ); // GPIO-0 TX State (output)
|
||||||
|
WriteByte ( REG_GPIO1, 0x15 ); // GPIO-1 RX State (output)
|
||||||
|
|
||||||
|
WriteByte ( REG_GPIO2, 0x1F ); // Set GPIO-2 output to ground until needed
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "Reset" - Initializes the Si4432.
|
||||||
|
*
|
||||||
|
* We write the "SW_RESET" (0x80) bit in the "REG_OFC1" register (0x07).
|
||||||
|
* Doing so resets all the registers to their default values. The process
|
||||||
|
* is complete when the "ICHIPRDY" bit (0x02) in the "REG_IS2" register
|
||||||
|
* (0x04) goes high.
|
||||||
|
*
|
||||||
|
* We will try reading the ready bit 100 times with a slight pause between
|
||||||
|
* tries. When it goes high, we're done.
|
||||||
|
*
|
||||||
|
* 03/24 - Logic verified against A440
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool Si4432::Reset ()
|
||||||
|
{
|
||||||
|
uint32_t count = 0;
|
||||||
|
uint32_t startTime = millis ();
|
||||||
|
uint8_t regRead = 0;
|
||||||
|
const char* unit[4] = { "RX", "TX", "TGIF", "TGLO" }; // For debugging
|
||||||
|
|
||||||
|
WriteByte ( REG_OFC1, SW_RESET ); // Always perform a system reset
|
||||||
|
|
||||||
|
while ( millis() - startTime < 10000 ) // Try for 10 seconds
|
||||||
|
{
|
||||||
|
regRead = ReadByte ( REG_IS2 );
|
||||||
|
|
||||||
|
if ( ( regRead & ICHIPRDY ) && ( regRead != 0xFF ) ) // Wait for chip ready bit
|
||||||
|
{
|
||||||
|
Serial.printf ( " Si4432 Good to go - regRead = %02X _cs = %i\n", regRead, _cs );
|
||||||
|
return true; // Good to go!
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we don't have "ICHIPRDY" yet, only display the error message once per second.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ((( millis () - startTime ) % 1000 ) == 0 )
|
||||||
|
{
|
||||||
|
// Serial.print ( "Waiting for " );
|
||||||
|
// Serial.print ( unit[_type] );
|
||||||
|
// Serial.print ( " Si4432 - regRead " );
|
||||||
|
// Serial.println ( regRead );
|
||||||
|
// Serial.printf ( "Type %X Version %X Status %X \n", ReadByte(0), ReadByte(1), ReadByte(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
delay ( 1 ); // Slight pause
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf ( "Si4432 Reset failed - _cs = %i\n", _cs );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "WriteByte" sends a byte of data into the selected Si4432 register. The
|
||||||
|
* specified register number is transmitted first with the MSB turned on
|
||||||
|
* which indicates it's a write operation. That is followed by the data byte
|
||||||
|
* to be written to the specified register.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Si4432::WriteByte ( byte reg, byte setting )
|
||||||
|
{
|
||||||
|
reg |= 0x80 ; // Indicate this is a "write" operation
|
||||||
|
|
||||||
|
//_spi->beginTransaction ( SPISettings ( BUS_SPEED, BUS_ORDER, BUS_MODE ));
|
||||||
|
//spiSimpleTransaction(_spi->bus());
|
||||||
|
digitalWrite ( _cs, LOW ); // Select the correct device
|
||||||
|
_spi->transfer ( reg ); // Send the register address
|
||||||
|
_spi->transfer ( setting ); // and the data byte
|
||||||
|
digitalWrite ( _cs, HIGH ); // Deselect the device
|
||||||
|
//_spi->endTransaction(); // Release the bus
|
||||||
|
// delayMicroseconds ( WRITE_DELAY );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "ReadByte" reads a byte of data from the selected Si4432. Here we send the
|
||||||
|
* register with the MSB set to zero indicating that we want to read the
|
||||||
|
* specified register.
|
||||||
|
*
|
||||||
|
* The "transfer" call to read the data has a dummy argument of '0'; the
|
||||||
|
* argument is needed but has absolutely no meaning.
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint8_t Si4432::ReadByte ( uint8_t reg )
|
||||||
|
{
|
||||||
|
uint8_t regValue; // Contains the requested data
|
||||||
|
|
||||||
|
//_spi->beginTransaction ( SPISettings ( BUS_SPEED, BUS_ORDER, BUS_MODE ));
|
||||||
|
//spiSimpleTransaction(_spi->bus());
|
||||||
|
digitalWrite ( _cs, LOW ); // Select the correct device
|
||||||
|
_spi->transfer ( reg ); // Send the register address
|
||||||
|
regValue = _spi->transfer ( 0 ); // Read the data byte
|
||||||
|
digitalWrite ( _cs, HIGH ); // Deselect the device
|
||||||
|
//_spi->endTransaction();
|
||||||
|
return regValue; // Return the answer
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "SetFrequency" sets the Si4432 frequency. Technical note A440 explains some of
|
||||||
|
* what's going on in here!
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Si4432::SetFrequency ( uint32_t freq )
|
||||||
|
{
|
||||||
|
int hbsel; // High/low band select bit
|
||||||
|
int sbsel; // Sideband select bit
|
||||||
|
uint16_t carrier; // Carrier frequency
|
||||||
|
uint32_t reqFreq = freq; // Copy of requested frequency
|
||||||
|
uint8_t fbs; // Frequency band select value (N - 24)
|
||||||
|
uint8_t ofc1; // To read the "REG_OFC1" register
|
||||||
|
uint8_t registerBuf[4]; // Used to send frequency data in burst mode
|
||||||
|
|
||||||
|
|
||||||
|
if ( freq >= 480000000 ) // Frequency > 480MHz (high band)?
|
||||||
|
{
|
||||||
|
hbsel = HBSEL; // High band is 480MHz to 960MHz
|
||||||
|
freq = freq / 2; // And divide the frequency in half
|
||||||
|
}
|
||||||
|
|
||||||
|
else // Frequency requested is less than 480MHz
|
||||||
|
{
|
||||||
|
hbsel = 0x00; // Low band is 240KHz to 479.9MHz
|
||||||
|
}
|
||||||
|
sbsel = SBSEL; // Select high sideband (always)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* add half the frequency resolution to required frequency so the actual value is rounded to nearest, not lowest
|
||||||
|
* Frequency resoluion is 156.25 in low band, 312.5 in high band but freq is already divided above
|
||||||
|
*/
|
||||||
|
freq = freq + 78;
|
||||||
|
/*
|
||||||
|
* "N" picks the 10MHz range for low band and the 20MHz range for the high band.
|
||||||
|
* It's explained in "Table 12" in the Silicon Labs datasheet.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int N = freq / 10000000; // Divide freq by 10MHz
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The low order 5 bits of the "Frequency Band Select" register (0x75) get
|
||||||
|
* loaded with "N - 24" and we add in the band select and sideband select
|
||||||
|
* bits.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fbs = (( N - 24 ) | hbsel | sbsel );
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compute the actual carrier frequency.
|
||||||
|
*
|
||||||
|
* It takes a few things to actually set the frequency. See Tech Note A440
|
||||||
|
* for an explanation (it made my head hurt)!
|
||||||
|
*
|
||||||
|
* The carrier frequency is actually an offset from the lower end of the
|
||||||
|
* frequency band selected (N-24), so for example in the initialization
|
||||||
|
* sequence we set the carrier frequency to 443,920,000. From Table 12 in
|
||||||
|
* the datasheet, we see that the frequency is in the "Low Band" (less than
|
||||||
|
* 480MHz) and the actual band will be '20', and "N" will be '44'.
|
||||||
|
*
|
||||||
|
* The "carrier" value is the number of '156.25Hz" increments from the base
|
||||||
|
* frequency of the band. For a frequency of 443,920,000, the answer is
|
||||||
|
* 25,088.
|
||||||
|
*
|
||||||
|
* If we're operating on the "High Band", the "carrier" is the number of
|
||||||
|
* '312.5Hz' increments from the base frequency of the band.
|
||||||
|
*/
|
||||||
|
|
||||||
|
carrier = ( 4 * ( freq - N * 10000000 )) / 625;
|
||||||
|
|
||||||
|
//if (_cs == 2 )
|
||||||
|
// Serial.printf ( "\nRequested frequency = %u \n", reqFreq );
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* M0WID mod - Update the frequency in "burst" mode as opposed to separate
|
||||||
|
* writes for each register, but only update what is needed
|
||||||
|
*/
|
||||||
|
uint8_t ncf1 = ( carrier >> 8 ) & 0xFF;
|
||||||
|
|
||||||
|
// if (fbs != _fbs) // write all three registers
|
||||||
|
// {
|
||||||
|
registerBuf[0] = REG_FBS|0x80; // First register in write mode (bit 7 set)
|
||||||
|
registerBuf[1] = fbs; // FBS register value
|
||||||
|
registerBuf[2] = ncf1 ; // NCF1 value
|
||||||
|
registerBuf[3] = carrier & 0xFF; // NCF0 value
|
||||||
|
|
||||||
|
//_spi->beginTransaction ( SPISettings ( BUS_SPEED, BUS_ORDER, BUS_MODE ));
|
||||||
|
// spiSimpleTransaction(_spi->bus());
|
||||||
|
digitalWrite ( _cs, LOW ); // Select the correct device
|
||||||
|
_spi->transfer ( registerBuf, 4 ); // Send the data
|
||||||
|
digitalWrite ( _cs, HIGH ); // Deselect the device
|
||||||
|
//_spi->endTransaction();
|
||||||
|
_ncf1 = ncf1;
|
||||||
|
_fbs = fbs;
|
||||||
|
// }
|
||||||
|
// else if (ncf1 != _ncf1) // Only write both bytes of the carrier data
|
||||||
|
// {
|
||||||
|
// registerBuf[0] = REG_NCF1|0x80; // First register in write mode (bit 7 set)
|
||||||
|
// registerBuf[1] = ncf1 ; // NCF1 value
|
||||||
|
// registerBuf[2] = carrier & 0xFF; // NCF0 value
|
||||||
|
// digitalWrite ( _cs, LOW ); // Select the correct device
|
||||||
|
// _spi->transfer ( registerBuf, 3 ); // Send the data
|
||||||
|
// digitalWrite ( _cs, HIGH ); // Deselect the device
|
||||||
|
// _ncf1 = ncf1;
|
||||||
|
// }
|
||||||
|
// else // Only write the least significant byte of the carrier register
|
||||||
|
// {
|
||||||
|
// registerBuf[0] = REG_NCF0|0x80; // First register in write mode (bit 7 set)
|
||||||
|
// registerBuf[1] = carrier & 0xFF; // NCF0 value
|
||||||
|
// digitalWrite ( _cs, LOW ); // Select the correct device
|
||||||
|
// _spi->transfer ( registerBuf, 2 ); // Send the data
|
||||||
|
// digitalWrite ( _cs, HIGH ); // Deselect the device
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
uint32_t fb = ( fbs & F_BAND ) ;
|
||||||
|
hbsel = hbsel>>5; // should be 1 or 0
|
||||||
|
|
||||||
|
// _freq will contain the actual frequency, not necessarily what was requested
|
||||||
|
_freq = (double)(10000000 * (hbsel + 1 )) * ( (double)fb + (double)24 + (double)carrier / (double)64000) ;
|
||||||
|
// Serial.printf("set Freq :%i, actual:%i, fb:%i, fc:%i, hbsel:%i\n", reqFreq, _freq, fb, carrier, hbsel);
|
||||||
|
|
||||||
|
// _spi->endTransaction(); // Release the bus
|
||||||
|
// delayMicroseconds ( WRITE_DELAY ); // Delay needed when writing frequency
|
||||||
|
|
||||||
|
// Serial.print ( ", N = " );
|
||||||
|
// Serial.print ( N );
|
||||||
|
|
||||||
|
// fbs = ReadByte ( REG_FBS );
|
||||||
|
|
||||||
|
// Serial.print ( ", Freq_Band = " );
|
||||||
|
// Serial.println ( fbs & F_BAND );
|
||||||
|
|
||||||
|
// Serial.print ( "hbsel = " );
|
||||||
|
// Serial.print ( fbs & HBSEL, HEX );
|
||||||
|
|
||||||
|
// carrier = ReadByte ( REG_NCF1 ) << 8;
|
||||||
|
// carrier |= ReadByte ( REG_NCF0 );
|
||||||
|
|
||||||
|
// Serial.print ( ", Carrier = " );
|
||||||
|
// Serial.print ( carrier );
|
||||||
|
|
||||||
|
// ofc1 = ReadByte ( REG_OFC1 );
|
||||||
|
|
||||||
|
// Serial.print ( ", REG_OFC1 = " );
|
||||||
|
// Serial.println ( ofc1, HEX );
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This delay is needed in the test programs. In the real TinySA software it is
|
||||||
|
* handled by the calling program.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// delayMicroseconds ( _dt ); // M0WID - Delay depends on RBW
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "SetRBW" Sets the "Resolution Bandwidth" based on a required value passed in
|
||||||
|
* and returns the actual value chosen as well as the required delay to allow the
|
||||||
|
* FIR filter in the SI4432 to settle (in microseconds) Delay time is longer for
|
||||||
|
* narrower bandwidths.
|
||||||
|
*/
|
||||||
|
|
||||||
|
float Si4432::SetRBW ( float reqBandwidth10, unsigned long* delaytime_p ) // "reqBandwidth" in kHz * 10
|
||||||
|
{
|
||||||
|
int filter = _bpfCount-1; // Elements in the "bandpassFilters" array
|
||||||
|
|
||||||
|
// Serial.printf ( "bpfCount %i\n", filter );
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "filter" is the index into "bandpassFilters" array. If the requested
|
||||||
|
* bandwidth is less than the bandwith in the first entry, we use that entry (2.6KHz).
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ( reqBandwidth10 <= _bandpassFilters[0].bandwidth10 )
|
||||||
|
filter = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the requested bandwidth is greater or equal to the value in the first entry,
|
||||||
|
* find the setting that is nearest (and above) the requested setting.
|
||||||
|
*/
|
||||||
|
else
|
||||||
|
while (( _bandpassFilters[filter-1].bandwidth10 > reqBandwidth10 - 0.01 ) && ( filter > 0 ))
|
||||||
|
filter--;
|
||||||
|
|
||||||
|
// Serial.print ( "filter = " ); Serial.println ( filter );
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Ok, we found the appropriate setting (or ended up with the maximum one),
|
||||||
|
* formulate the byte to sent to the "REG_IFBW" register (0x1C) from the piece parts.
|
||||||
|
*/
|
||||||
|
|
||||||
|
byte BW = ( _bandpassFilters[filter].dwn3_bypass << 7 )
|
||||||
|
| ( _bandpassFilters[filter].ndec_exp << 4 )
|
||||||
|
| _bandpassFilters[filter].filset;
|
||||||
|
|
||||||
|
WriteByte ( REG_IFBW ,BW ); // Send the bandwidth setting to the Si4432
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "Oversampling rate for clock recovery". Let me know if you understand the explanation
|
||||||
|
* in Tech Note A440!
|
||||||
|
*/
|
||||||
|
|
||||||
|
float rxosr = 500.0 * ( 1.0 + 2.0 * _bandpassFilters[filter].dwn3_bypass )
|
||||||
|
/ ( pow ( 2.0, ( _bandpassFilters[filter].ndec_exp - 3.0 ))
|
||||||
|
* _bandpassFilters[filter].bandwidth10 / 10.0 );
|
||||||
|
|
||||||
|
byte integer = (byte) rxosr ;
|
||||||
|
byte fractio = (byte) (( rxosr - integer ) * 8 );
|
||||||
|
byte memory = ( integer << 3 ) | ( 0x07 & fractio );
|
||||||
|
|
||||||
|
WriteByte ( REG_CROSR , memory ); // Clock recovery oversampling rate
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the bandwidth and delay time in the "RBW" structure returned by the function
|
||||||
|
* and in our internal variables.
|
||||||
|
*/
|
||||||
|
|
||||||
|
_bw = _bandpassFilters[filter].bandwidth10 / 10.0;
|
||||||
|
_dt = _bandpassFilters[filter].settleTime;
|
||||||
|
|
||||||
|
*delaytime_p = _dt;
|
||||||
|
return _bw;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Si4432::SetPreampGain ( int gain ) // Sets preamp gain
|
||||||
|
{
|
||||||
|
WriteByte ( REG_AGCOR1, gain ); // Just feed it to the Si4432
|
||||||
|
_gainReg = gain;
|
||||||
|
_autoGain = (bool)(gain & AGCEN);
|
||||||
|
|
||||||
|
// Serial.printf("Si4432 set gain:%i, auto:%i\n", _gainReg, _autoGain);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "GetPreampGain" was added by M0WID to read the LNA/PGA gain from the RX Si4432. Later
|
||||||
|
* modified to return the AGC setting state (on or off) and the "PGAGAIN | LNAGAIN"
|
||||||
|
* settings via the pointers in the argument list.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int Si4432::GetPreampGain ()
|
||||||
|
{
|
||||||
|
if (_autoGain) {
|
||||||
|
return ReadByte ( REG_AGCOR1 ); // Just return the register
|
||||||
|
} else {
|
||||||
|
return _gainReg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int Si4432::ReadPreampGain ()
|
||||||
|
{
|
||||||
|
return ReadByte ( REG_AGCOR1 ); // Just return the register
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "PreAmpAGC" returns "true" if the AGC is set to auto:
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool Si4432::PreAmpAGC () // Return true if agc set to auto
|
||||||
|
{
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Register 0x69 (REG_AGCOR1) contains the current setting of the LNA and PGA
|
||||||
|
* amplifiers. If AGC is enabled the value can vary during a sweep
|
||||||
|
*/
|
||||||
|
|
||||||
|
byte Reg69 = ReadByte ( REG_AGCOR1 ); // Read the register
|
||||||
|
|
||||||
|
// Serial.printf ( "REG_AGCOR1 %X \n", Reg69 ); // Debugging
|
||||||
|
|
||||||
|
return ( Reg69 & AGCEN ); // And the answer is!
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool Si4432::GetPreAmpAGC () // Return true if agc set to auto
|
||||||
|
{
|
||||||
|
|
||||||
|
return ( _autoGain );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "GetRSSI" is perhaps the most important function in the whole TinySA program.
|
||||||
|
* It returns the "Received Signal Strength Indicator" value from the receiver
|
||||||
|
* module. The RSSI is essentially an "S" meter indication from the receiver that
|
||||||
|
* will be used to measure the received level. The daya dheet indicates and accuracy
|
||||||
|
* of +/- 0.5dB.
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint8_t Si4432::GetRSSI ()
|
||||||
|
{
|
||||||
|
uint8_t rawRSSI;
|
||||||
|
|
||||||
|
rawRSSI = ReadByte ( REG_RSSI );
|
||||||
|
|
||||||
|
// float dBm = 0.5 * rawRSSI - 120.0 ;
|
||||||
|
// Serial.println ( dBm, 2 );
|
||||||
|
|
||||||
|
return rawRSSI ;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "SetPowerReference" - Set the GPIO-2 output for the LO (TX) SI4432 to required
|
||||||
|
* frequency, or off.
|
||||||
|
*
|
||||||
|
* If freq < 0 or > 6 GPIO-2 is grounded
|
||||||
|
*
|
||||||
|
* Freq = 0 30MHz
|
||||||
|
* Freq = 1 15MHz
|
||||||
|
* Freq = 2 10MHz
|
||||||
|
* Freq = 3 4MHz
|
||||||
|
* Freq = 4 3MHz
|
||||||
|
* Freq = 5 2MHz
|
||||||
|
* Freq = 6 1MHz
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Si4432::SetPowerReference ( int freq )
|
||||||
|
{
|
||||||
|
if ( freq < 0 || freq > 6 ) // Illegal frequency selection?
|
||||||
|
WriteByte ( REG_GPIO2, 0x1F ); // Set GPIO-2 to ground
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteByte ( REG_GPIO2, 0xC0 ); // Maximum drive and microcontroller clock output
|
||||||
|
WriteByte ( REG_MCOC, freq & 0x07 ); // Set GPIO-2 frequency as specified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "SetDrive" can be used to to set the transmitter (aka local oscillator) power
|
||||||
|
* output level.
|
||||||
|
*
|
||||||
|
* The drive value can be from '0' to '7' and the output power will be set
|
||||||
|
* according to the following table (from page 39 of the Si4432 datasheet):
|
||||||
|
*
|
||||||
|
* 0 => +1dBm 4 => +11dBm
|
||||||
|
* 1 => +2dBm 5 => +14dBm
|
||||||
|
* 2 => +5dBm 6 => +17dBm
|
||||||
|
* 3 => +8dBm 7 => +20dBm
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Si4432::SetDrive ( uint8_t level ) // Sets the LO drive level
|
||||||
|
{
|
||||||
|
if (( level < 0 ) || ( level > 7 ))
|
||||||
|
{
|
||||||
|
// Serial.printf ( "VFO %i Drive request refused level was %i\n", _type, level );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_pwr = level;
|
||||||
|
WriteByte ( REG_TXPWR, _pwr ); // Set power level
|
||||||
|
// Serial.printf ( "VFO %i Drive set to %i\n", _type, _pwr );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The transmitter module isn't always in transmit mode and the receiver module isn't
|
||||||
|
* always in receive mode, so these two functions allow us to switch modes for one or
|
||||||
|
* the other:
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Si4432::TxMode ( uint8_t level ) // Put module in TX mode
|
||||||
|
{
|
||||||
|
WriteByte ( REG_OFC1, ( TXON | PLLON | XTON )); // Transmitter, PLL and "Ready Mode" all on
|
||||||
|
SetDrive ( level );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Si4432::RxMode () // Put module in RX mode
|
||||||
|
{
|
||||||
|
WriteByte ( REG_OFC1, ( RXON | PLLON | XTON )); // Receiver, PLL and "Ready Mode" all on
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "Tune" sets the crystal tuning capacitance.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Si4432::Tune ( uint8_t cap ) // Set the crystal tuning capacitance
|
||||||
|
{
|
||||||
|
_capacitance = cap; // Save in local data
|
||||||
|
WriteByte ( REG_COLC, _capacitance ); // Send to the Si4432
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get frequency from Si4432
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint32_t Si4432::GetFrequency ()
|
||||||
|
{
|
||||||
|
return _freq;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Si4432::ReadFrequency ()
|
||||||
|
{
|
||||||
|
uint8_t fbs = ReadByte(REG_FBS);
|
||||||
|
uint16_t ncf1 = ReadByte(REG_NCF1);
|
||||||
|
uint16_t ncf0 = ReadByte(REG_NCF0);
|
||||||
|
uint32_t fc = ( ncf1<<8 ) + ncf0;
|
||||||
|
uint32_t fb = ( fbs & F_BAND ) ;
|
||||||
|
uint32_t hb = ( fbs & HBSEL ) >> 5; // HBSEL is bit 5
|
||||||
|
|
||||||
|
// Serial.printf ( "FBS=%X ncf1=%X ncf0=%X HBSEL=%X F_BAND=%X fc=%X (%u)\n",
|
||||||
|
// fbs, ncf1, ncf0, hb, fb, fc, fc);
|
||||||
|
|
||||||
|
uint32_t f = (uint32_t) ( 10000000.0 * ( (float) hb + 1.0 )
|
||||||
|
* ( (float) fb + 24.0 + ( (float) fc ) / 64000.0 ));
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "GetBandpassFilter10" - Get filter bandwidth * 10 from the specifed element of
|
||||||
|
* the bandpassfilter array
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint16_t Si4432::GetBandpassFilter10 ( uint8_t index )
|
||||||
|
{
|
||||||
|
if ( index >= _bpfCount )
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
else
|
||||||
|
return _bandpassFilters[index].bandwidth10;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Si4432::GetBandpassFilterCount ()
|
||||||
|
{
|
||||||
|
return _bpfCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For debugging, we can do a register dump on the module.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void Si4432::PrintRegs () // Dump all the Si4432 registers
|
||||||
|
{
|
||||||
|
for ( int r = 0; r < 0x80; r++)
|
||||||
|
if ( _type == RX_4432 ) // Receiver?
|
||||||
|
Serial.printf ( "RX Reg[0x%02X] = 0x%02X\n", r, ReadByte ( r ));
|
||||||
|
else if ( _type == TX_4432 ) // Transmitter?
|
||||||
|
Serial.printf ( "TX Reg[0x%02X] = 0x%02X\n", r, ReadByte ( r ));
|
||||||
|
else if ( _type == TGIF_4432 ) // Transmitter?
|
||||||
|
Serial.printf ( "TGIF Reg[0x%02X] = 0x%02X\n", r, ReadByte ( r ));
|
||||||
|
else if ( _type == TGLO_4432 ) // Transmitter?
|
||||||
|
Serial.printf ( "TGLO Reg[0x%02X] = 0x%02X\n", r, ReadByte ( r ));
|
||||||
|
}
|
237
si4432.h
Normal file
237
si4432.h
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
/*
|
||||||
|
* "Si4432.h"
|
||||||
|
*
|
||||||
|
* Added to the program in Version 1.1 by John Price (WA2FZW):
|
||||||
|
*
|
||||||
|
* Modified in Version 1.7 to create a "true" class/object implementation for
|
||||||
|
* handling the Si4432 modules.
|
||||||
|
* Modified by M0WID for 2.6 to remove dependencies on tinySA specific include files
|
||||||
|
* and use SPI class pointer in the constructor
|
||||||
|
*
|
||||||
|
* This file contains symbolic definitions for all the Si4432 registers that are
|
||||||
|
* used in the program and symbols for some of the values that get loaded into or
|
||||||
|
* read from them. For full explanations (whether you can make sense out of them
|
||||||
|
* or not) of the registers please refer to the "Silicon Labs Si4430/31/32 - B1"
|
||||||
|
* data sheet and "Silicon Labs Technical Note A440".
|
||||||
|
*
|
||||||
|
* Some of the functions in here apply only to the receiver and some apply only
|
||||||
|
* to the transmitter. The "Init" functions remember which type of module is
|
||||||
|
* being initiated. Eventually we will use that to avoid doing things which don't
|
||||||
|
* make sense for one type or the other.
|
||||||
|
*
|
||||||
|
* It also contains some structure definitions and some data elements we keep
|
||||||
|
* to ourselves.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SI4432A_H_
|
||||||
|
#define SI4432A_H_ // Prevent double inclusion
|
||||||
|
|
||||||
|
#include <Arduino.h> // Basic Arduino definitions
|
||||||
|
#include <SPI.h> // SPI bus related stuff
|
||||||
|
|
||||||
|
#define ELEMENTS(x) ( sizeof ( x ) / sizeof ( x[0] ))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In the original program, these were the indicies into the "SI_nSEL" array used
|
||||||
|
* to indicate whether the receiver or transmitter module was selected to perform
|
||||||
|
* an operation on.
|
||||||
|
*
|
||||||
|
* In this implementation, the "_type" variable is set to one or the other in the
|
||||||
|
* initialization functions to remember which type we are.
|
||||||
|
*
|
||||||
|
* The "_type" value will eventually be used to prevent certain operations from
|
||||||
|
* being accidently performed on the wrong type of module; for example, setting the
|
||||||
|
* RBW ("SetRBW") is not valid for the transmitter module or "Get_RSSI" is only
|
||||||
|
* applicable to the receiver module.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define RX_4432 0 // Receiver is Si4432 #0
|
||||||
|
#define TX_4432 1 // Transmitter is Si4432 #1
|
||||||
|
#define TGIF_4432 2
|
||||||
|
#define TGLO_4432 3
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The maximum SPI bus speed for the Si4432 is 10MHz. It operates in SPI MODE0 and
|
||||||
|
* the address and data are transmitted MSBFIRST.
|
||||||
|
*
|
||||||
|
* That being the case, the following definitions will be used in the read and write
|
||||||
|
* functions to set those parameters before each transaction:
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define BUS_SPEED 10000000 // 10 MHz
|
||||||
|
#define BUS_MODE SPI_MODE0 // Data is read on rising edge of the clock
|
||||||
|
#define BUS_ORDER MSBFIRST // Send stuff MSB first
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "WRITE_DELAY" is a time in microseconds that seems to be required after each write
|
||||||
|
* to one of the Si4432 registers on some (but not all) Si4431 modules. You can try
|
||||||
|
* reducing the value to '1', but if you have problems getting one of them to actually
|
||||||
|
* work, try increasing the value. '25' is the value that works for my modules.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define WRITE_DELAY 25
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Si4432 Registers used in the program and bit definitions for some of them:
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define REG_IS2 0x04 // Interrupt Status #1
|
||||||
|
#define ICHIPRDY 0x02 // Chip ready
|
||||||
|
|
||||||
|
#define REG_INT1 0x06 // Interrupt enable register 1
|
||||||
|
|
||||||
|
#define REG_OFC1 0x07 // Operating & Function Control 1
|
||||||
|
#define XTON 0x01 // Ready mode on (see datasheet)
|
||||||
|
#define PLLON 0x02 // PLL on
|
||||||
|
#define RXON 0x04 // Receiver on
|
||||||
|
#define TXON 0x08 // Transmitter on
|
||||||
|
#define X32KSEL 0x10 // Use external 32KHz crystal
|
||||||
|
#define ENLBD 0x40 // Device enabled
|
||||||
|
#define SW_RESET 0x80 // System reset
|
||||||
|
|
||||||
|
#define REG_COLC 0x09 // Crystal Oscillator Load Capacitance
|
||||||
|
|
||||||
|
#define REG_MCOC 0x0A // Microcontroller Output Clock
|
||||||
|
|
||||||
|
#define REG_GPIO0 0x0B // GPIO0 Configuration
|
||||||
|
|
||||||
|
#define REG_GPIO1 0x0C // GPIO1 Configuration
|
||||||
|
|
||||||
|
#define REG_GPIO2 0x0D // GPIO2 Configuration
|
||||||
|
|
||||||
|
#define REG_IFBW 0x1C // IF Filter Bandwidth
|
||||||
|
|
||||||
|
#define REG_AFCGSO 0x1D // AFC Loop Gearshift Override
|
||||||
|
|
||||||
|
#define REG_AFCTC 0x1E // AFC Timing Control
|
||||||
|
|
||||||
|
#define REG_CRGO 0x1F // Clock Recovery Gearshift Override
|
||||||
|
|
||||||
|
#define REG_CROSR 0x20 // Clock Recovery Oversampling Ratio
|
||||||
|
|
||||||
|
#define REG_CRO2 0x21 // Clock Recovery Offset 2
|
||||||
|
|
||||||
|
#define REG_CRO1 0x22 // Clock Recovery Offset 1
|
||||||
|
|
||||||
|
#define REG_CRO0 0x23 // Clock Recovery Offset 0
|
||||||
|
|
||||||
|
#define REG_CRTLG1 0x24 // Clock Recovery Timing Loop Gain 1
|
||||||
|
|
||||||
|
#define REG_CRTLG0 0x25 // Clock Recovery Timing Loop Gain 0
|
||||||
|
|
||||||
|
#define REG_RSSI 0x26 // Received Signal Strength Indicator
|
||||||
|
|
||||||
|
#define REG_AFCLIM 0x2A // AFC Limiter
|
||||||
|
|
||||||
|
#define REG_OOKC1 0x2C // OOK Counter Value 1
|
||||||
|
|
||||||
|
#define REG_OOKC2 0x2D // OOK Counter Value 2
|
||||||
|
|
||||||
|
#define REG_SPH 0x2E // Slicer Peak Hold
|
||||||
|
|
||||||
|
#define REG_DATAC 0x30 // Data access control
|
||||||
|
|
||||||
|
#define REG_AGCOR1 0x69 // AGC Override 1
|
||||||
|
#define LNAGAIN 0x10 // LNA enabled
|
||||||
|
#define AGCEN 0x20 // AGC enabled
|
||||||
|
#define PGAGAIN 0x0F // PGA Gain value in dB/3
|
||||||
|
#define AGC_ON 0x60 // Turn the AGC on
|
||||||
|
|
||||||
|
#define REG_TXPWR 0x6D // Transmitter Power
|
||||||
|
|
||||||
|
#define REG_FBS 0x75 // Frequency Band Select
|
||||||
|
#define SBSEL 0x40
|
||||||
|
#define HBSEL 0x20
|
||||||
|
#define F_BAND 0x1F
|
||||||
|
|
||||||
|
#define REG_NCF1 0x76 // Nominal Carrier Frequency 1
|
||||||
|
|
||||||
|
#define REG_NCF0 0x77 // Nominal Carrier Frequency 0
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
float bandwidth10; // Just one decimal point to save space (kHz * 10)
|
||||||
|
unsigned long settleTime; // Narrower bandwidth filter needs longer settling time before RSSI is stable
|
||||||
|
uint16_t dwn3_bypass; // Bypass decimate by 3 stage if set
|
||||||
|
uint16_t ndec_exp; // IF Filter decimation rate = 2^ndec_exp. larger number -> lower bandwidth (range 0-5)
|
||||||
|
uint16_t filset; // IF Filter coefficient set. Predefined FIR filter sets (1-15)
|
||||||
|
} bandpassFilter_t;
|
||||||
|
|
||||||
|
class Si4432
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit Si4432 ( SPIClass* spi, uint8_t cs, uint8_t id ); // Constructor
|
||||||
|
|
||||||
|
bool Init ( uint8_t cap ); // Initialize the SI4432 module, return false if failed
|
||||||
|
//bool TX_Init ( uint8_t cap, uint8_t power ); // Initialize the transmitter module, return false if failed
|
||||||
|
|
||||||
|
void SetFrequency ( uint32_t Freq ); // Set module's frequency
|
||||||
|
uint32_t GetFrequency (); // Get the module's frequency
|
||||||
|
uint32_t ReadFrequency (); // Read frequency from SI4432
|
||||||
|
|
||||||
|
void SetPowerReference ( int freq ); // Set the GPIO output for the LO
|
||||||
|
void SetDrive ( uint8_t level ); // Sets the drive level
|
||||||
|
void TxMode ( uint8_t level ); // Put module in TX mode
|
||||||
|
|
||||||
|
float SetRBW ( float reqBandwidth10, unsigned long* delaytime_p ); // "reqBandwidth" in kHz * 10
|
||||||
|
|
||||||
|
void SetPreampGain ( int gain ); // Sets preamp gain
|
||||||
|
int GetPreampGain (); // Get current gain register
|
||||||
|
int ReadPreampGain (); // Read gain register from SI4432
|
||||||
|
bool PreAmpAGC(); // Reads the register and return true if agc set to auto
|
||||||
|
bool GetPreAmpAGC (); // Return true if agc set to auto
|
||||||
|
|
||||||
|
uint8_t GetRSSI (); // Get Receiver Signal Strength
|
||||||
|
void RxMode (); // Put module in RX mode
|
||||||
|
void Tune ( uint8_t cap ); // Set the crystal tuning capacitance
|
||||||
|
|
||||||
|
void WriteByte ( byte reg, byte setting ); // Write a byte of data to the specified register
|
||||||
|
uint8_t ReadByte ( uint8_t reg ); // Read a byte of data from the specified register
|
||||||
|
|
||||||
|
uint16_t GetBandpassFilter10 ( uint8_t index ); // Return the filter bandwidth for selected element of bandpassFilters array
|
||||||
|
uint8_t GetBandpassFilterCount ();
|
||||||
|
|
||||||
|
void PrintRegs (); // Dump all the Si4432 registers
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These are common functions that are only accessible from within the object.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void SubInit (); // Initialization common to both modules
|
||||||
|
bool Reset (); // Initialize the module
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Private data elements:
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint8_t _cs; // Chip select pin number for the module
|
||||||
|
float _bw; // Current bandwidth setting
|
||||||
|
uint32_t _dt; // Current delay time setting
|
||||||
|
uint8_t _pwr; // Current power output (TX only?)
|
||||||
|
uint8_t _type; // Transmitter or receiver
|
||||||
|
uint8_t _capacitance; // Crystal load capacitance
|
||||||
|
SPIClass* _spi; // Pointer to the SPI object
|
||||||
|
uint8_t _fbs; // Current value of frequency band select register
|
||||||
|
uint8_t _ncf1; // Current value for most significant byte of carrier
|
||||||
|
uint32_t _freq; // Current actual frequency
|
||||||
|
// can be different to that requested due
|
||||||
|
// to resolution)
|
||||||
|
int _gainReg; // value of the gain reg written to the device
|
||||||
|
bool _autoGain; // true if auto
|
||||||
|
|
||||||
|
static bandpassFilter_t _bandpassFilters[];
|
||||||
|
static uint8_t _bpfCount; // Number of elements in bandpassFilters array
|
||||||
|
|
||||||
|
}; // End of class "Si4432"
|
||||||
|
|
||||||
|
#endif
|
355
simpleSA.h
Normal file
355
simpleSA.h
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
/*
|
||||||
|
* "tinySA.h"
|
||||||
|
*
|
||||||
|
* This file contains various parameters for the TinySA spectrum analyzer software.
|
||||||
|
*
|
||||||
|
* In general, the user should not have any need to change anything defined in here.
|
||||||
|
* All the things that a user might need to (or want to) change can be found in the
|
||||||
|
* "My_SA.h" file.
|
||||||
|
*
|
||||||
|
* The starting point for this version is the "tinySA_touch02" software developed
|
||||||
|
* by Dave (M0WID). That software is based on the original version by Erik Kaashoek.
|
||||||
|
*
|
||||||
|
* Modified by John Price (WA2FZW):
|
||||||
|
*
|
||||||
|
* Version 1.0:
|
||||||
|
*
|
||||||
|
* Just add comments and try to figure out how it all works!
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Version 1.7:
|
||||||
|
*
|
||||||
|
* Moved lots of definitions from the main file to here to reduce the clutter
|
||||||
|
* in that file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TINYSA_H_
|
||||||
|
#define TINYSA_H_ // Prevent double inclusion
|
||||||
|
|
||||||
|
#include "My_SA.h" // User settable parameters
|
||||||
|
#include <Arduino.h> // General Arduino definitions
|
||||||
|
#include <TFT_eSPI.h>
|
||||||
|
|
||||||
|
|
||||||
|
#define PROGRAM_NAME "TinySA" // These are for the WiFi interface
|
||||||
|
#define PROGRAM_VERSION "3.0" // Current version is 3.0
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* I think this symbol defines the number of different trace types that are available,
|
||||||
|
* but I'm not sure about that yet. This is the way Dave has it set, so until I figure
|
||||||
|
* it out, it will stay set at '1'.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define TRACE_COUNT 1 // Number fo different traces available
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Define variables and functions associated with drawing stuff on the screen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DISPLAY_POINTS 290 // Number of scan points in a sweep
|
||||||
|
#define CHAR_HEIGHT 8 // Height of a character
|
||||||
|
#define HALF_CHAR_H 4 // Half a character height
|
||||||
|
#define CHAR_WIDTH 6 // Width of a character
|
||||||
|
#define X_GRID 10 // Number of vertical grid lines
|
||||||
|
#define Y_GRID 10 // Number of horizontal grid lines
|
||||||
|
|
||||||
|
#define DELTA_X ( DISPLAY_POINTS / X_GRID ) // Spacing of x axis grid lines
|
||||||
|
#define DELTA_Y ( 21 ) // Spacing of y axis grid lines
|
||||||
|
|
||||||
|
#define X_ORIGIN 27 // 'X' origin of checkerboard
|
||||||
|
#define Y_ORIGIN ( CHAR_HEIGHT * 2 + 3 ) // 'Y' origin of checkerboard
|
||||||
|
|
||||||
|
#define GRID_HEIGHT ( Y_GRID * DELTA_Y ) // Height of checkerboard
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Definitions used in signal generator mode
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum {SIG_MENU_KEY, SIG_FM_KEY, SIG_AM_KEY, SIG_ON_KEY, SIG_FREQ_KEY, SIG_MOD_KEY};
|
||||||
|
|
||||||
|
#define SA_FONT_LARGE "NotoSansBold56"
|
||||||
|
|
||||||
|
// sig gen mode key position, size and font
|
||||||
|
#define KEY_W 50 // Width and height
|
||||||
|
#define KEY_H 40
|
||||||
|
#define NUM_W 31 // width for numeric digits
|
||||||
|
#define NUM_H 33 // height for numeric digit +/- keys
|
||||||
|
#define KEY_FONT "NotoSansSCM14" //Semi Condensed Monospaced 14pt
|
||||||
|
#define KEY_COLOR TFT_WHITE
|
||||||
|
#define KEY_SEL_COLOR TFT_CYAN
|
||||||
|
#define KEY_ON_COLOR TFT_GREEN
|
||||||
|
#define KEY_OFF_COLOR TFT_PINK
|
||||||
|
|
||||||
|
#define SIG_KEY_COUNT 18
|
||||||
|
#define MAX_SIGLO_FREQ 250000000
|
||||||
|
#define MIN_SIGLO_FREQ 100
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Symbols for the various attenuator options
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define PE4302_PCF 1
|
||||||
|
#define PE4302_GPIO 2
|
||||||
|
#define PE4302_SERIAL 3
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Color definitions for the standard displays; Again, these need to be moved to
|
||||||
|
* a separate header file.
|
||||||
|
*
|
||||||
|
* Modified in M0WID Version 05 - Eliminate all but the ILI9431 color definitions.
|
||||||
|
* Modified in WA2FZW Version 1.1 - Change all "DISPLAY_color" to simply "color".
|
||||||
|
*
|
||||||
|
* The "TFT_color" values are defined in the "TFT_eSPI.h" file in the library.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define WHITE TFT_WHITE
|
||||||
|
#define BLACK TFT_BLACK
|
||||||
|
#define DARKGREY TFT_DARKGREY
|
||||||
|
#define YELLOW TFT_YELLOW
|
||||||
|
#define ORANGE TFT_ORANGE
|
||||||
|
#define RED TFT_RED
|
||||||
|
#define GREEN TFT_GREEN
|
||||||
|
#define BLUE TFT_BLUE
|
||||||
|
#define PINK TFT_PINK
|
||||||
|
#define LT_BLUE 0x6F1F
|
||||||
|
#define MAGENTA TFT_MAGENTA
|
||||||
|
#define INVERSE TFT_WHITE
|
||||||
|
#define BACKGROUND TFT_BLACK // Default background color
|
||||||
|
|
||||||
|
#define SCREEN_WIDTH 320 // Display width, in pixels
|
||||||
|
#define SCREEN_HEIGHT 240 // Display height, in pixels
|
||||||
|
|
||||||
|
#define DELAY_ERROR 2 // Time in seconds to show error message on display
|
||||||
|
|
||||||
|
#define ERR_INFO 1 // Informational type error
|
||||||
|
#define ERR_WARN 2 // Warning
|
||||||
|
#define ERR_FATAL 3 // Fatal error
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A factor used to increase the number of measurement points above that calculated by
|
||||||
|
* just dividing the sweep span by the RBW. Allows for some overlap to reduce the effect
|
||||||
|
* of the 3dB drop at the filter edges
|
||||||
|
*/
|
||||||
|
#define OVERLAP 1.1
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These are the minimum and maximum values for the "IF Frequency". In reality, testing has
|
||||||
|
* shown that settings of more than about 100KHz from the normal 433.92MHz cause lots of
|
||||||
|
* spurs, but some SAW filters may behave differently.
|
||||||
|
*/
|
||||||
|
#define MIN_IF_FREQ 433000000UL // 433MHz
|
||||||
|
#define MAX_IF_FREQ 435000000UL // 435MHz
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tracking Generator offset limits - note signed
|
||||||
|
*/
|
||||||
|
#define MIN_TG_OFFSET -1000000L // -1MHz
|
||||||
|
#define MAX_TG_OFFSET 1000000L // +1MHz
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SI4432 max and min drive levels
|
||||||
|
*/
|
||||||
|
#define MIN_DRIVE 0
|
||||||
|
#define MAX_DRIVE 7
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The various operating modes:
|
||||||
|
*
|
||||||
|
* Only SA_LOW_RANGE implemented so far - some of these may never get implemented!
|
||||||
|
* Low range is using the mixer, range around 1Mhz-250Mhz depending on low pass
|
||||||
|
* filter installed.
|
||||||
|
*
|
||||||
|
* High range is direct to the LO SI4432, bypassing mixer, attenuator and filters,
|
||||||
|
* range approx 240MHz - 930Mhz
|
||||||
|
*
|
||||||
|
* High range performance will be limited due to no bandpass filtering other than
|
||||||
|
* in SI4432 itself.
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum { SA_LOW_RANGE, SA_HIGH_RANGE, SIG_GEN_LOW_RANGE, SIG_GEN_HIGH_RANGE,
|
||||||
|
IF_SWEEP, ZERO_SPAN_LOW_RANGE, ZERO_SPAN_HIGH_RANGE, TRACKING_GENERATOR, BANDSCOPE };
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The "AV_XXXX" symbols define various options for how readings are averaged"
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum { AV_OFF, AV_MIN, AV_MAX, AV_2, AV_4, AV_8 };
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Modulation types for signal generator mode
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum { MOD_OFF, MOD_AM, MOD_FM, NOISE };
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is a macro that is used to determine the number of elements in an array. It figures
|
||||||
|
* that out by dividing the total size of the array by the size of a single element. This is
|
||||||
|
* how we will calculate the number of entries in the "msgTable" array.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define ELEMENTS(x) ( sizeof ( x ) / sizeof ( x[0] ))
|
||||||
|
#define min(a,b) ( a<b ? a : b )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The "peak_t" type structure is used for recording the locations of the markers:
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t Level;
|
||||||
|
uint16_t Index;
|
||||||
|
uint32_t Freq;
|
||||||
|
} peak_t;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The "settings_t" structure defines the parameters used to configure the
|
||||||
|
* spectrum analyzer for a particular scan.
|
||||||
|
*
|
||||||
|
* The "settings" are saved as a file in the flash memory and can be recalled
|
||||||
|
* from there.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t ScanStart = 0; // Scan start frequency (***)
|
||||||
|
uint32_t ScanStop = 100000000; // Scan end frequency (***)
|
||||||
|
uint32_t IF_Freq = 433920000; // Default IF frequency (***)
|
||||||
|
int16_t MaxGrid = -10; // Default dB for top line of graph (***)
|
||||||
|
int16_t MinGrid = -110; // Default dB for bottom line of graph (***)
|
||||||
|
int8_t Attenuate = 0; // Attenuator setting (***)
|
||||||
|
int8_t Generate = 0; // Signal generator mode if not zero (***)
|
||||||
|
int16_t Bandwidth10 = 0; // Resolution Bandwidth setting*10; 0 = auto
|
||||||
|
int16_t LevelOffset = 0; // Related to power reading; not sure what though!
|
||||||
|
int8_t ReferenceOut = 1; // Transmitter GPIO2 set to 15MHz
|
||||||
|
int16_t PowerGrid = 10; // dB/vertical divison on the grid
|
||||||
|
bool Spur = 0; // Spur reduction on or off
|
||||||
|
int32_t SpurOffset = 300000; // Amount to offset IF if spur reduction on. Can be -ve
|
||||||
|
uint8_t Average = 0; // Averaging setting (0 - 5)
|
||||||
|
bool ShowStorage = 0; // Display stored scan (on or off)
|
||||||
|
bool SubtractStorage = 0; // Subtract stored scan (on or off)
|
||||||
|
bool ShowGain = 1; // Display gain trace (on or off)
|
||||||
|
bool ShowSweep = 1; // Display dB trace (on or off)
|
||||||
|
uint8_t Drive = 6; // LO Drive power (***)
|
||||||
|
uint8_t SigGenDrive = 5; // Signal generator drive (for RX SI4432)
|
||||||
|
uint8_t spareUint8t_1 = 5; // spare
|
||||||
|
uint8_t spareUint8t_2 = 5; // spare
|
||||||
|
uint8_t PreampGain = 0x60; // AGC on
|
||||||
|
uint16_t Mode = SA_LOW_RANGE; // Default to low freq range Spectrum analyser mode
|
||||||
|
uint16_t Timebase = 100; // Timebase for Zero IF modes (milliSeconds)
|
||||||
|
int16_t TriggerLevel = -40; // Trigger level for ZeroIF mode (dBm)
|
||||||
|
uint32_t BandscopeStart = 7000000; // Start frequency for bandscope sweep
|
||||||
|
uint32_t BandscopeSpan = 200000; // Span for the bandscope sweep
|
||||||
|
uint16_t BandscopePoints = 80; // No of points in the sweep. 80/160/320
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The following line should read:
|
||||||
|
*
|
||||||
|
* MkrStatus[MARKER_COUNT] = { MKR_ACTIVE, 0, 0, 0 };
|
||||||
|
*
|
||||||
|
* however, there is a chicken & egg thing going on between this file and "Marker.h"
|
||||||
|
* so for now, the initialization values are hard-coded.
|
||||||
|
*/
|
||||||
|
uint8_t MkrStatus[4] = { 0x08, 0, 0, 0 };
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The "Dummy" entry is here for testing purposes. Bu enabling or disabling it, one
|
||||||
|
* can force the default settings to be used at startup as opposed to the saved ones.
|
||||||
|
*/
|
||||||
|
uint32_t Dummy;
|
||||||
|
|
||||||
|
} settings_t;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Setting structure for signal generator mode
|
||||||
|
*
|
||||||
|
* Saved as a file to flash.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint32_t Frequency = 14000000; // in Hz
|
||||||
|
uint8_t LO_Drive = 6; // 0 = -1, 1=2, 2=5, 3=8, 4=11, 5=14, 6=17, 7=20 dBm
|
||||||
|
uint8_t RX_Drive = 3; // as above, B3555 SAW filter max 10dBm, assume some loss in switch, so 4 would be the max allowed
|
||||||
|
uint8_t ModulationType = MOD_OFF; // see enum
|
||||||
|
uint16_t ModFrequency = 1000; // in Hz
|
||||||
|
uint8_t ModDepth = 100; // in %
|
||||||
|
int16_t Calibration = -13; // in dBm, max power out from your unit with no attenuation
|
||||||
|
int16_t Power = -15; // in dBm. Required output power.
|
||||||
|
uint8_t Dummy = 123; // dummy to check if the data is valid
|
||||||
|
} sigGenSettings_t;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Setting structure for tracking generator
|
||||||
|
* If two SI4432 are used for tracking generator IF and LO then
|
||||||
|
* the Tracking Generator can also be used as a signal generator
|
||||||
|
*
|
||||||
|
* Saved as a file to flash.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint8_t Mode = 0; // 0 = off, 1 = track, 2 = sig gen
|
||||||
|
uint32_t Frequency = 14000000; // in Hz
|
||||||
|
int32_t Offset = 0; // Offset from LO in Hz. Can be -ve
|
||||||
|
uint8_t LO_Drive = 6; // 0 = -1, 1=2, 2=5, 3=8, 4=11, 5=14, 6=17, 7=20 dBm
|
||||||
|
uint8_t IF_Drive = 3; // as above, B3555 SAW filter max 10dBm, assume some loss in switch, so 4 would be the max allowed
|
||||||
|
uint8_t ModulationType = MOD_OFF; // see enum
|
||||||
|
uint16_t ModFrequency = 1000; // in Hz
|
||||||
|
uint8_t ModDepth = 100; // in %
|
||||||
|
int16_t Calibration = -13; // in dBm, max power out from your unit with no attenuation
|
||||||
|
int16_t Power = -15; // in dBm. Required output power.
|
||||||
|
uint8_t Dummy = 99; // dummy to check if the data is valid
|
||||||
|
} trackGenSettings_t;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The "config" structure defines some general display parameters.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t magic = 1234;
|
||||||
|
|
||||||
|
#ifdef __DAC__
|
||||||
|
uint16_t dac_value;
|
||||||
|
#endif
|
||||||
|
uint16_t grid_color = BLACK;
|
||||||
|
uint16_t menu_normal_color = WHITE;
|
||||||
|
uint16_t menu_active_color = LT_BLUE;
|
||||||
|
uint16_t trace_color[TRACE_COUNT] = { LT_BLUE };
|
||||||
|
uint32_t harmonic_freq_threshold = 0;
|
||||||
|
int16_t vbat_offset = 0;
|
||||||
|
uint16_t touch_cal[5] = { 398, 3479, 335, 3465, 1 }; // Default values for TFT_eSPI touch
|
||||||
|
uint8_t RX_capacitance = RX_CAPACITANCE; // allows some calibration of frequency
|
||||||
|
uint8_t TX_capacitance = TX_CAPACITANCE;
|
||||||
|
uint8_t tgLO_capacitance = TG_LO_CAPACITANCE;
|
||||||
|
uint8_t tgIF_capacitance = TG_IF_CAPACITANCE;
|
||||||
|
int32_t checksum = 0;
|
||||||
|
} config_t;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Used for keys in Sig Gen mode
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
uint16_t x;
|
||||||
|
uint16_t y;
|
||||||
|
uint16_t width;
|
||||||
|
uint16_t height;
|
||||||
|
uint16_t color;
|
||||||
|
uint16_t activeColor;
|
||||||
|
const char * text; // pointer to key text
|
||||||
|
const char * activeText;
|
||||||
|
} sig_key_t;
|
||||||
|
|
||||||
|
#endif // #ifndef TINYSA_H_
|
1913
simpleSA.ino
Normal file
1913
simpleSA.ino
Normal file
File diff suppressed because it is too large
Load Diff
866
simpleSA_wifi.cpp
Normal file
866
simpleSA_wifi.cpp
Normal file
@ -0,0 +1,866 @@
|
|||||||
|
/*
|
||||||
|
* "TinySA_wifi.cpp"
|
||||||
|
*
|
||||||
|
* Trial wifi charting functionality to see if WiFi affects the scan results
|
||||||
|
* Based on example here https://circuits4you.com/2019/01/11/esp8266-data-logging-with-real-time-graphs/
|
||||||
|
*
|
||||||
|
* Requires some files to be placed into the spiffs area of flash
|
||||||
|
*
|
||||||
|
* Modified in Version 2.1 by WA2FZW:
|
||||||
|
*
|
||||||
|
* Eliminated all calls to "WriteSettings". All of the functions in "Cmd.cpp"
|
||||||
|
* that actually update the "setting" structure elements now handle that task
|
||||||
|
* and do so based on whether or not the value of the parameter actually
|
||||||
|
* changed or not which wasn't the case before.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "TinySA_wifi.h" // WiFi definitions
|
||||||
|
#include "Si4432.h" // Si4432 definitions
|
||||||
|
#include "Cmd.h" // Command processing functions
|
||||||
|
|
||||||
|
extern int bpfCount; // Number of elements in the bandpassFilters array
|
||||||
|
extern int updateSidebar; // Flag to indicate no of clients has changed
|
||||||
|
extern void ClearDisplay ();
|
||||||
|
extern void DisplayError ( uint8_t severity, const char *l1, const char *l2, const char *l3, const char *l4 );
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In Version 1.8, the transmitter and receiver Si4432 modules are implemented as
|
||||||
|
* objects.
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern Si4432 rcvr; // Object for the receiver
|
||||||
|
extern Si4432 xmit; // And the transmitter
|
||||||
|
|
||||||
|
extern bool AGC_On; // Flag indicates if Preamp AGC is enabled
|
||||||
|
extern uint8_t AGC_Reg; // Fixed value for preampGain if not auto
|
||||||
|
|
||||||
|
extern uint32_t startFreq_IF;
|
||||||
|
extern uint32_t stopFreq_IF;
|
||||||
|
extern uint32_t sigFreq_IF;
|
||||||
|
|
||||||
|
|
||||||
|
AsyncWebServer server ( 80 ); // Create the webserver object
|
||||||
|
AsyncResponseStream *response;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Install WebSockets library by Markus Sattler
|
||||||
|
* https://github.com/Links2004/arduinoWebSockets
|
||||||
|
*/
|
||||||
|
|
||||||
|
WebSocketsServer webSocket = WebSocketsServer ( 81 );
|
||||||
|
|
||||||
|
uint8_t socketNumber;
|
||||||
|
unsigned long messageNumber;
|
||||||
|
|
||||||
|
extern uint8_t numberOfWebsocketClients;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tracking of number of Wi-Fi reconnects and total connection time
|
||||||
|
*/
|
||||||
|
|
||||||
|
unsigned long numberOfReconnects;
|
||||||
|
unsigned long millisConnected;
|
||||||
|
|
||||||
|
IPAddress ipAddress; // Store the IP address for use elsewhere, eg info
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function to format IP address nicely
|
||||||
|
*/
|
||||||
|
|
||||||
|
char *FormatIPAddress ( IPAddress ipAddress )
|
||||||
|
{
|
||||||
|
static char formatBuffer[20] = {0};
|
||||||
|
sprintf( formatBuffer, "%d.%d.%d.%d", ipAddress[0], ipAddress[1],
|
||||||
|
ipAddress[2], ipAddress[3] );
|
||||||
|
return formatBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
boolean startAP () // Start the WiFi Access Point, keep the user informed.
|
||||||
|
{
|
||||||
|
ClearDisplay (); // Fade to black
|
||||||
|
|
||||||
|
Serial.println ( "Starting Access Point" ); // Put in the instructions
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start by kicking off the soft-AP. Call depends on whether or not password
|
||||||
|
* is required to connect
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef AP_PASSWORD
|
||||||
|
|
||||||
|
boolean result = WiFi.softAP ( PROGRAM_NAME, AP_PASSWORD );
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
boolean result = WiFi.softAP ( PROGRAM_NAME );
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if ( !result ) // This has failed, tell the user
|
||||||
|
DisplayError ( ERR_WARN, "Failed to open AP:", "WiFi Disabled", NULL, NULL );
|
||||||
|
|
||||||
|
else
|
||||||
|
ipAddress = WiFi.localIP ();
|
||||||
|
|
||||||
|
Serial.printf ( "Access Point started, result = %b \n", result );
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
boolean connectWiFi() // Connect to Wifi using SSID (ie via Router)
|
||||||
|
{
|
||||||
|
|
||||||
|
Serial.printf ( "Connecting to: %s \n", WIFI_SSID );
|
||||||
|
// Serial.println( (char *) &configuration.WiFi_SSID[0] );
|
||||||
|
|
||||||
|
WiFi.softAPdisconnect (); // Disconnect anything that we may have
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start the connection:
|
||||||
|
*/
|
||||||
|
|
||||||
|
// WiFi.begin ( (char *) &configuration.WiFi_SSID[0], (char *) &configuration.WiFi_Password[0] );
|
||||||
|
WiFi.begin ( WIFI_SSID, WIFI_PASSWORD );
|
||||||
|
|
||||||
|
WiFi.setSleep (false ); // Disable sleep
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Apply any hostname that we may have
|
||||||
|
*/
|
||||||
|
|
||||||
|
// if ( strlen ( (char *) &configuration.Hostname[0]) > 0 )
|
||||||
|
// WiFi.setHostname ( (char *) &configuration.Hostname[0] );
|
||||||
|
// else
|
||||||
|
WiFi.setHostname ( PROGRAM_NAME );
|
||||||
|
|
||||||
|
int maxTry = 10; // Wait for the connection to be made.
|
||||||
|
|
||||||
|
tft.setCursor ( 0, 190 );
|
||||||
|
tft.printf ( "Connecting to WiFi %s", WIFI_SSID );
|
||||||
|
|
||||||
|
while (( WiFi.status() != WL_CONNECTED ) && ( maxTry > 0 ))
|
||||||
|
{
|
||||||
|
tft.print("."); // Nice touch!!!
|
||||||
|
delay ( 1000 ); // Wait and update the try count.
|
||||||
|
maxTry--;
|
||||||
|
|
||||||
|
if ( maxTry <= 0 )
|
||||||
|
{
|
||||||
|
DisplayError ( ERR_WARN, "Connecting to", WIFI_SSID, "failed!", "WiFi Disabled" );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ipAddress = WiFi.localIP (); // We are connected, display the IP address
|
||||||
|
|
||||||
|
Serial.printf ( "Connected - IP %s \n", FormatIPAddress ( ipAddress ));
|
||||||
|
tft.printf ( "\n\nConnected - IP %s \n", FormatIPAddress ( ipAddress ));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handle websocket events
|
||||||
|
* This is how dta is sent from the web page to the ESP32
|
||||||
|
*/
|
||||||
|
|
||||||
|
void webSocketEvent ( uint8_t num, WStype_t type, uint8_t* payload, size_t payloadLength )
|
||||||
|
{
|
||||||
|
switch ( type )
|
||||||
|
{
|
||||||
|
case WStype_DISCONNECTED:
|
||||||
|
Serial.printf("[%u] Disconnected!\n", num);
|
||||||
|
if ( numberOfWebsocketClients > 0 )
|
||||||
|
numberOfWebsocketClients--;
|
||||||
|
updateSidebar = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WStype_CONNECTED:
|
||||||
|
Serial.printf ( "[%u] Connected from ", num );
|
||||||
|
Serial.println ( webSocket.remoteIP ( num ));
|
||||||
|
numberOfWebsocketClients++;
|
||||||
|
updateSidebar = true;
|
||||||
|
webSocket.sendTXT ( num, "Connected" ); // send message back to client
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Format of message to process
|
||||||
|
*
|
||||||
|
* #a 123.123 Start frequency
|
||||||
|
* #b 123.123 Stop frequency
|
||||||
|
*
|
||||||
|
* Other formats are ignored
|
||||||
|
*/
|
||||||
|
|
||||||
|
case WStype_TEXT:
|
||||||
|
if ( payload[0] == '#' )
|
||||||
|
{
|
||||||
|
if ( payloadLength > 3 )
|
||||||
|
{
|
||||||
|
char* field = strtok ( (char*) &payload[3], " " ); // Extract the field value as string
|
||||||
|
float value = atof ( field ); // Convert to float
|
||||||
|
|
||||||
|
if ( isnan ( value )) // If not a number
|
||||||
|
return; // Bail out!
|
||||||
|
|
||||||
|
Serial.printf ( "payload command %c value %f\n", payload[1], value );
|
||||||
|
|
||||||
|
switch ( payload[1] )
|
||||||
|
{
|
||||||
|
case 'a':
|
||||||
|
SetSweepStart ( value * 1000000.0 ); // Set sweep start frequency
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'b':
|
||||||
|
SetSweepStop ( value * 1000000.0 ); // Set sweep stop frequency
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'c':
|
||||||
|
SetSweepCenter ( value * 1000000.0, WIDE ); // Set sweep center frequency
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'd':
|
||||||
|
SetLoDrive ( (uint8_t) value );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'g':
|
||||||
|
SetPreampGain( (int) value ); // Set PreAmp gain register
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'i': // IF Frequency
|
||||||
|
SetIFFrequency ( (int32_t) ( value * 1000000.0 ));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'o': // Ref Output (LO GPIO2)
|
||||||
|
SetRefOutput ( (int) value );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'p': // Adjust actual power level
|
||||||
|
RequestSetPowerLevel( value );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 's': // Adjust sweep span
|
||||||
|
SetSweepSpan ( (int32_t) ( value * 1000000.0 ));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'A': // Internal Attenuation (PE4302)
|
||||||
|
SetAttenuation ( value );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'R': // Requested RBW. 0=Auto
|
||||||
|
SetRBW ( value );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'S': // Spur Reduction on/off
|
||||||
|
SetSpur ( value );
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Serial.printf ( "payload[1] was %c\n", payload[1] );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else // payload[0] is not '#'
|
||||||
|
{
|
||||||
|
webSocket.sendTXT ( num, "pong" ); // send message back to client to keep connection alive
|
||||||
|
Serial.printf ( "[%u] get Text: %s\n", num, payload );
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Serial.println ( "Case?" );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Monitor Wi-Fi connection if it is alive. If not alive then wait until it reconnects.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void isWiFiAlive ( void )
|
||||||
|
{
|
||||||
|
if ( WiFi.status() != WL_CONNECTED )
|
||||||
|
{
|
||||||
|
Serial.print ( "not connected" );
|
||||||
|
|
||||||
|
while ( WiFi.status() != WL_CONNECTED )
|
||||||
|
{
|
||||||
|
Serial.print ( "." );
|
||||||
|
delay(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
numberOfReconnects++;
|
||||||
|
millisConnected = millis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Some routines for XML
|
||||||
|
*/
|
||||||
|
|
||||||
|
char *escapeXML ( char *s ) // Use special codes for some characters
|
||||||
|
{
|
||||||
|
static char b[1024];
|
||||||
|
|
||||||
|
b[0] = '\0'; // Null terminator
|
||||||
|
|
||||||
|
for ( int i = 0; i < strlen(s); i++ )
|
||||||
|
{
|
||||||
|
switch ( s[i] )
|
||||||
|
{
|
||||||
|
case '\"':
|
||||||
|
strcat ( b,""" );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '&':
|
||||||
|
strcat (b, "&" );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '<':
|
||||||
|
strcat ( b,"<" );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '>':
|
||||||
|
strcat ( b,">" );
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
int l = strlen ( b );
|
||||||
|
b[l] = s[i];
|
||||||
|
b[l + 1] = '\0';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add an XML tag with its value to the buffer b
|
||||||
|
*/
|
||||||
|
|
||||||
|
void addTagNameValue ( char *b, char *_name, char *value )
|
||||||
|
{
|
||||||
|
strcat ( b,_name );
|
||||||
|
strcat ( b, "=\"" );
|
||||||
|
strcat ( b,value );
|
||||||
|
strcat ( b,"\" " );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On request from web page convert the data from a scan into XML and send
|
||||||
|
*
|
||||||
|
* Ideally we would push the data to the web page at the end of a scan,
|
||||||
|
* or perhaps just create the xml at the end of each scan - investigate later
|
||||||
|
*/
|
||||||
|
|
||||||
|
void onGetScan ( AsyncWebServerRequest *request )
|
||||||
|
{
|
||||||
|
response = request->beginResponseStream ( "text/xml" );
|
||||||
|
// Serial.println ( "onGetScan" );
|
||||||
|
|
||||||
|
response->print ( "<?xml version=\"1.0\" encoding=\"utf-16\"?>" );
|
||||||
|
response->println ( "<Points>" );
|
||||||
|
|
||||||
|
for( int i = 0; i < DISPLAY_POINTS-1; i++ ) // For each data point
|
||||||
|
{
|
||||||
|
// Serial.printf ( "<Point F=\"%i\" RSSI=\"%i\"/> %i\n",myFreq[i], myData[i], i );
|
||||||
|
response->printf ( "<P F=\"%i\" R=\"%i\"/>\n", myFreq[i], myData[i] );
|
||||||
|
}
|
||||||
|
|
||||||
|
response->print ( "</Points>" );
|
||||||
|
request->send ( response );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On request from web page convert the gain data from a sweep into JSON and send
|
||||||
|
* Ideally we would push the data to the web page at the end of a scan,
|
||||||
|
*/
|
||||||
|
|
||||||
|
void onGetGainSweep ( AsyncWebServerRequest *request )
|
||||||
|
{
|
||||||
|
size_t bufferSize = JSON_ARRAY_SIZE ( DISPLAY_POINTS )
|
||||||
|
+ JSON_OBJECT_SIZE ( 1 ) + DISPLAY_POINTS * JSON_OBJECT_SIZE ( 2 );
|
||||||
|
|
||||||
|
AsyncJsonResponse * response = new AsyncJsonResponse ( false, bufferSize );
|
||||||
|
// response->addHeader ( "Server","ESP Async Web Server" );
|
||||||
|
|
||||||
|
JsonObject root = response->getRoot();
|
||||||
|
JsonArray gainPoints = root.createNestedArray ( "gainPoints" ); // Add gainPoints array
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add the objects to the array
|
||||||
|
*/
|
||||||
|
|
||||||
|
for ( int i = 0; i < DISPLAY_POINTS; i++ ) // For each data point
|
||||||
|
{
|
||||||
|
JsonObject dataPoint = gainPoints.createNestedObject(); // Add an object to the array
|
||||||
|
dataPoint["x"] = myFreq[i]/1000000.0; // set the x(frequency) value
|
||||||
|
dataPoint["y"] = myGain[i]; // set the y (gain) value
|
||||||
|
}
|
||||||
|
|
||||||
|
response->setLength();
|
||||||
|
request->send ( response );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On request from web page convert the data from a sweep into JSON and send.
|
||||||
|
* Ideally we would push the data to the web page at the end of a scan.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void onGetSweep ( AsyncWebServerRequest *request )
|
||||||
|
{
|
||||||
|
size_t bufferSize = JSON_ARRAY_SIZE ( DISPLAY_POINTS )
|
||||||
|
+ JSON_OBJECT_SIZE ( 14 ) + DISPLAY_POINTS * JSON_OBJECT_SIZE ( 2 );
|
||||||
|
|
||||||
|
AsyncJsonResponse * response = new AsyncJsonResponse ( false, bufferSize );
|
||||||
|
|
||||||
|
JsonObject root = response->getRoot();
|
||||||
|
|
||||||
|
root["dispPoints"] = DISPLAY_POINTS;
|
||||||
|
root["start"] = setting.ScanStart / 1000.0;
|
||||||
|
root["stop"] = setting.ScanStop / 1000.0;
|
||||||
|
root["IF"] = setting.IF_Freq / 1000000.0;
|
||||||
|
root["attenuation"] = setting.Attenuate;
|
||||||
|
root["levelOffset"] = setting.LevelOffset;
|
||||||
|
root["setRBW"] = setting.Bandwidth10;
|
||||||
|
root["bandwidth"] = bandwidth;
|
||||||
|
root["RefOut"] = setting.ReferenceOut;
|
||||||
|
root["Drive"] = setting.Drive;
|
||||||
|
root["sweepPoints"] = sweepPoints;
|
||||||
|
|
||||||
|
if ( AGC_On )
|
||||||
|
root["PreAmp"] = 0x60; // Auto
|
||||||
|
|
||||||
|
else
|
||||||
|
root["PreAmp"] = setting.PreampGain; // Fixed gain
|
||||||
|
|
||||||
|
JsonArray Points = root.createNestedArray ( "Points" ); // Add Points array
|
||||||
|
|
||||||
|
|
||||||
|
for ( int i = 0; i < DISPLAY_POINTS; i++ ) //For each data point
|
||||||
|
{
|
||||||
|
JsonObject dataPoint = Points.createNestedObject(); // add an object to the array
|
||||||
|
dataPoint["x"] = myFreq[i]/1000000.0; // set the x(frequency) value
|
||||||
|
dataPoint["y"] = myData[i]; // set the y (RSSI) value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
response->setLength();
|
||||||
|
request->send ( response );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On request from web page send the settings as JSON
|
||||||
|
*/
|
||||||
|
void onGetSettings (AsyncWebServerRequest *request)
|
||||||
|
{
|
||||||
|
AsyncJsonResponse * response = new AsyncJsonResponse(false) ;
|
||||||
|
|
||||||
|
JsonObject root = response->getRoot();
|
||||||
|
|
||||||
|
root["mType"] = "Settings";
|
||||||
|
root["dispPoints"] = DISPLAY_POINTS;
|
||||||
|
root["start"] = setting.ScanStart / 1000.0;
|
||||||
|
root["stop"] = setting.ScanStop / 1000.0;
|
||||||
|
root["IF"] = setting.IF_Freq / 1000000.0;
|
||||||
|
root["attenuation"] = setting.Attenuate;
|
||||||
|
root["levelOffset"] = setting.LevelOffset;
|
||||||
|
root["setRBW"] = setting.Bandwidth10;
|
||||||
|
root["bandwidth"] = bandwidth;
|
||||||
|
root["RefOut"] = setting.ReferenceOut;
|
||||||
|
root["Drive"] = setting.Drive;
|
||||||
|
root["sweepPoints"] = sweepPoints;
|
||||||
|
root["spur"] = setting.Spur;
|
||||||
|
|
||||||
|
if ( AGC_On )
|
||||||
|
root["PreAmp"] = 0x60; // Auto
|
||||||
|
|
||||||
|
else
|
||||||
|
root["PreAmp"] = setting.PreampGain; // Fixed gain
|
||||||
|
|
||||||
|
response->setLength();
|
||||||
|
request->send ( response );
|
||||||
|
// Serial.printf ( "Get Settings sweepPoints %u\n", sweepPoints );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Push the settings data to the websocket clients
|
||||||
|
*/
|
||||||
|
void pushSettings ()
|
||||||
|
{
|
||||||
|
size_t capacity = JSON_ARRAY_SIZE ( DISPLAY_POINTS )
|
||||||
|
+ DISPLAY_POINTS*JSON_OBJECT_SIZE ( 2 ) + JSON_OBJECT_SIZE ( 13 );
|
||||||
|
static DynamicJsonDocument jsonDocument ( capacity ); // buffer for json data to be pushed to the web clients
|
||||||
|
|
||||||
|
jsonDocument["mType"] = "Settings";
|
||||||
|
jsonDocument["dispPoints"] = DISPLAY_POINTS;
|
||||||
|
jsonDocument["start"] = setting.ScanStart / 1000.0;
|
||||||
|
jsonDocument["stop"] = setting.ScanStop / 1000.0;
|
||||||
|
jsonDocument["IF"] = setting.IF_Freq / 1000000.0;
|
||||||
|
jsonDocument["attenuation"] = setting.Attenuate;
|
||||||
|
jsonDocument["levelOffset"] = setting.LevelOffset;
|
||||||
|
jsonDocument["setRBW"] = setting.Bandwidth10;
|
||||||
|
jsonDocument["bandwidth"] = bandwidth;
|
||||||
|
jsonDocument["RefOut"] = setting.ReferenceOut;
|
||||||
|
jsonDocument["Drive"] = setting.Drive;
|
||||||
|
jsonDocument["sweepPoints"] = sweepPoints;
|
||||||
|
jsonDocument["spur"] = setting.Spur;
|
||||||
|
|
||||||
|
if ( AGC_On )
|
||||||
|
jsonDocument["PreAmp"] = 0x60; // Auto
|
||||||
|
|
||||||
|
else
|
||||||
|
jsonDocument["PreAmp"] = setting.PreampGain; // Fixed gain
|
||||||
|
|
||||||
|
String wsBuffer;
|
||||||
|
|
||||||
|
if ( wsBuffer )
|
||||||
|
{
|
||||||
|
serializeJson ( jsonDocument, wsBuffer );
|
||||||
|
webSocket.broadcastTXT ( wsBuffer ); // Send to all connected websocket clients
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
Serial.println ( "No buffer :(");
|
||||||
|
|
||||||
|
// Serial.printf ( "Push Settings sweepPoints %u\n", sweepPoints );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Push the settings data to the websocket clients
|
||||||
|
*/
|
||||||
|
void pushIFSweepSettings ()
|
||||||
|
{
|
||||||
|
size_t capacity = JSON_ARRAY_SIZE ( DISPLAY_POINTS )
|
||||||
|
+ DISPLAY_POINTS*JSON_OBJECT_SIZE ( 2 ) + JSON_OBJECT_SIZE ( 13 );
|
||||||
|
static DynamicJsonDocument jsonDocument ( capacity ); // buffer for json data to be pushed to the web clients
|
||||||
|
|
||||||
|
jsonDocument["mType"] = "Settings";
|
||||||
|
jsonDocument["dispPoints"] = DISPLAY_POINTS;
|
||||||
|
jsonDocument["start"] = startFreq_IF / 1000.0;
|
||||||
|
jsonDocument["stop"] = stopFreq_IF / 1000.0;
|
||||||
|
jsonDocument["IF"] = sigFreq_IF / 1000000.0;
|
||||||
|
jsonDocument["attenuation"] = setting.Attenuate;
|
||||||
|
jsonDocument["levelOffset"] = setting.LevelOffset;
|
||||||
|
jsonDocument["setRBW"] = setting.Bandwidth10;
|
||||||
|
jsonDocument["bandwidth"] = bandwidth;
|
||||||
|
jsonDocument["RefOut"] = setting.ReferenceOut;
|
||||||
|
jsonDocument["Drive"] = setting.Drive;
|
||||||
|
jsonDocument["sweepPoints"] = sweepPoints;
|
||||||
|
jsonDocument["spur"] = setting.Spur;
|
||||||
|
|
||||||
|
if ( AGC_On )
|
||||||
|
jsonDocument["PreAmp"] = 0x60; // Auto
|
||||||
|
|
||||||
|
else
|
||||||
|
jsonDocument["PreAmp"] = setting.PreampGain; // Fixed gain
|
||||||
|
|
||||||
|
String wsBuffer;
|
||||||
|
|
||||||
|
if ( wsBuffer )
|
||||||
|
{
|
||||||
|
serializeJson ( jsonDocument, wsBuffer );
|
||||||
|
webSocket.broadcastTXT ( wsBuffer ); // Send to all connected websocket clients
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
Serial.println ( "No buffer :(");
|
||||||
|
|
||||||
|
// Serial.printf ( "Push Settings sweepPoints %u\n", sweepPoints );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Push the settings data to the websocket clients
|
||||||
|
*/
|
||||||
|
void pushBandscopeSettings ()
|
||||||
|
{
|
||||||
|
size_t capacity = JSON_ARRAY_SIZE ( DISPLAY_POINTS )
|
||||||
|
+ DISPLAY_POINTS*JSON_OBJECT_SIZE ( 2 ) + JSON_OBJECT_SIZE ( 13 );
|
||||||
|
static DynamicJsonDocument jsonDocument ( capacity ); // buffer for json data to be pushed to the web clients
|
||||||
|
|
||||||
|
jsonDocument["mType"] = "Settings";
|
||||||
|
jsonDocument["dispPoints"] = setting.BandscopePoints;
|
||||||
|
jsonDocument["start"] = setting.BandscopeStart / 1000.0;
|
||||||
|
jsonDocument["stop"] = ( setting.BandscopeStart + setting.BandscopeSpan ) / 1000.0;
|
||||||
|
jsonDocument["IF"] = setting.IF_Freq / 1000000.0;
|
||||||
|
jsonDocument["attenuation"] = setting.Attenuate;
|
||||||
|
jsonDocument["levelOffset"] = setting.LevelOffset;
|
||||||
|
jsonDocument["setRBW"] = setting.Bandwidth10;
|
||||||
|
jsonDocument["bandwidth"] = bandwidth;
|
||||||
|
jsonDocument["RefOut"] = setting.ReferenceOut;
|
||||||
|
jsonDocument["Drive"] = setting.Drive;
|
||||||
|
jsonDocument["sweepPoints"] = sweepPoints;
|
||||||
|
jsonDocument["spur"] = setting.Spur;
|
||||||
|
|
||||||
|
if ( AGC_On )
|
||||||
|
jsonDocument["PreAmp"] = 0x60; // Auto
|
||||||
|
|
||||||
|
else
|
||||||
|
jsonDocument["PreAmp"] = setting.PreampGain; // Fixed gain
|
||||||
|
|
||||||
|
String wsBuffer;
|
||||||
|
|
||||||
|
if ( wsBuffer )
|
||||||
|
{
|
||||||
|
serializeJson ( jsonDocument, wsBuffer );
|
||||||
|
webSocket.broadcastTXT ( wsBuffer ); // Send to all connected websocket clients
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
Serial.println ( "No buffer :(");
|
||||||
|
|
||||||
|
// Serial.printf ( "Push Settings sweepPoints %u\n", sweepPoints );
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Prepare a response ready for push to web clients
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On request from web page return the list of valid RBW settings from the
|
||||||
|
* "bandpassFilters" array (found in the ".ino" file).
|
||||||
|
*/
|
||||||
|
|
||||||
|
void onGetRbwList ( AsyncWebServerRequest *request )
|
||||||
|
{
|
||||||
|
response = request->beginResponseStream ( "application/json" );
|
||||||
|
// Serial.println ( "onGetRbwList" );
|
||||||
|
|
||||||
|
response->print ( "[" ); // Start of object
|
||||||
|
|
||||||
|
int filterCount = rcvr.GetBandpassFilterCount ();
|
||||||
|
|
||||||
|
for ( int i = 0; i < filterCount-1 ; i++ ) // For each element in the bandpassfilters array
|
||||||
|
response->printf ( "{\"bw10\":%i,\"bw\":%5.1f},",
|
||||||
|
rcvr.GetBandpassFilter10(i),
|
||||||
|
(float) rcvr.GetBandpassFilter10(i) / 10.0 );
|
||||||
|
|
||||||
|
response->printf ( "{\"bw10\":%i,\"bw\":%5.1f}", rcvr.GetBandpassFilter10(filterCount-1),
|
||||||
|
(float) rcvr.GetBandpassFilter10(filterCount-1) / 10.0 );
|
||||||
|
|
||||||
|
response->println ( "]" ); // End of object
|
||||||
|
request->send ( response );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On request from web page return the list of valid attenuations
|
||||||
|
* In the case of the PE4302 this is 0-31.5 in 0.5db steps,
|
||||||
|
* but we will reduce this to 3dB steps
|
||||||
|
* Insertion loss is about 1.5dB
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
void onGetAttenList ( AsyncWebServerRequest *request )
|
||||||
|
{
|
||||||
|
response = request->beginResponseStream ( "application/json" );
|
||||||
|
// Serial.println ( "onGetAttList" );
|
||||||
|
|
||||||
|
response->print ( "[" ); // Start of object
|
||||||
|
|
||||||
|
for ( int i = 0; i < 30 ; i = i + 3 ) // For each possible attenuation
|
||||||
|
response->printf ( "{\"dB\":%i},", i );
|
||||||
|
|
||||||
|
response->printf ( "{\"dB\":%i}", 30 );
|
||||||
|
response->println ( "]" ); // End of object
|
||||||
|
request->send ( response );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Functions to execute when the user presses buttons on the webpage
|
||||||
|
*/
|
||||||
|
|
||||||
|
void onDoReboot ( AsyncWebServerRequest *request )
|
||||||
|
{
|
||||||
|
request->redirect ( "index.html" ); // Redirect to the index page
|
||||||
|
ESP.restart ();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function sets the sweep parameters based on the data in the form posted by the web page
|
||||||
|
* No longer used
|
||||||
|
*/
|
||||||
|
|
||||||
|
// doSetSweep ? setStart = 10 & setStop = 20 & setExtGain = 0 & setRBW = 26
|
||||||
|
|
||||||
|
void onSetSweep ( AsyncWebServerRequest *request )
|
||||||
|
{
|
||||||
|
|
||||||
|
Serial.print ( request->url ()); // Get the paramaters passed from the
|
||||||
|
Serial.print ( ":-" ); // web page, checking they exist
|
||||||
|
|
||||||
|
if ( request->hasParam ( "setStart" ))
|
||||||
|
{
|
||||||
|
Serial.print ( "setStart;" );
|
||||||
|
AsyncWebParameter* startInput = request->getParam ( "setStart" );
|
||||||
|
SetSweepStart ( atof ( startInput->value().c_str()) * 1000000.0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( request->hasParam ( "setStop" ))
|
||||||
|
{
|
||||||
|
Serial.print ( "setStop;" );
|
||||||
|
AsyncWebParameter* stopInput = request->getParam ( "setStop" );
|
||||||
|
SetSweepStop( atof ( stopInput->value().c_str()) * 1000000.0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( request->hasParam ( "setExtGain" ))
|
||||||
|
{
|
||||||
|
Serial.print ( "setExtGain;" );
|
||||||
|
AsyncWebParameter* extGainInput = request->getParam ( "setExtGain" );
|
||||||
|
// Need to add function later
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( request->hasParam ( "refOut" ))
|
||||||
|
{
|
||||||
|
Serial.print ( "refOut;" );
|
||||||
|
AsyncWebParameter* setRefOut = request->getParam ( "setRefOut" );
|
||||||
|
setting.ReferenceOut = atoi ( setRefOut->value().c_str() );
|
||||||
|
xmit.SetPowerReference ( setting.ReferenceOut );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( request->hasParam ( "setAtten" ))
|
||||||
|
{
|
||||||
|
Serial.print ( "setAtten:" );
|
||||||
|
AsyncWebParameter* setAtten = request->getParam ( "setAtten");
|
||||||
|
SetAttenuation ( atoi ( setAtten->value().c_str() ));
|
||||||
|
Serial.print ( atoi ( setAtten->value().c_str() ));
|
||||||
|
Serial.print ( "; " );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( request->hasParam ( "setRBW" ))
|
||||||
|
{
|
||||||
|
Serial.print ( "setRBW ");
|
||||||
|
AsyncWebParameter* rbwInput = request->getParam ( "setRBW" );
|
||||||
|
SetRBW ( atoi ( rbwInput->value().c_str() ));
|
||||||
|
Serial.printf ( "setting.bandwidth = %i, input = %i;", setting.Bandwidth10, atoi ( rbwInput->value().c_str() ));
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println ();
|
||||||
|
request->redirect ( "index.html" ); // redirect to the index page
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function sets the sweep parameters based on the data in the form posted by the web page
|
||||||
|
* No longer used
|
||||||
|
*/
|
||||||
|
|
||||||
|
// doSetSweep ? setStart = 10 &setStop = 20 & setExtGain = 0 & setRBW = 26
|
||||||
|
|
||||||
|
void onSettings ( AsyncWebServerRequest *request )
|
||||||
|
{
|
||||||
|
Serial.print ( request->url() ); // Get the paramaters passed from the web
|
||||||
|
Serial.print ( ":-" ); // page, checking they exist
|
||||||
|
|
||||||
|
if ( request->hasParam ( "setActPower" ))
|
||||||
|
{
|
||||||
|
Serial.print ( "setActPower;" );
|
||||||
|
AsyncWebParameter* setLevelInput = request->getParam ( "setActPower" );
|
||||||
|
SetPowerLevel ( atof ( setLevelInput->value().c_str()) );
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println();
|
||||||
|
request->redirect ( "index.html"); // Redirect to the index page
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void onGetNameVersion ( AsyncWebServerRequest *request )
|
||||||
|
{
|
||||||
|
AsyncResponseStream *response = request->beginResponseStream ( "text/xml" );
|
||||||
|
|
||||||
|
response->printf ( "<?xml version=\"1.0\" encoding=\"utf-16\"?>" );
|
||||||
|
response->printf ( "<IndexNameVersion " );
|
||||||
|
|
||||||
|
response->printf ( "Name=\"%s\" ",PROGRAM_NAME );
|
||||||
|
response->printf ( "Version=\"V%s\" ",PROGRAM_VERSION );
|
||||||
|
response->printf ( "Copyright=\"PD0EK\"" );
|
||||||
|
|
||||||
|
response->printf ( "/>" );
|
||||||
|
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void onGetSIDDs ( AsyncWebServerRequest *request )
|
||||||
|
{
|
||||||
|
char b[1024];
|
||||||
|
b[0] = '\0';
|
||||||
|
|
||||||
|
Serial.println ( "" );
|
||||||
|
Serial.println ( "Scanning for SSIDs" );
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We need to return a blob of XML containing the visible SSIDs
|
||||||
|
*/
|
||||||
|
strcpy ( b, "<SSIDs>" ); // Start of XML
|
||||||
|
|
||||||
|
int n = WiFi.scanComplete ();
|
||||||
|
|
||||||
|
if ( n == -2 )
|
||||||
|
WiFi.scanNetworks ( true );
|
||||||
|
else if ( n )
|
||||||
|
{
|
||||||
|
for ( int i = 0; i < n; ++i )
|
||||||
|
{
|
||||||
|
strcat ( b,"<SSID Name = \"" ); // Add the SSID to the result
|
||||||
|
strcat ( b, WiFi.SSID (i).c_str() );
|
||||||
|
strcat ( b,"\" />" );
|
||||||
|
Serial.println ( "... " + WiFi.SSID (i) );
|
||||||
|
}
|
||||||
|
WiFi.scanDelete ();
|
||||||
|
if ( WiFi.scanComplete() == -2 )
|
||||||
|
WiFi.scanNetworks ( true );
|
||||||
|
}
|
||||||
|
strcat ( b, "</SSIDs>" ); // Complete the XML
|
||||||
|
request->send ( 200, "text/xml", b ); // Send it to the server
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build the web server
|
||||||
|
* the order here is important - put frequent ones at top of list to improve performance
|
||||||
|
*/
|
||||||
|
void buildServer () // We can now configure and start the server
|
||||||
|
{
|
||||||
|
Serial.println ( "Building Server.." );
|
||||||
|
server.reset (); // Clear any existing settings and events
|
||||||
|
server.on ( "/getSweep", HTTP_GET, onGetSweep ); // Set event to return sweep data as JSON array
|
||||||
|
server.on ( "/getGainSweep", HTTP_GET, onGetGainSweep ); // Set event to return sweep gain data as JSON array
|
||||||
|
server.on ( "/getSettings", HTTP_GET, onGetSettings ); // Set event to return settings data as JSON array
|
||||||
|
server.on ( "/doSetSweep", HTTP_POST, onSetSweep ); // Set event to set sweep values received from client
|
||||||
|
server.on ( "/doReboot", HTTP_GET, onDoReboot ); // Set event to reboot the ESP32
|
||||||
|
server.on ( "/getNameVersion", HTTP_GET, onGetNameVersion );// Set event to return name and version
|
||||||
|
server.on ( "/getScan", HTTP_GET, onGetScan ); // Set event to return sweep data as XML
|
||||||
|
server.on ( "/getRbwList", HTTP_GET, onGetRbwList ); // Set event to return RBW options as JSON array
|
||||||
|
server.on ( "/getAttenList", HTTP_GET, onGetAttenList ); // Set event to return attenuator options as JSON array
|
||||||
|
server.on ( "/getSSIDs", HTTP_GET, onGetSIDDs ); // Set event to return list of SSID as XML
|
||||||
|
server.on ( "/doSettings", HTTP_POST, onSettings ); // Set event to set setting values received from client
|
||||||
|
|
||||||
|
server.serveStatic ( "/", SPIFFS, "/" ).setDefaultFile ( "index.html" );
|
||||||
|
|
||||||
|
server.begin ();
|
||||||
|
}
|
87
simpleSA_wifi.h
Normal file
87
simpleSA_wifi.h
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* "TinySA_wifi.h"
|
||||||
|
*
|
||||||
|
* Definitions and function prototypes for the WiFi capability.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TINYSA_WIFI_H_
|
||||||
|
#define TINYSA_WIFI_H_ // Prevent double inclusion
|
||||||
|
|
||||||
|
#include "Arduino.h" // Basic Arduino definitions
|
||||||
|
#include "tinySA.h" // Program definitions
|
||||||
|
#include "Si4432.h" // RF module definitions
|
||||||
|
|
||||||
|
#include <WiFi.h> // WiFi library
|
||||||
|
#include <AsyncTCP.h>
|
||||||
|
#include "ESPAsyncWebServer.h" // ESP32 Webserver library
|
||||||
|
#include "SPIFFS.h" // ESP32 File system
|
||||||
|
#include <TFT_eSPI.h> // Display library
|
||||||
|
|
||||||
|
#include <AsyncJson.h>
|
||||||
|
#include <ArduinoJson.h> // Install using Library Manager or go to arduinojson.org
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Install WebSocketes library by Markus Sattler
|
||||||
|
* https://github.com/Links2004/arduinoWebSockets
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <WebSocketsServer.h>
|
||||||
|
|
||||||
|
#include <HardwareSerial.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#define SSID_NAME "TinySA" // Name of access point
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function prototypes:
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern boolean startAP ();
|
||||||
|
extern boolean connectWiFi ();
|
||||||
|
extern void buildServer ();
|
||||||
|
extern void addTagNameValue ( char *b, char *_name, char *value );
|
||||||
|
extern char *escapeXML ( char *s );
|
||||||
|
extern void webSocketEvent ( uint8_t num, WStype_t type, uint8_t* payload, size_t lenght );
|
||||||
|
extern char *FormatIPAddress ( IPAddress ipAddress );
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Functions outside of "TinySA_wifi:
|
||||||
|
*/
|
||||||
|
|
||||||
|
int GetPointsAsXML ( void textHandler (char *s) );
|
||||||
|
void set_sweep_frequency ( int type, int32_t frequency );
|
||||||
|
void SetRBW ( int );
|
||||||
|
void SetAttenuation ( int a );
|
||||||
|
void RequestSetPowerLevel ( float o );
|
||||||
|
void SetPowerLevel ( int o );
|
||||||
|
void SetPowerReference (int freq );
|
||||||
|
void SetLoDrive ( uint8_t level );
|
||||||
|
bool SetIFFrequency ( int32_t f );
|
||||||
|
void SetPreAmpGain ( int g );
|
||||||
|
void WriteSettings ();
|
||||||
|
void SetSpur ( int v );
|
||||||
|
|
||||||
|
/*
|
||||||
|
* variables and objects outside of TinySA_wifi
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern settings_t setting;
|
||||||
|
|
||||||
|
extern uint8_t myData[DISPLAY_POINTS+1];
|
||||||
|
extern uint8_t myStorage[DISPLAY_POINTS+1];
|
||||||
|
extern uint8_t myActual[DISPLAY_POINTS+1];
|
||||||
|
extern uint8_t myGain[DISPLAY_POINTS+1]; // M0WID addition to record preamp gain
|
||||||
|
extern uint32_t myFreq[DISPLAY_POINTS+1]; // M0WID addition to store frequency for XML file
|
||||||
|
|
||||||
|
|
||||||
|
extern WebSocketsServer webSocket; // Initiated in TinySA.ino
|
||||||
|
extern TFT_eSPI tft; // TFT Screen object
|
||||||
|
extern bandpassFilter_t bandpassFilters[11];
|
||||||
|
extern float bandwidth;
|
||||||
|
extern uint32_t sweepPoints; // Number of points in the sweep. Can be more than DISPLAY_POINTS if RBW is set less than video resolution
|
||||||
|
|
||||||
|
#endif
|
32
ui.h
Normal file
32
ui.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* "ui.h"
|
||||||
|
*
|
||||||
|
* This file contains the definitions of things related to the TinySA touch screen
|
||||||
|
* interface.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h> // Standard stuff
|
||||||
|
#include "tinySA.h" // General definitions for the program
|
||||||
|
#include "preferences.h" // Things to save in flash memory
|
||||||
|
|
||||||
|
#ifndef _UI_H_
|
||||||
|
#define _UI_H_ // Prevent double inclusion
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The "UI_XXXX" symbols define the various modes that the user interface might
|
||||||
|
* be in such as normal mode, using a touch menu, reading a keypad, etc.
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum { UI_NORMAL, UI_MENU, UI_NUMERIC, UI_KEYPAD };
|
||||||
|
|
||||||
|
#define MENU_STACK_DEPTH 6 // Maximum number of menu levels
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "UiProcessTouch" is called from the "loop" function in the main program:
|
||||||
|
*/
|
||||||
|
|
||||||
|
void UiProcessTouch ( void );
|
||||||
|
void ShowSplash ( void );
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue
Block a user