Add files via upload

This commit is contained in:
DPWilde 2020-08-15 19:03:43 +01:00 committed by GitHub
commit b04a218586
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 14928 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

586
Bandscope.ino Normal file
View 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
View 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
View 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
View 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
View 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
View 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"

2886
cmd.cpp Normal file

File diff suppressed because it is too large Load Diff

192
cmd.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

866
simpleSA_wifi.cpp Normal file
View 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,"&quot;" );
break;
case '&':
strcat (b, "&amp;" );
break;
case '<':
strcat ( b,"&lt;" );
break;
case '>':
strcat ( b,"&gt;" );
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
View 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

3196
ui.cpp Normal file

File diff suppressed because it is too large Load Diff

32
ui.h Normal file
View 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